TFTP, the Trivial File Transfer Protocol, is a small UDP protocol that can only read and write files, specified as revision 2 in RFC 1350 in July 1992. It has no login, no encryption and no integrity check, yet it still boots PXE clients and provisions switches, routers, IP phones and thin clients. You contain it by serving read only files from a locked root, on a provisioning network only, with signed or hashed content and a log of every transfer.
That is the short answer. The rest of this guide goes through the packet formats, the option extensions that made TFTP fast enough for modern boot images, the security consequences of a protocol with no authentication, a lab build of a contained server, and a plan for moving devices off TFTP where they can move.
Key takeaways
- RFC 1350 (STD 33, July 1992) replaced RFC 783 (June 1981). Options came later: RFC 2347, 2348 and 2349 in May 1998, and RFC 7440 (
windowsize) in January 2015. - A client asks on UDP port 69 and the server answers from a new port, which is why TFTP and simple firewalls fight.
- Base TFTP sends one 512 byte block per round trip. Larger blocks and windows cut round trips, which is where the speed comes from.
- TFTP "includes no login or access control mechanisms" (RFC 1350). Anyone on the path can read a transfer, and anyone who reaches a writable server can replace what devices load.
- MITRE ATT&CK tracks abuse of TFTP network boot as T1542.005.
- Containment is configuration: read only service, a rooted directory, a provisioning VLAN, firewall rules, signed or hashed files and transfer logs.
- The long term fix is authenticated provisioning (HTTPS, signed configuration, Secure Zero Touch Provisioning per RFC 8572), keeping TFTP only for devices that can do nothing else.
What is TFTP, and when was it specified?
TFTP is a file transfer protocol built to be small enough to fit in boot ROM. RFC 1350 describes its scope in one line: "The only thing it can do is read and write files (or mail) from/to a remote server." It cannot list directories, rename or delete files, and it has no user accounts.
The history is short and worth knowing because it explains the design:
| Date | Document | What it did |
|---|---|---|
| June 1981 | RFC 783 | First published TFTP specification (revision 2 lineage starts here) |
| June 1984 | RFC 906 | Proposed TFTP for bootstrap loading of diskless machines |
| October 1989 | RFC 1123 | Host requirements: mandated the Sorcerer's Apprentice fix, adaptive timeouts and configurable path access control |
| July 1992 | RFC 1350 (STD 33) | TFTP revision 2, the current base specification; obsoleted RFC 783 |
| March 1995 | RFC 1785 | Showed option negotiation was backward compatible with unmodified servers |
| May 1998 | RFC 2347, 2348, 2349 | Option negotiation, block size, timeout and transfer size options (obsoleting RFC 1782, 1783, 1784) |
| October 2003 | RFC 3617 | Defined the tftp:// URI and strongly recommended against continued TFTP use |
| November 2006 | RFC 4578 | DHCP options for PXE, including the client system architecture type |
| September 2010 | RFC 5970 | DHCPv6 boot file URL option, which can point at TFTP or HTTP |
| January 2015 | RFC 7440 | Window size option for multiple blocks per acknowledgement |
| April 2019 | RFC 8572 | Secure Zero Touch Provisioning, an authenticated alternative for network devices |
One date wrinkle: RFC 1350's own header says July 1992, while RFC 2348 and RFC 2349 cite it as October 1992. We use the document's date.
Why is TFTP still used?
RFC 1123 gave the reason in 1989: TFTP's most important application is bootstrapping a host over a local network, because it is "simple and small enough to be easily implemented in EPROM". That is still true. A boot ROM, a network card's PXE firmware or a switch's recovery loader can carry a UDP stack and a TFTP client in very little code. An HTTP client with TLS, certificate validation and a trust store is much larger.
RFC 7440 put it bluntly in 2015: TFTP is "virtually unused" for Internet transfers, but "still massively used in network boot/installation scenarios including EFI". In practice you will find TFTP in these places:
- PXE network boot. Legacy PXE clients fetch the boot file named by DHCP over TFTP. Our PXE and UEFI HTTP boot guide covers that flow, so this post does not repeat it.
- Switch and router provisioning. Many network operating systems fetch a startup configuration or image by TFTP at first boot, and scripts still copy configuration backups to TFTP servers. See our zero touch provisioning guide.
- IP phones, thin clients and embedded devices. These commonly fetch firmware and per device configuration by TFTP, and those files often carry credentials.
- Recovery. Many boot loaders and recovery consoles support only TFTP.
How does TFTP work on the wire?
A TFTP transfer has three parts: a request, a lock step data exchange, and a short final packet that marks the end. Everything runs over UDP, so TFTP carries its own sequence numbers, acknowledgements and timeouts.
The five packet types
RFC 1350 defines five opcodes. RFC 2347 added a sixth.
| Opcode | Name | Contents | Sent by |
|---|---|---|---|
| 1 | RRQ (read request) | File name, zero byte, mode, zero byte, optional options | Client |
| 2 | WRQ (write request) | Same layout as RRQ | Client |
| 3 | DATA | 2 byte block number, 0 to 512 bytes of data (more with blksize) | Whoever is sending the file |
| 4 | ACK | 2 byte block number being acknowledged | Whoever is receiving the file |
| 5 | ERROR | 2 byte error code, message text, zero byte | Either side |
| 6 | OACK (RFC 2347) | Accepted option names and values | Server |
The mode field is a text string: netascii, octet or mail. Netascii is 8 bit ASCII with Telnet style line endings, octet is raw bytes, and RFC 1350 says mail mode "is obsolete and should not be implemented or used". Every boot file and firmware image you care about moves in octet mode.
Error codes
RFC 1350 lists eight error codes, and RFC 2347 adds a ninth:
| Code | Meaning |
|---|---|
| 0 | Not defined, see error message |
| 1 | File not found |
| 2 | Access violation |
| 3 | Disk full or allocation exceeded |
| 4 | Illegal TFTP operation |
| 5 | Unknown transfer ID |
| 6 | File already exists |
| 7 | No such user |
| 8 | Terminate transfer due to option negotiation (RFC 2347) |
An ERROR packet is never acknowledged or retransmitted, so timeouts are the real failure detector. When troubleshooting, code 2 from a server usually means containment is working (a write was refused or a path left the root), and code 1 often means a case or path mismatch.
Port 69, then ephemeral ports
TFTP calls its port numbers transfer identifiers (TIDs). The client picks a random source port and sends its request to the server's well known port, 69. The server answers from a new port it picks for that transfer, and both sides use that pair for the rest of the exchange. RFC 1350 tells each side to check the source TID on every packet, discard packets from the wrong port, and answer them with error 5 without disturbing the real transfer.
This detail matters for operations. A stateful firewall that allows UDP to port 69 does not automatically allow the server's reply from a different port. Firewalls handle it with a TFTP helper, with an explicit data port range, or not at all. RFC 3617 notes that "many NATs will not either support or allow TFTP transfers" for the same reason. Servers help by pinning the data ports to a range: dnsmasq has --tftp-port-range, and tftp-hpa's tftpd has --port-range.
dnsmasq also offers --tftp-single-port, which answers from port 69 itself so transfers survive NAT. Its manual says this "is not strictly compliant with the RFCs specifying the TFTP protocol: use at your own risk".
A read transfer, step by step
- The client sends RRQ for
boot.imgin octet mode from port 50123 to server port 69. - The server picks port 41000 and sends DATA block 1 (512 bytes); the client ACKs block 1, and so on.
- A DATA block with 0 to 511 bytes marks the end. The client ACKs it and should "dally" briefly in case that ACK was lost.
A write mirrors this: WRQ, then ACK block 0 from the server, then DATA from block 1. A file that is an exact multiple of 512 bytes ends with a zero byte DATA packet.
Lock step, block numbers and the size ceiling
Base TFTP keeps one packet in flight. RFC 1350 explains that "the lock step acknowledgement provides flow control and eliminates the need to reorder incoming data packets". It also means every block costs one full round trip.
The block number field is 16 bits, so a transfer can count up to 65,535 blocks before the number wraps. With 512 byte blocks that caps a single transfer at 65,535 × 512 = 33,553,920 bytes, about 32 MiB, unless both sides agree how to handle rollover. Rollover is not standardised: tftp-hpa documents a nonstandard rollover option and notes that "some clients expect 1 instead" of 0. Larger blocks lift the ceiling: at 1,468 bytes it is 96,205,380 bytes, and at the RFC 2348 maximum of 65,464 bytes it is 4,290,183,240 bytes.
The Sorcerer's Apprentice bug
RFC 1123 (section 4.2.3.1) describes a flaw in the original specification: if an ACK is delayed rather than lost, both sides start resending on every duplicate, so every packet is sent twice from then on. The required fix is that the sender never resends the current DATA packet just because a duplicate ACK arrived. RFC 1123 also requires adaptive timeouts with exponential backoff, and says a server "SHOULD include some configurable access control over what pathnames are allowed".
What did the option extensions add?
By the mid 1990s, 512 byte lock step was the bottleneck. The fix had to stay compatible with servers that had never heard of options, and it had to fit in a request packet.
RFC 2347: option negotiation and OACK
RFC 2347 appends option name and value pairs, each terminated by a zero byte, after the mode field of an RRQ or WRQ. The rules are short:
- Only the client can propose options. The server "must not include in the OACK any option which had not been specifically requested by the client".
- The server returns an OACK listing the options it accepts. Unknown options are simply left out, not refused.
- After a read request, the client confirms the OACK by sending ACK block 0. After a write request, it confirms by sending DATA block 1.
- If the client dislikes the OACK, it sends ERROR code 8 and the transfer ends.
- A request packet, options included, may be at most 512 octets.
Backward compatibility was tested before the standard. RFC 1785 (March 1995) sent options to five unmodified servers and checked the BSD 4.3 tftpd source. In each case the server ignored the extra fields and transferred normally. That is why a modern client can always ask for options: an old server just answers with DATA instead of OACK.
RFC 2348: blksize
The blksize option asks for a block size between 8 and 65,464 octets. The server may answer with the same or a smaller value, and the client must use what the OACK says or abort with error 8. The end of file rule is unchanged: a block shorter than the negotiated size is the last one.
What value to pick depends on the path MTU. On standard 1,500 byte Ethernet with IPv4, the arithmetic is 1,500 minus 20 bytes of IPv4 header, minus 8 bytes of UDP header, minus 4 bytes of TFTP header, which leaves 1,468 bytes of data. RFC 7440 uses that figure: blocks "no larger than 1468 Bytes to avoid IP block fragmentation". With IPv6 the header is 40 bytes, so the same frame carries 1,448 bytes of data. RFC 2348's own example uses 1,428, which leaves extra room for headers; either works, and anything larger than the path allows causes IP fragmentation.
RFC 2348 included its own test results from 2.25 MB transfers on lightly loaded Ethernet between two HP-UX machines. Without a gateway in the path, a 512 byte transfer took 23.85 seconds, 1,428 bytes took 13.70 seconds, and 8,192 bytes took 4.90 seconds, a 42 percent and 80 percent reduction respectively. The RFC explains why: doubling the block size halves both the DATA packets and the ACK packets, and with them the number of times the sender waits. It also warns that blocks bigger than the path MTU bring fragmentation overhead that grows with every gateway.
RFC 2349: timeout and tsize
RFC 2349 defines two options in one document.
timeout lets the client ask for a retransmission timeout from 1 to 255 seconds. Unlike blksize, the server must either accept the exact value or leave it out of the OACK.
tsize exchanges the file size. In a read request the client sends tsize 0, and the server returns the real size in the OACK. In a write request the client states the size and the server echoes it back. Either side can abort with error code 3 if the file is too large for it. This is more useful than it looks: a boot loader can check that a kernel will fit in memory before downloading it, and a server can refuse an oversized upload before it fills a disk. tftp-hpa's current manual page ties its --max-upload limit to the tsize value, refusing write requests that announce a size above the limit.
RFC 7440: windowsize
RFC 7440 attacks the remaining cost: one acknowledgement per block. The windowsize option (1 to 65,535 blocks) lets the sender transmit a window of consecutive blocks, then wait for an ACK of the last block in the window. If the receiver sees a gap, it ACKs the last block it received in order, and the sender restarts the window from there. A window size of 1 "MUST be equivalent" to RFC 1350 lock step.
The RFC's own test moved a 180 MB file over lightly loaded Gigabit Ethernet with 1,456 byte blocks. Lock step took 257 seconds. A window of 4 took 76 seconds, 16 took 42 seconds and 64 took 35 seconds. RFC 7440's example setting for 1 Gbit/s Ethernet is window size 8 with 1,456 byte blocks, and it says implementations should cap retries so a failing transfer aborts.
How the options change speed: two worked examples
The simplest useful model of lock step TFTP on a low loss network is: transfer time is about the number of round trips multiplied by the round trip time, plus the time to put the bytes on the wire. This ignores processing delays and loss, but it shows where the time goes.
Worked example 1: a 50 MiB boot image on a local network. The file is 52,428,800 bytes. Assume a round trip time of 0.5 milliseconds and a 1 Gbit/s link.
| Settings | DATA packets | Round trips | Wait time at 0.5 ms | Approximate total |
|---|---|---|---|---|
| Base TFTP, 512 byte blocks | 102,401 (an exact multiple, so one empty final block) | 102,401 | 51.2 s | about 52 s |
blksize 1468 | 35,715 | 35,715 | 17.9 s | about 18 s |
blksize 1468, windowsize 16 | 35,715 | 2,233 | 1.1 s | about 1.5 s |
The base case cannot even finish in one pass without rollover handling: 102,401 blocks exceeds the 65,535 block counter. With blksize 1468 the block count fits. The last row adds roughly 0.44 seconds of serialisation time (about 55 MB of frames at 1 Gbit/s) to the 1.1 seconds of waiting.
Worked example 2: the same image across a WAN link. Now assume a 40 millisecond round trip, which is common for a branch office booting from a data centre.
| Settings | Round trips | Wait time at 40 ms |
|---|---|---|
| Base TFTP, 512 byte blocks | 102,401 | 4,096 s, about 68 minutes |
blksize 1468 | 35,715 | 1,429 s, about 24 minutes |
blksize 1468, windowsize 16 | 2,233 | 89 s |
blksize 1468, windowsize 64 | 559 | 22 s |
Block size and window size both cut round trips, and their effects multiply. These figures come from the model, not a measurement, so test your own path before planning around them.
Options that break clients
Not every client handles options well. dnsmasq has --tftp-no-blocksize because "some buggy clients" misbehave when the option is granted, and tftp-hpa has --refuse, --refuse-all and --blocksize mtu for embedded clients that cannot handle fragments. If a device boots from one server and not another, check option handling first.
Why does TFTP have no authentication or encryption?
Because it was designed in 1981 to fit in a boot ROM, on networks where every host was trusted. RFC 1350's two sentence security section warns that because TFTP has no login or access control, "care must be taken in the rights granted to a TFTP server process". RFC 1123 noted that nonstandard password extensions existed but none had been standardised, and none has been since.
The option RFCs did not change this: RFC 2348 and RFC 2349 say "the basic TFTP protocol has no security mechanism". RFC 7440 adds that TFTP "includes no protection against an on-path attacker". RFC 3617, which defined the tftp:// URI, recommended in capital letters against continued use of TFTP at all.
What follows from that
Without authentication, confidentiality or integrity, four consequences follow. None of them needs an exotic attack to happen.
- Anyone who can reach the server can read any served file. The only access control is who can send UDP to port 69 and which files the server process can open. Phone and switch configurations often carry SNMP communities, account hashes or provisioning passwords.
- Anyone on the path can read the transfer. TFTP is cleartext UDP.
- Anyone on the path can alter the transfer. RFC 3617 notes "no inherent integrity check"; the UDP checksum catches line errors, not deliberate changes.
- A writable server lets anyone who reaches it replace what devices load. Even without server writes, whoever can answer DHCP or change a device's configured server address can point it at a TFTP server they control.
MITRE ATT&CK T1542.005 at concept level
MITRE ATT&CK lists this pattern as T1542.005, Pre-OS Boot: TFTP Boot, a sub-technique under the Persistence and Stealth tactics for the Network Devices platform. The technique page (version 2.0, last modified 12 May 2026) describes adversaries changing a network device's configuration so that it netboots an unauthorized operating system image from a TFTP server they control, gaining persistence below the level most monitoring sees. Our T1542.005 explainer goes deeper on the technique; here is what matters for defenders.
MITRE's mitigations map directly onto the controls in this guide:
| MITRE mitigation | What it means for a TFTP deployment |
|---|---|
| M1047 Audit | Periodically check the integrity of the running configuration and system image |
| M1046 Boot Integrity | Enable secure boot features so a device refuses an image whose signature does not validate |
| M1035 Limit Access to Resource Over Network | Restrict protocols without encryption or authentication, and keep management interfaces away from untrusted networks |
| M1031 Network Intrusion Prevention | Use signatures for protocols such as TFTP at the network level |
| M1028 Operating System Configuration | Disable unused services such as an unused TFTP client or boot fallback, and log |
| M1026 Privileged Account Management | Use AAA so configuration changes, including boot source changes, are authorised and recorded |
MITRE's detection strategy looks for boot configuration changes that point at TFTP servers, unusual firmware loads during netboot, and unexpected image hashes. Each depends on records you keep yourself: a baseline of boot settings, approved image hashes and transfer logs. TFTP cannot tell a device whether a file is genuine, so something else has to: a signature check in the boot chain, a hash checked by a script, or an authenticated transport.
TFTP versus HTTP boot versus SFTP or SCP versus HTTPS
The decision table below compares TFTP with the transports that usually replace it. "HTTP boot" here means UEFI HTTP boot or a boot loader fetching over HTTP; it can run over plain HTTP or HTTPS, and the security properties depend on which.
| Property | TFTP | HTTP boot (plain HTTP) | HTTPS boot or HTTPS provisioning | SFTP or SCP |
|---|---|---|---|---|
| Transport | UDP, port 69 plus ephemeral data ports | TCP, usually port 80 | TCP, usually port 443 | TCP, port 22 |
| Server authentication | None | None | Yes, by TLS certificate, if the client validates it | Yes, by SSH host key, if the client checks it |
| Client authentication | None | Optional at application level | Optional, client certificates or tokens | Yes, keys or passwords |
| Encryption | None | None | Yes | Yes |
| Integrity in transit | None beyond UDP checksum | TCP checksum only | Yes, authenticated by TLS | Yes, authenticated by SSH |
| Integrity of the file itself | Needs separate signature or hash | Needs separate signature or hash | Transport proves the server, not the file; signing still recommended | Same as HTTPS |
| Firewall and NAT friendliness | Poor, because replies come from a new port | Good | Good | Good |
| Large file performance | Poor without blksize and windowsize | Good | Good | Good |
| Support in boot ROMs | Near universal for PXE clients | Many UEFI clients; firmware dependent | Some UEFI clients, with a configured trust store | Rare in boot ROMs |
| Support in network devices | Very common in recovery loaders and legacy ZTP | Common in newer ZTP | Common in newer ZTP; required by RFC 8572 SZTP's design | Common for configuration backup |
| Write access | Possible, and dangerous | Only if the server allows uploads | Only if the server allows uploads | Yes, with authentication |
Two caveats. HTTPS proves which server you reached, not that the file is the one you approved, so a compromised provisioning server still needs signed artifacts to be caught. And "supports HTTPS" is not "validates certificates": confirm the device rejects an untrusted server certificate before counting the transport as authenticated.
How do TFTP servers limit damage?
Every hardening control for TFTP narrows one of four things: which files the server can see, whether it can write, which hosts can reach it, and what record you keep. The two common open source servers on Linux, dnsmasq and tftp-hpa, both expose these controls.
Read only service
Write access is the most dangerous setting. If backups need it, put them on a separate server.
- dnsmasq has no write support. Its manual says "Only reading is allowed."
- tftp-hpa allows writes only to files that already exist unless you add
--create, and then creates files "with default permissions allowing anyone to read or write them" unless--permissiveor--umaskis set. The development manual page in the project's git repository also documents--read-onlyand--max-upload. Distribution packages can lag the repository, so checkin.tftpd -V. Without a read only switch, make every served file owned by root and not writable by the daemon's user.
A rooted or chrooted directory
The server should only ever see one directory.
- dnsmasq
--tftp-rootmakes requests relative to a directory and rejects paths that include... Absolute paths are allowed only if they fall inside the root. You can set a different root per interface. - tftp-hpa
--secure(-s) callschrootinto the one directory you name. It requires starting as root; the development manual also documents--jail, which resolves names under a virtual root withopenat2on newer Linux systems.
Secure mode in dnsmasq
dnsmasq's --tftp-secure is easy to misread. Without it, any file the dnsmasq process can read is available by TFTP. With it, only files owned by the user that dnsmasq runs as are served. If dnsmasq runs as root, --tftp-secure has no effect and only world readable files are served, so never run it as root with TFTP and no --tftp-root.
Per client roots, binding, ports and logs
dnsmasq's --tftp-unique-root appends the client's IP or MAC address to the root path, so each device gets its own directory. That is not authentication, since addresses can be spoofed on the local segment, but it stops casual collection of every device's configuration. Bind the service to the provisioning interface only, pin the per transfer data ports to a range so firewall rules can be exact, and keep one log line per transfer. dnsmasq's --dhcp-script receives a tftp action at the end of each transfer with the byte count, client address and file path; tftp-hpa logs to syslog, with more detail under --verbose. Avoid dnsmasq's --quiet-tftp on provisioning servers.
Summary of server controls
| Goal | dnsmasq | tftp-hpa |
|---|---|---|
| No writes | Built in, read only | No --create; root owned files; --read-only where available |
| Confine paths | --tftp-root (rejects ..) | --secure chroot, or --jail where available |
| Restrict files by owner | --tftp-secure (not as root) | Run as an unprivileged --user; file permissions |
| Per client files | --tftp-unique-root=ip or =mac | Remap rules with --map-file |
| Bind interface or address | --enable-tftp=<iface> | --address <ip>:69 |
| Fixed data ports | --tftp-port-range | --port-range |
| Limit concurrency | --tftp-max | Service manager limits |
| Cap block size | --tftp-mtu, --tftp-no-blocksize | --blocksize mtu, --refuse blksize |
| Transfer log | --dhcp-script tftp action, log facility | syslog, --verbose |
Step by step: a contained, read only TFTP server for a lab
This walkthrough builds a lab TFTP server on a Debian or Ubuntu host. It serves files read only, from one directory, to one provisioning VLAN, with hashes and a signature for every file and a log line for every transfer. Adjust the addresses and interface names to your lab. Do this on a lab host first, not on a production provisioning server.
The example plan:
| Item | Value |
|---|---|
| Provisioning VLAN | 30, subnet 10.20.30.0/24 |
| Server interface | ens19.30 with address 10.20.30.5 |
| TFTP root | /srv/tftp |
| Data port range | UDP 50000 to 50100 |
| Signing key | An OpenSSH Ed25519 key kept off the TFTP server |
Step 1: Create the provisioning VLAN interface
Put the server on its own VLAN so TFTP is never reachable from user networks. With ip:
sudo ip link add link ens19 name ens19.30 type vlan id 30
sudo ip addr add 10.20.30.5/24 dev ens19.30
sudo ip link set ens19.30 up
ip -br addr show ens19.30
Make it permanent in your network configuration tool afterwards. On the switch side, the provisioning VLAN should carry only devices being provisioned and the provisioning server, with no route to user VLANs.
Step 2: Build the root directory, owned by root and read only
sudo install -d -o root -g root -m 0755 /srv/tftp
sudo install -o root -g root -m 0644 ./build/bootx64.efi /srv/tftp/
sudo install -o root -g root -m 0644 ./build/switch-image.bin /srv/tftp/
sudo find /srv/tftp -type d -exec chmod 0755 {} +
sudo find /srv/tftp -type f -exec chmod 0644 {} +
ls -l /srv/tftp
Keep configurations with secrets out of this directory; serve a minimal bootstrap configuration instead (see "How to move devices off TFTP").
Step 3a: Option A, tftp-hpa
Install and configure the daemon. Debian packaging reads its settings from /etc/default/tftpd-hpa.
sudo apt-get install tftpd-hpa
in.tftpd -V
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS="10.20.30.5:69"
TFTP_OPTIONS="--secure --verbose --blocksize 1468 --port-range 50000:50100"
This runs as the unprivileged tftp user, chroots into /srv/tftp, binds only to the provisioning address, caps block size for 1,500 byte Ethernet, pins data ports and logs each request. It does not include --create, and because every file is owned by root with mode 0644, write requests fail. If in.tftpd -V shows your build supports --read-only, add it as an explicit belt to those braces.
sudo systemctl restart tftpd-hpa
sudo ss -ulpn 'sport = :69'
Step 3b: Option B, dnsmasq as a TFTP only server
If you already use dnsmasq for PXE, its TFTP server is read only by design. This configuration turns off DNS and runs TFTP only on the provisioning interface.
# /etc/dnsmasq.d/tftp-provisioning.conf
port=0
interface=ens19.30
bind-interfaces
enable-tftp=ens19.30
tftp-root=/srv/tftp
tftp-secure
tftp-port-range=50000,50100
tftp-mtu=1500
tftp-max=30
log-facility=/var/log/dnsmasq-tftp.log
dhcp-script=/usr/local/sbin/tftp-ledger
Because tftp-secure serves only files owned by the dnsmasq user (and dnsmasq must not run as root), set ownership for this server to that user and keep the files read only. dnsmasq has no write path, so the ownership does not open one:
sudo chown -R dnsmasq:nogroup /srv/tftp
sudo find /srv/tftp -type f -exec chmod 0444 {} +
sudo systemctl restart dnsmasq
Check your distribution's service user name with ps -o user= -C dnsmasq before running chown.
Step 4: Restrict access with nftables
Allow TFTP only from the provisioning subnet on the provisioning interface. Everything else is dropped.
sudo nft add table inet tftp
sudo nft add chain inet tftp input '{ type filter hook input priority 0; policy accept; }'
sudo nft add rule inet tftp input iifname "ens19.30" ip saddr 10.20.30.0/24 udp dport 69 accept
sudo nft add rule inet tftp input iifname "ens19.30" ip saddr 10.20.30.0/24 udp dport 50000-50100 accept
sudo nft add rule inet tftp input udp dport 69 drop
sudo nft add rule inet tftp input udp dport 50000-50100 drop
sudo nft list table inet tftp
Mirror the rule in the network: an access control list on the routed interface of every user and guest VLAN that drops UDP 69 toward the provisioning subnet and the server. Defence in depth matters here because a host firewall is one configuration change from open.
Step 5: Hash and sign every served file
TFTP cannot prove a file is genuine, so publish a manifest that can. On a separate build or signing host (not the TFTP server), create a SHA-256 manifest and sign it with an OpenSSH key. ssh-keygen -Y sign and -Y verify are documented in the OpenSSH manual.
cd ./build
sha256sum bootx64.efi switch-image.bin > SHA256SUMS
ssh-keygen -Y sign -f ~/.ssh/provisioning_signing_ed25519 -n file SHA256SUMS
# produces SHA256SUMS.sig
Anyone who receives the files can verify the manifest against an allowed signers file that you distribute through a trusted channel, then check the files:
ssh-keygen -Y verify -f allowed_signers -I provisioning@example.internal \
-n file -s SHA256SUMS.sig < SHA256SUMS
sha256sum -c SHA256SUMS
Copy SHA256SUMS and SHA256SUMS.sig to /srv/tftp with the files. On the device side, use whatever verification the platform offers. Boot chains with UEFI Secure Boot verify signed EFI binaries no matter how they arrive, and iPXE's imgverify command marks an image as trusted only when its detached signature validates. Many network operating systems can check an image hash or vendor signature after download; turn that on wherever it exists. For deeper background on the boot chain side, see our guides to Secure Boot, trusted boot and measured boot.
Step 6: Check the served tree against the manifest on a schedule
A signature on the build host does not help if the files on the server change later. Check the live tree regularly and alert on any difference:
cd /srv/tftp && sha256sum -c --quiet SHA256SUMS || logger -p auth.crit "TFTP root integrity check failed"
Run it from a systemd timer or cron every few minutes. This is the M1047 Audit mitigation in practice.
Step 7: Log every transfer
For tftp-hpa, --verbose writes each request to syslog:
journalctl -u tftpd-hpa --since "1 hour ago"
For dnsmasq, the dhcp-script receives a tftp action at the end of each transfer with the byte count, the client address and the file path. A minimal ledger script:
#!/bin/sh
# /usr/local/sbin/tftp-ledger
# dnsmasq calls: <action> <arg1> <arg2> <arg3>
if [ "$1" = "tftp" ]; then
logger -t tftp-ledger "bytes=$2 client=$3 file=$4"
fi
exit 0
sudo install -o root -g root -m 0755 tftp-ledger /usr/local/sbin/tftp-ledger
Forward those lines to your central log store and look for unexpected images, clients outside the provisioning range, or bursts of configuration requests.
Step 8: Test from a client on the provisioning VLAN
curl speaks TFTP and lets you set the block size, so it is a convenient test client:
curl -s -o bootx64.efi tftp://10.20.30.5/bootx64.efi
curl -s --tftp-blksize 1468 -o switch-image.bin tftp://10.20.30.5/switch-image.bin
sha256sum -c SHA256SUMS --ignore-missing
Then confirm the negative cases. Each of these should fail:
# A write should be refused (access violation or illegal operation)
echo test > probe.txt && curl -s -T probe.txt tftp://10.20.30.5/probe.txt; echo "exit=$?"
# A path outside the root should be refused
curl -s --path-as-is -o /dev/null "tftp://10.20.30.5/../../etc/hostname"; echo "exit=$?"
From a host on a user VLAN, the first curl above should time out, because the firewall and the network ACL drop it. If any of these tests succeed, stop and fix the configuration before any device depends on the server.
How to move devices off TFTP
TFTP is fine as a short first hop on a closed network. It is not fine as the channel that delivers secrets or the only check on what a device runs. Moving off it is usually a staged change.
1. Chain from TFTP to HTTPS for anything large or sensitive
For PXE clients, the common pattern is to fetch a small, signed boot loader by TFTP and have it load everything else over HTTP or HTTPS. That shrinks the TFTP payload to one file you can verify, and moves kernels, initial ramdisks and installers onto a transport with integrity. UEFI clients that support HTTP boot can skip TFTP entirely; DHCPv6 can hand them a boot file URL with RFC 5970's OPT_BOOTFILE_URL (option 59), which RFC 5970 says can name HTTP or TFTP. The PXE and UEFI HTTP boot guide walks through both flows, and the DHCP guide covers the DHCP side and its own spoofing risks.
2. Use HTTPS provisioning and signed configuration for network devices
Most current network operating systems can fetch images and configuration over HTTPS during zero touch provisioning. Prefer that, require certificate validation, and sign the configuration or the script the device runs where the platform supports it.
3. Adopt Secure Zero Touch Provisioning where devices support it
RFC 8572 (April 2019) defines Secure Zero Touch Provisioning (SZTP), "a technique to securely provision a networking device when it is booting in a factory-default state". The RFC recommends that devices present a manufacturer installed IDevID certificate to the bootstrap server, and the bootstrapping data can be signed and tied to the device's owner through an ownership voucher and owner certificate, so a device can trust provisioning data even when it arrives from a server it does not yet trust. The provisioning steps can update the boot image, commit an initial configuration and run scripts. Device support varies, so check your platforms.
4. Take secrets out of first boot files
Whatever transport you use, make the first file a device fetches boring: no long lived passwords, keys or community strings. Use it to point the device at an authenticated management channel, then deliver secrets there. That way a captured bootstrap file is worth little.
What to do for devices that cannot move
Some devices will never do anything but TFTP: old phones, embedded controllers, recovery loaders. For those:
| Control | Why |
|---|---|
| Dedicated provisioning VLAN, no route to user networks | Removes most of the people who could read or tamper with transfers |
| Read only server, rooted directory, root owned files | Removes the write path entirely |
| Per device file names or per client roots | Stops casual collection of every device's configuration |
| Bootstrap configuration without secrets | Limits the value of anything read off the wire |
| Device side image verification where available | Lets the device refuse an altered image |
| Manifest of approved images with hashes, checked on schedule | Detects a changed file on the server |
| DHCP snooping and port security on the provisioning VLAN | Makes it harder to redirect devices to a rogue server |
| Transfer logs compared against expectations | Detects unusual fetches and rogue clients |
| An inventory of TFTP dependent devices with a retirement date | Keeps the exception from becoming permanent |
Common mistakes
A writable TFTP root. --create or a world writable directory lets anyone on the network drop or replace files. Keep provisioning servers read only, and move configuration backups to a separate server or to SFTP.
Device configurations with plaintext secrets on the server. Anyone who can reach port 69 can request them by name. Remove secrets from TFTP served files, use per client roots, and rotate anything already exposed.
TFTP reachable from user VLANs. Bind to the provisioning interface, firewall port 69 and the data range, and drop TFTP at every user VLAN's router interface.
No integrity check on firmware served over TFTP. Whatever arrives is what the device runs. Enable device side signature or hash checks, sign a manifest, and check the served tree on a schedule.
Running dnsmasq as root with TFTP and no root directory. The manual warns this "can expose any world-readable file on the server to any host on the net".
Assuming "secure" means authenticated. In dnsmasq, tftp-secure only filters files by owner; in tftp-hpa, --secure means chroot. Neither adds login.
Forcing large block sizes without checking the path. Blocks above the path MTU fragment, and some embedded clients cannot reassemble fragments. Cap block size to the MTU and test each device model.
Leaving TFTP boot fallback enabled on production network devices. If a device will try a TFTP boot when its local image is missing or its boot settings change, that is the exact path T1542.005 describes. Disable netboot fallback where you do not need it, and alert on boot configuration changes.
FAQ
What port does TFTP use?
The client sends its first request to UDP port 69 on the server. The server then replies from a new port it chooses for that transfer, and the rest of the exchange uses that port pair. Firewalls need a TFTP helper or an explicit data port range on the server, such as dnsmasq's --tftp-port-range or tftp-hpa's --port-range.
Is TFTP secure?
No. RFC 1350 states that TFTP has no login or access control, and the option RFCs add no security. Transfers are unauthenticated and unencrypted, and there is no integrity check beyond the UDP checksum. Use it only on a controlled provisioning network, read only, with signed or hashed files.
What is the default TFTP block size?
512 bytes of data per block, set by RFC 1350. A block shorter than 512 bytes marks the end of the file. RFC 2348's blksize option lets client and server agree on a size from 8 to 65,464 bytes.
What TFTP block size should I use on Ethernet?
For 1,500 byte Ethernet frames with IPv4, 1,468 bytes avoids IP fragmentation (1,500 minus 20 for IPv4, 8 for UDP and 4 for TFTP). With IPv6 it is 1,448. Smaller values such as 1,428 leave extra headroom for tunnels or VLAN overhead.
What is the TFTP windowsize option?
RFC 7440 (January 2015) lets the sender transmit several blocks before waiting for one acknowledgement. In the RFC's own test a 180 MB file took 257 seconds with lock step and 42 seconds with a window of 16 blocks. A window of 1 behaves exactly like original TFTP.
What is the largest file TFTP can transfer?
The 16 bit block number limits a transfer to 65,535 blocks without rollover handling: about 32 MiB at 512 byte blocks and about 4 GB at the 65,464 byte maximum. Rollover is not standardised.
What is the difference between TFTP and FTP?
FTP runs over TCP with login, directory listing, rename and delete. TFTP runs over UDP and can only read or write a named file, which makes it small enough for boot firmware and leaves it with no security.
How do I make a TFTP server read only?
dnsmasq's TFTP server is read only by design. With tftp-hpa, do not pass --create, keep served files owned by root, and add --read-only if in.tftpd -V shows your build supports it. Then test that a write fails.
What replaces TFTP for network device provisioning?
HTTPS provisioning with certificate validation and signed configuration, UEFI HTTP boot, and Secure Zero Touch Provisioning (RFC 8572) where devices support it.
Why software supply chain hygiene still matters for TFTP
TFTP itself is a network protocol, so most of what keeps it safe is network and server configuration, not dependency management. But the files it serves are software: boot loaders, kernels, network device images and the provisioning scripts and server images that build and serve them all carry open source components that need patching like any other code.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
To be plain about the fit: Safeguard does not run or harden TFTP servers, does not inspect network traffic, does not scan device firmware or network operating system images, and does not check device configurations. Where it helps is the software you build around provisioning. It scans repositories, container images and SBOM files for dependencies, matches them against vulnerability data, and shows which projects contain a given component and version. That covers a containerised dnsmasq or tftp-hpa provisioning server, the Python or Go tooling that renders device configurations, and the iPXE or boot loader builds you produce yourself. Policy gates in CI can Block, Warn or Notify when a build carries a vulnerable version, with approvals handled through exceptions.
The first demo is a real CLI session. Asked to fix a lodash CVE, the assistant reads the finding and advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. The same flow applies when the web front end or API of your provisioning portal pins an outdated library.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with its device code, then scans from the terminal. Provisioning infrastructure usually lives on an isolated management network, and the runner lets you scan the server images and tooling repositories where they are built, for example with safeguard scan --image on your provisioning container.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. On the day an advisory lands for a TFTP or DHCP server package, it is a quick way to ask which of your projects carry the affected version, with tool permissions you control.
Click through it at your own pace, or open it full screen.
To try the CLI, download Safeguard and follow the CLI documentation.
Quick check
A client asks for blksize 8192 and the server replies with an OACK of blksize 1468. What must the client do?
Use 1,468 byte blocks or abort. RFC 2348 says the server's value must be less than or equal to the client's, and the client must either use the size in the OACK or send ERROR code 8 to end the transfer.
A 1,024 byte file is read with default settings. How many DATA packets are sent?
Three. Two full 512 byte blocks carry the data, and because the file is an exact multiple of the block size, a third DATA packet with zero bytes marks the end of the transfer.
dnsmasq is running as root with TFTP enabled and tftp-secure set. Is the server restricted to files owned by one user?
No. The dnsmasq manual says that when it runs as root, --tftp-secure has no effect and only world readable files are served. Run dnsmasq as an unprivileged user and always set --tftp-root.
Sources
- RFC 1350: The TFTP Protocol (Revision 2), STD 33, July 1992
- RFC 783: The TFTP Protocol (Revision 2), June 1981
- RFC 906: Bootstrap Loading using TFTP, June 1984
- RFC 1123: Requirements for Internet Hosts, Application and Support, October 1989
- RFC 1785: TFTP Option Negotiation Analysis, March 1995
- RFC 2347: TFTP Option Extension, May 1998
- RFC 2348: TFTP Blocksize Option, May 1998
- RFC 2349: TFTP Timeout Interval and Transfer Size Options, May 1998
- RFC 3617: Uniform Resource Identifier (URI) Scheme and Applicability Statement for TFTP, October 2003
- RFC 4578: DHCP Options for the Intel Preboot eXecution Environment (PXE), November 2006
- RFC 5970: DHCPv6 Options for Network Boot, September 2010
- RFC 7440: TFTP Windowsize Option, January 2015
- RFC 8572: Secure Zero Touch Provisioning (SZTP), April 2019
- MITRE ATT&CK T1542.005: Pre-OS Boot: TFTP Boot
- dnsmasq manual page
- tftp-hpa tftpd(8) manual page source, project git repository
- curl manual page (TFTP options)
- OpenSSH ssh-keygen(1) manual (-Y sign and -Y verify)
- iPXE imgverify command documentation
Last reviewed: 25 October 2026. TFTP's base specification has not changed since RFC 1350; check the server manuals for option changes in newer releases.