Running a TFTP Server Properly
On paper, running a TFTP server is the easiest job in networking: install one package, point it at one folder, done. That apparent triviality is exactly why so many TFTP servers are quietly broken — nobody believed anything this simple needed attention, so nobody gave it any. Then a switch upgrade fails at midnight with %Error opening tftp://... (Timed out) and the "trivial" server gets an hour of frantic attention it never got at setup time.
TFTP servers also fail in an unusually unhelpful way. The clients are often boot ROMs and recovery loaders — software too small to explain itself, capable of reporting little more than "timeout" or "file not found." All the diagnostic burden lands on the server side. A server set up properly, with logging on and a few habits in place, turns those mysteries into two-minute reads of a log file.
This article is the server side done right: choosing the machine's role, a clean installation you can copy, root directory and permission discipline, the tuning knobs that matter, and the troubleshooting playbook for the classic failures. It is part of our TFTP series — the protocol behavior underneath (lock-step blocks, the port-69-then-ephemeral dance, option negotiation) is explained in how TFTP works and assumed here.
Decide What This Server Is For
Before touching a package manager, name the server's job, because the job decides the settings:
- Boot server (PXE). Serves bootloaders, kernels, and boot configs. Strictly read-only, needs solid support for the block-size and transfer-size options (boot loaders lean on them — see PXE boot and TFTP), and benefits from living close to the machines that boot from it.
- Config drop zone. Receives device config backups. This is the one role that needs uploads enabled — the most dangerous setting a TFTP server has, so it gets a fenced inbox and a sweep job, per the workflow in our configs and firmware article.
- Firmware shelf. Serves images to devices during upgrades. Read-only, occasionally busy with large files, so block-size tuning pays off most here.
One server can do all three, but separating writes from everything else — even just into different directories with different rules — keeps each role's risks from contaminating the others. Wherever it runs, the machine belongs on the management network with the containment described in TFTP security and containment: this article covers making the server work; that one covers keeping it fenced.
As for software: every platform has options. On Linux, tftpd-hpa is the widely packaged standalone classic, and dnsmasq bundles a TFTP server alongside DHCP (convenient for PXE labs). Windows has several service and GUI implementations. The walkthrough below uses tftpd-hpa on a Debian-family system because it is common and its settings map cleanly to concepts every server shares — adapt names and paths to your platform and the ideas carry over.
Installation and First Start
Here is the whole setup, verified at each step. Run it top to bottom and you finish with a server you have proven works — not one you merely hope does:
# 1. Install the server, plus a command-line client for testing $ sudo apt install tftpd-hpa tftp-hpa # 2. Look at the settings before changing anything $ cat /etc/default/tftpd-hpa TFTP_USERNAME="tftp" TFTP_DIRECTORY="/srv/tftp" TFTP_ADDRESS=":69" TFTP_OPTIONS="--secure" # 3. Make sure the root exists and belongs to the service account $ sudo mkdir -p /srv/tftp $ sudo chown tftp:tftp /srv/tftp # 4. Restart, then confirm something is listening on UDP port 69 $ sudo systemctl restart tftpd-hpa $ sudo ss -lun | grep ':69' UNCONN 0 0 0.0.0.0:69 0.0.0.0:* # 5. Plant a test file and fetch it through the front door $ echo "tftp server alive" | sudo tee /srv/tftp/canary.txt $ cd /tmp && tftp 127.0.0.1 -c get canary.txt && cat canary.txt tftp server alive # 6. Repeat the fetch from ANOTHER machine on the device subnet $ tftp 192.0.2.50 -c get canary.txt
Each line earns its keep. The four settings in step 2 are the server's whole personality: the unprivileged account it runs as, the root directory it serves, the address and port it listens on, and its behavior flags — --secure being the important default, explained in the next section. Step 4's ss check catches the embarrassing class of failure (service not actually running, port taken by something else) before any device does. And step 6 matters more than step 5: a loopback test proves the daemon works, but only a fetch from another machine proves the network lets transfers happen — firewalls do not apply to loopback, so localhost success plus remote failure points straight at filtering.
Remember: always finish a TFTP server change by testing from a real machine on the same network segment the devices use — not from the server itself. The lock-step transfer's reply comes from an ephemeral port, and a firewall that passes the request but drops the replies produces the exact "timeout" a dead server would. Loopback tests cannot see that failure; remote tests cannot miss it.
Root Directory Discipline
The --secure flag is the first thing to understand, because it silently rewrites what every filename means. With it, the server locks itself into the root directory at startup (a chroot, in Unix terms — the directory becomes the daemon's entire visible world), and every path a client requests is interpreted relative to that root. A client asking for boot/loader.efi gets /srv/tftp/boot/loader.efi. This is both a safety property — requests can never escape the tree, no matter how creatively they are written — and the source of the single most classic TFTP configuration error:
The double-path trap. An administrator, knowing the file lives at /srv/tftp/images/fw.bin, configures the client to request exactly that path. The server, secure in its chroot, appends the request to its root and looks for /srv/tftp/srv/tftp/images/fw.bin — which does not exist. The client reports "file not found"; the administrator, staring directly at the existing file, concludes the server is haunted. The rule that dissolves the confusion: clients request paths as seen from inside the root. Here, the correct request is images/fw.bin.
Three more habits keep the root boring in the best sense:
- Match case exactly. On Linux filesystems,
Firmware.BINandfirmware.binare different files. Devices request literal strings; copy-paste filenames into device configs rather than retyping them. - Structure by role.
boot/,firmware/,inbox/— one subdirectory per job, so permissions can differ per job and a directory listing explains itself to the next administrator. - Give the root its own filesystem or quota. A config drop zone accepts writes; writes can fill disks; a full disk on a shared filesystem takes the whole host down with it. A modest dedicated volume turns that outage into an error code.
Permissions That Match the Role
The daemon starts as root (it must, to claim port 69) and immediately drops to the unprivileged account from its settings — tftp here. From then on, ordinary file permissions decide everything, evaluated as that account:
- For downloads: files must be readable by the
tftpuser. A file copied into the root asrootwith mode600is invisible to clients in the most confusing way — the server is running, the file exists, and every request fails with "access violation" or "file not found" depending on the implementation. - For uploads, the rules surprise everyone. Out of the box,
tftpd-hpaand its relatives accept an upload only onto a file that already exists and is world-writable. A device pushingsw1.cfgfor the first time is refused with "access violation" — the historical safety default. You either pre-create each file (touch sw1.cfg && chmod 666 sw1.cfg— workable for a handful of devices, absurd for fifty) or enable creation properly:
# /etc/default/tftpd-hpa — a drop zone that can accept new files TFTP_OPTIONS="--secure --create --umask 022" # Fence writes into one inbox owned by the service account $ sudo mkdir -p /srv/tftp/inbox $ sudo chown tftp:tftp /srv/tftp/inbox $ sudo chmod 755 /srv/tftp/inbox
With --create, new files are allowed wherever the tftp user can write — so the discipline is to make the inbox the only directory it can write to (own the rest of the tree as root, read-only for the service account). That single decision converts "anyone can overwrite my boot files" into "uploads land in one fenced folder that a sweep job empties." If the server's role never includes uploads, skip --create entirely and enjoy a server on which writes are impossible.
Timeouts, Retries, and Block Size
The defaults suit a healthy LAN, which is where most TFTP servers live, so the honest first advice is: tune nothing until a measurement tells you to. When one does, three knobs matter, whatever your server calls them:
- Retransmission timeout. How long the sender waits for an ACK before resending the last packet (the lock-step's recovery mechanism). Raise it for genuinely slow or long-haul paths where retransmissions are firing for packets that were merely late. Check the units in your server's documentation before touching it —
tftpd-hpa's--timeout-family options take microseconds in places where a human expects seconds, and a value six orders of magnitude too small produces a retransmission storm that looks like network failure. - Maximum block size. Clients negotiate block size upward via the
blksizeoption; the server enforces a ceiling (--blocksizein tftpd-hpa). The sweet spot on standard Ethernet is 1,468 bytes or slightly lower: block plus TFTP, UDP, and IP headers then fits inside one 1,500-byte frame. Allow much more and each block gets fragmented — split across multiple IP packets that must all arrive to count — which some minimal boot-time network stacks handle poorly, producing transfers that start briskly and then stall. If a PXE client dies partway through a kernel download, an over-generous block size is a leading suspect. - Ephemeral port range. Some servers (tftpd-hpa:
--port-range 40000:40100) can confine the transfer-side ports the protocol picks after the initial request to a fixed range. Operationally golden when strict or stateless firewalls stand between clients and server: instead of "allow high UDP ports," the rule becomes "allow 40000–40100 to this one host."
One non-knob worth restating: TFTP will never be fast over distance. One block per round trip is the protocol's nature; a bigger block softens it, and nothing removes it. When someone asks you to "fix the slow TFTP transfer" across a WAN, the correct fixes are a server nearer the clients or a heavier protocol — the arithmetic is in the protocol article.
The Classic Troubleshooting Cases
When a device-side transfer fails, resist the urge to squint at the device's two-word error. Go to the server and run the same short diagnostic ladder every time — it localizes almost any TFTP failure in a few minutes:
# 1. Is the service alive and listening? $ systemctl status tftpd-hpa $ sudo ss -lunp | grep ':69' # 2. Watch requests arrive as the device retries (logging tells all) $ sudo journalctl -u tftpd-hpa -f # no line when the device tries? -> the request never arrived: network/firewall # "RRQ from ..." then silence? -> replies not getting back: filtering, ports # an error in the log? -> the log names the file and the reason # 3. Watch the wire itself: the request and the ephemeral-port replies $ sudo tcpdump -ni any 'udp port 69 or (udp and portrange 32768-60999)' # 4. Reproduce with a normal client from the device's subnet $ tftp 192.0.2.50 -c get images/fw.bin # 5. Check what the DAEMON can see, as the account it runs as $ sudo -u tftp ls -l /srv/tftp/images/ $ sudo -u tftp head -c1 /srv/tftp/images/fw.bin >/dev/null && echo readable
Step 5 is the underused one: testing as the service account catches every permission and path problem in one command, including the ones invisible when you look as root. With the ladder's evidence in hand, the classic cases map to fixes almost mechanically:
| Symptom | Usual cause | Fix |
|---|---|---|
| "File not found," but you can see the file | Double-path trap (client sent the full server-side path), or a case mismatch | Request the path as seen from inside the root; copy-paste exact filenames |
| "Access violation" on download | File not readable by the service account; sometimes a security framework (SELinux/AppArmor) denying quietly | Fix ownership/mode (step 5 proves it); check the audit log if permissions look right |
| "Access violation" on upload | Server refuses new files by default (no --create), or inbox not writable by the service account |
Enable --create with a fenced inbox, or pre-create the target file |
| Timeout; nothing works at all | Service down, wrong server IP on the device, or firewall dropping the request or the ephemeral-port replies | Ladder steps 1–3 split it: no log line = network in; log line then silence = replies out |
| Transfer starts, then stalls partway | Fragmentation from an oversized negotiated block, or a lossy path outrunning the timeout | Cap block size near 1,468; revisit timeout; test the path for loss |
| Upload "succeeded" but the file is empty or short | Transfer died mid-write; TFTP leaves the partial file with no warning | Verify sizes after every upload; make the sweep job refuse files that end abruptly |
Keep It Healthy
A TFTP server that matters — and if devices boot or back up through it, it matters — deserves the same modest operational care as any other service:
- Leave verbose logging on and ship the logs. The per-request log lines are your only visibility into an unauthenticated service, for troubleshooting and for security alike (the alerting side is covered in the containment article).
- Run a canary fetch on a schedule. A monitoring job that TFTP-fetches a small known file every few minutes, from a machine on the device subnet, catches dead services and broken firewall paths before a midnight upgrade does. This is the single highest-value habit on this list.
- Watch the disk if uploads are enabled, and alert well before full.
- Inventory the tree. A nightly listing (or hash) of the root, diffed against yesterday's, catches both intruders and colleagues who "just added a file for a minute."
- Write the runbook line. Root path, options, roles of each subdirectory, where the logs go, how the canary works. Five lines in the wiki turns your careful setup into the team's careful setup.
The Server in One Paragraph
Give the server one named job, install it with the walkthrough above, and prove it with a remote fetch before any device depends on it. Serve a chrooted root where client paths are relative, case matters, and the double-path trap is dissolved by rule rather than rediscovered by outage. Let permissions be evaluated as the unprivileged service account, enable file creation only into a fenced inbox — or not at all — and tune only the three knobs that matter: retransmission timeout, block-size ceiling, port range. When something fails, run the diagnostic ladder and let the log, the wire, and the as-the-daemon file test point at the layer that lied.
With the server solid, the natural companions are the config and firmware procedures that will use it, the PXE chain if it serves boots, and — before anything touches production — the containment posture that keeps a defenseless protocol out of trouble.
Frequently Asked Questions
Why does the server say "file not found" when the file is right there?
How do I let devices upload backups to my server?
Which firewall ports does a TFTP server need open?
Can several devices transfer at the same time?
Does the TFTP server need to run as root?
Is a Windows or a Linux TFTP server better?
From the Sysax team: we build secure file transfer software for Windows — Sysax Multi Server, an FTP, FTPS, SFTP, and HTTPS server, and Sysax FTP Automation for scheduled, scripted transfers. Free trials are on the download page.
