Before using a TFTP server
It is used where a device speaks nothing but TFTP, such as saving router configurations or transferring OS images. This article uses tftpd-hpa as the server and tftp-hpa as the client.
| File to edit | Purpose |
|---|---|
/etc/default/tftpd-hpa | The served directory, listening address and options |
/etc/hosts.deny | Restricting sources (TCP Wrappers) |
The essentials of TFTP (RFC 1350):
| Item | Details |
|---|---|
| Transport | UDP. The first request goes to port 69 on the server; the transfer itself comes from a new port chosen by the server |
| Packets | RRQ (read request) / WRQ (write request) / DATA / ACK / ERROR |
| Transfer modes | netascii (text, line ends sent as CR LF) / octet (bytes sent as they are) |
Checking what is installed
Neither the server nor the client is installed, and neither the configuration file nor the served directory exists yet.
kazulog@sv1:~$ apt-cache policy tftpd-hpa | head -3
tftpd-hpa:
Installed: (none)
Candidate: 5.3+20251209-2
kazulog@sv1:~$ ls -l /etc/default/tftpd-hpa /srv/tftp 2>&1
ls: cannot access '/etc/default/tftpd-hpa': No such file or directory
ls: cannot access '/srv/tftp': No such file or directory
kazulog@sv1:~$ sudo ss -ulnp 'sport = :69'; echo "ss rc=$?"
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ss rc=0kazulog@sv2:~$ which tftp; echo "which rc=$?"
which rc=1
kazulog@sv2:~$ apt-cache policy tftp-hpa | head -3
tftp-hpa:
Installed: (none)
Candidate: 5.3+20251209-2Installing the TFTP server
sudo apt install -y tftpd-hpaNo super-server such as inetd is involved: tftpd-hpa.service listens on UDP port 69 by itself. How a super-server works is covered in Configuring inetd.
Installing creates /etc/default/tftpd-hpa, whose configuration is just these four lines.
kazulog@sv1:~$ sudo apt install -y tftpd-hpa | cat
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Reading package lists...
Building dependency tree...
Reading state information...
Solving dependencies...
Installing:
tftpd-hpa
Suggested packages:
pxelinux
Summary:
Upgrading: 0, Installing: 1, Removing: 0, Not Upgrading: 96
Download size: 44.5 kB
Space needed: 128 kB / 63.1 GB available
Get:1 http://archive.ubuntu.com/ubuntu resolute/main amd64 tftpd-hpa amd64 5.3+20251209-2 [44.5 kB]
Preconfiguring packages ...
Fetched 44.5 kB in 1s (40.5 kB/s)
Selecting previously unselected package tftpd-hpa.
(Reading database ... 91400 files and directories currently installed.)
Preparing to unpack .../tftpd-hpa_5.3+20251209-2_amd64.deb ...
Unpacking tftpd-hpa (5.3+20251209-2) ...
Setting up tftpd-hpa (5.3+20251209-2) ...
Created symlink '/etc/systemd/system/multi-user.target.wants/tftpd-hpa.service' → '/usr/lib/systemd/system/tftpd-hpa.service'.
Processing triggers for man-db (2.13.1-1build1) ...
Scanning processes...
Scanning linux images...
Running kernel seems to be up-to-date.
No services need to be restarted.
No containers need to be restarted.
No user sessions are running outdated binaries.
No VM guests are running outdated hypervisor (qemu) binaries on this host.
kazulog@sv1:~$ cat /etc/default/tftpd-hpa
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
kazulog@sv1:~$ ls -ld /srv/tftp
drwxr-xr-x 2 root nogroup 4096 Sep 12 10:01 /srv/tftp
kazulog@sv1:~$ systemctl status tftpd-hpa --no-pager | head -7 | cat
● tftpd-hpa.service - tftpd-hpa TFTP Server
Loaded: loaded (/usr/lib/systemd/system/tftpd-hpa.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-09-12 10:01:24 JST; 6s ago
Invocation: 4d07df8db779481dbcc25f5fda912011
Docs: man:in.tftpd
Process: 2706 ExecStart=/usr/sbin/in.tftpd --listen --user $TFTP_USERNAME --address $TFTP_ADDRESS $TFTP_OPTIONS $TFTP_DIRECTORY (code=exited, status=0/SUCCESS)
Main PID: 2707 (in.tftpd)
kazulog@sv1:~$ sudo ss -ulnp 'sport = :69'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
UNCONN 0 0 0.0.0.0:69 0.0.0.0:* users:(("in.tftpd",pid=2707,fd=4))
UNCONN 0 0 [::]:69 [::]:* users:(("in.tftpd",pid=2707,fd=5)) | Variable | Default | Meaning |
|---|---|---|
TFTP_USERNAME | tftp | The user in.tftpd runs as |
TFTP_DIRECTORY | /srv/tftp | The directory to serve |
TFTP_ADDRESS | :69 | Address and port to listen on. Without an address, all addresses |
TFTP_OPTIONS | --secure | Extra options |
--secure changes the root directory to TFTP_DIRECTORY at startup (chroot), so clients give just the file name without a directory. /srv/tftp is owned by root, so uploads do not work with the defaults.
After changing the settings, apply them with sudo systemctl restart tftpd-hpa.
Downloading a file
Put a file in /srv/tftp on the server.
kazulog@sv1:~$ echo "hello from tftp server sv1" | sudo tee /srv/tftp/hello.txt
hello from tftp server sv1
kazulog@sv1:~$ sudo ls -l /srv/tftp/
total 4
-rw-r--r-- 1 root root 27 Sep 12 10:01 hello.txtInstall the client and fetch it. -c runs a single command and exits; -v shows the details of the transfer.
kazulog@sv2:~$ sudo apt install -y tftp-hpa | cat
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Reading package lists...
Building dependency tree...
Reading state information...
Solving dependencies...
Installing:
tftp-hpa
Summary:
Upgrading: 0, Installing: 1, Removing: 0, Not Upgrading: 96
Download size: 19.8 kB
Space needed: 55.3 kB / 63.1 GB available
Get:1 http://archive.ubuntu.com/ubuntu resolute/main amd64 tftp-hpa amd64 5.3+20251209-2 [19.8 kB]
Fetched 19.8 kB in 1s (24.6 kB/s)
Selecting previously unselected package tftp-hpa.
(Reading database ... 91448 files and directories currently installed.)
Preparing to unpack .../tftp-hpa_5.3+20251209-2_amd64.deb ...
Unpacking tftp-hpa (5.3+20251209-2) ...
Setting up tftp-hpa (5.3+20251209-2) ...
Processing triggers for man-db (2.13.1-1build1) ...
Scanning processes...
Scanning linux images...
Running kernel seems to be up-to-date.
No services need to be restarted.
No containers need to be restarted.
No user sessions are running outdated binaries.
No VM guests are running outdated hypervisor (qemu) binaries on this host.
kazulog@sv2:~$ cd /tmp && tftp -v 192.168.100.10 -c get hello.txt
Connected to 192.168.100.10 (192.168.100.10), port 69
getting from 192.168.100.10:hello.txt to hello.txt [netascii]
Received 28 bytes in 0.1 seconds [2524 bit/s]
kazulog@sv2:/tmp$ cat /tmp/hello.txt
hello from tftp server sv1The default transfer mode of the tftp-hpa client is netascii. The 27-byte file arrives as 28 bytes because the line end (LF) is sent as CR LF. The receiver turns it back into LF, so the saved file matches the original.
Allowing uploads (/etc/default/tftpd-hpa)
With the defaults, only overwriting a file that already exists on the server is allowed.
kazulog@sv2:~$ echo "uploaded from sv2" > /tmp/upload.txt
kazulog@sv2:~$ cd /tmp && tftp -v 192.168.100.10 -c put upload.txt
Connected to 192.168.100.10 (192.168.100.10), port 69
putting upload.txt to 192.168.100.10:upload.txt [netascii]
Error code 1: File not foundEdit /etc/default/tftpd-hpa and add --create to TFTP_OPTIONS.
sudo vi /etc/default/tftpd-hpa# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure --create"kazulog@sv1:~$ cat /etc/default/tftpd-hpa
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure --create"
kazulog@sv1:~$ sudo systemctl restart tftpd-hpa
kazulog@sv1:~$ ls -ld /srv/tftp
drwxr-xr-x 2 root nogroup 4096 Sep 12 10:01 /srv/tftpThat alone still fails, because the served directory is still owned by root.
kazulog@sv2:~$ echo "uploaded from sv2" > /tmp/upload.txt
kazulog@sv2:~$ cd /tmp && tftp -v 192.168.100.10 -c put upload.txt
Connected to 192.168.100.10 (192.168.100.10), port 69
putting upload.txt to 192.168.100.10:upload.txt [netascii]
Error code 0: Request failedChange the directory’s owner to tftp (the user in TFTP_USERNAME) and the upload succeeds.
kazulog@sv1:~$ sudo chown tftp:tftp /srv/tftp
kazulog@sv1:~$ ls -ld /srv/tftp
drwxr-xr-x 2 tftp tftp 4096 Sep 12 10:01 /srv/tftpkazulog@sv2:~$ echo "uploaded from sv2" > /tmp/upload.txt
kazulog@sv2:~$ cd /tmp && tftp -v 192.168.100.10 -c put upload.txt
Connected to 192.168.100.10 (192.168.100.10), port 69
putting upload.txt to 192.168.100.10:upload.txt [netascii]
Sent 19 bytes in 0.1 seconds [1441 bit/s]Restricting the listening address (/etc/default/tftpd-hpa)
Put an address in TFTP_ADDRESS and the server listens only on that address. Enclose an IPv6 address in square brackets, as in [2001:db8:100::10]:69.
sudo vi /etc/default/tftpd-hpa# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS="192.168.100.10:69"
TFTP_OPTIONS="--secure --create"Uploaded files are created readable and writable by anyone (666), and only one listener remains.
kazulog@sv1:~$ sudo ls -l /srv/tftp/
total 8
-rw-r--r-- 1 root root 27 Sep 12 10:01 hello.txt
-rw-rw-rw- 1 tftp tftp 18 Sep 12 10:02 upload.txt
kazulog@sv1:~$ cat /etc/default/tftpd-hpa
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS="192.168.100.10:69"Restricting sources (/etc/hosts.deny)
in.tftpd has TCP Wrappers (libwrap) built in, so /etc/hosts.deny can refuse a source. The daemon name is in.tftpd.
sudo vi /etc/hosts.deny# TFTP は 192.168.100.20 からの接続を拒否する
in.tftpd: 192.168.100.20kazulog@sv1:~$ ldd /usr/sbin/in.tftpd | grep libwrap
libwrap.so.0 => /usr/lib/x86_64-linux-gnu/libwrap.so.0 (0x00007cfa15a18000)
kazulog@sv1:~$ cat /etc/hosts.deny
# /etc/hosts.deny: list of hosts that are _not_ allowed to access the system.
# See the manual pages hosts_access(5) and hosts_options(5).
#
# Example: ALL: some.host.name, .some.domain
# ALL EXCEPT in.fingerd: other.host.name, .other.domain
#
# If you're going to protect the portmapper use the name "rpcbind" for the
# daemon name. See rpcbind(8) and rpc.mountd(8) for further information.
#
# The PARANOID wildcard matches any host whose name does not match its
# address.
#
# You may wish to enable this to ensure any programs that don't
# validate looked up hostnames still leave understandable logs. In past
# versions of Debian this has been the default.
# ALL: PARANOID
# TFTP は 192.168.100.20 からの接続を拒否する
in.tftpd: 192.168.100.20A refused request gets no ERROR; it is silently dropped. The client keeps resending and finally gives up with Transfer timed out.
kazulog@sv2:~$ cd /tmp && timeout 40 tftp -v 192.168.100.10 -c get hello.txt hello-denied.txt; echo "rc=$?"
Connected to 192.168.100.10 (192.168.100.10), port 69
getting from 192.168.100.10:hello.txt to hello-denied.txt [netascii]
Transfer timed out.
rc=69The refusal is logged. However, the address after from is not the client’s but the server’s own (192.168.100.10). In tftp-hpa 5.3, in.tftpd uses the client address for the decision but writes the server-side address to the log.
kazulog@sv1:~$ sudo journalctl -u tftpd-hpa --no-pager -n 3
Sep 12 10:02:55 sv1 in.tftpd[5396]: connection refused from 192.168.100.10Final state of the configuration files
This is the state with everything above applied. Only two files were touched: /etc/default/tftpd-hpa and /etc/hosts.deny.
kazulog@sv1:~$ cat /etc/default/tftpd-hpa
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS="192.168.100.10:69"
TFTP_OPTIONS="--secure --create"
kazulog@sv1:~$ cat /etc/hosts.deny
# /etc/hosts.deny: list of hosts that are _not_ allowed to access the system.
# See the manual pages hosts_access(5) and hosts_options(5).
#
# Example: ALL: some.host.name, .some.domain
# ALL EXCEPT in.fingerd: other.host.name, .other.domain
#
# If you're going to protect the portmapper use the name "rpcbind" for the
# daemon name. See rpcbind(8) and rpc.mountd(8) for further information.
#
# The PARANOID wildcard matches any host whose name does not match its
# address.
#
# You may wish to enable this to ensure any programs that don't
# validate looked up hostnames still leave understandable logs. In past
# versions of Debian this has been the default.
# ALL: PARANOID
# TFTP は 192.168.100.20 からの接続を拒否する
in.tftpd: 192.168.100.20
kazulog@sv1:~$ sudo ls -l /srv/tftp/
total 8
-rw-r--r-- 1 root root 27 Sep 12 10:01 hello.txt
-rw-rw-rw- 1 tftp tftp 18 Sep 12 10:02 upload.txt
kazulog@sv1:~$ systemctl is-enabled tftpd-hpa; systemctl is-active tftpd-hpa
enabled
active
kazulog@sv1:~$ sudo ss -ulnp 'sport = :69'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
UNCONN 0 0 192.168.100.10:69 0.0.0.0:* users:(("in.tftpd",pid=4631,fd=4)) | File | Change |
|---|---|
/etc/default/tftpd-hpa | Add --create to TFTP_OPTIONS; set TFTP_ADDRESS to 192.168.100.10:69 |
/etc/hosts.deny | Append in.tftpd: 192.168.100.20 |
/srv/tftp | Change the owner to tftp:tftp so uploads are possible |
Saving a router configuration
From the IOS XE router R1, upload the running-config with the copy command. It was run with file prompt quiet configured, to skip the confirmation questions.
Because --secure chroots the server to /srv/tftp, the URL takes just the file name. Adding a directory gives %Error opening ... (No such file or directory).
R1#copy running-config tftp://192.168.100.10/r1-confg
!!
4964 bytes copied in 0.597 secs (8315 bytes/sec)kazulog@sv1:~$ sudo ls -l /srv/tftp/
total 16
-rw-r--r-- 1 root root 27 Sep 12 10:01 hello.txt
-rw-rw-rw- 1 tftp tftp 4964 Sep 12 10:05 r1-confg
-rw-rw-rw- 1 tftp tftp 18 Sep 12 10:02 upload.txt
kazulog@sv1:~$ sudo head -3 /srv/tftp/r1-confg
!
! Last configuration change at 01:04:58 UTC Sat Sep 12 2026Removing
Purging removes /etc/default/tftpd-hpa, but /srv/tftp, the files in it and the tftp user remain.
kazulog@sv1:~$ sudo apt purge -y tftpd-hpa | cat
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Reading package lists...
Building dependency tree...
Reading state information...
Solving dependencies...
REMOVING:
tftpd-hpa*
Summary:
Upgrading: 0, Installing: 0, Removing: 1, Not Upgrading: 96
Freed space: 128 kB
(Reading database ... 91411 files and directories currently installed.)
Removing tftpd-hpa (5.3+20251209-2) ...
Processing triggers for man-db (2.13.1-1build1) ...
(Reading database ... 91399 files and directories currently installed.)
Purging configuration files for tftpd-hpa (5.3+20251209-2) ...
kazulog@sv1:~$ dpkg -l tftpd-hpa 2>&1 | tail -1
dpkg-query: no packages found matching tftpd-hpa
kazulog@sv1:~$ ls -l /etc/default/tftpd-hpa 2>&1
ls: cannot access '/etc/default/tftpd-hpa': No such file or directory
kazulog@sv1:~$ sudo ls -ld /srv/tftp 2>&1
drwxr-xr-x 2 tftp tftp 4096 Sep 12 10:05 /srv/tftp
kazulog@sv1:~$ getent passwd tftp
tftp:x:103:109:tftp daemon:/srv/tftp:/usr/sbin/nologin
kazulog@sv1:~$ sudo ss -ulnp 'sport = :69'; echo "ss rc=$?"
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ss rc=0Looking inside with a capture
UDP to and from sv2 was captured on the link between sv1 and the switch. The download, the two failed uploads, the successful upload and the retries of a refused request are all in one file.
$ tshark -r ubuntu-tftp.pcap -n -t ad
1 2026-09-12 10:01:45.203039 192.168.100.20 → 192.168.100.10 TFTP 63 Read Request, File: hello.txt, Transfer type: netascii
2 2026-09-12 10:01:45.209098 192.168.100.10 → 192.168.100.20 TFTP 74 Data Packet, Block: 1 (last)
3 2026-09-12 10:01:45.209729 192.168.100.20 → 192.168.100.10 UDP 46 54144 → 55822 Len=4
4 2026-09-12 10:02:07.874218 192.168.100.20 → 192.168.100.10 TFTP 64 Write Request, File: upload.txt, Transfer type: netascii
5 2026-09-12 10:02:07.880190 192.168.100.10 → 192.168.100.20 TFTP 61 Error Code, Code: File not found, Message: File not found
6 2026-09-12 10:02:13.525002 192.168.100.20 → 192.168.100.10 TFTP 64 Write Request, File: upload.txt, Transfer type: netascii
7 2026-09-12 10:02:13.532589 192.168.100.10 → 192.168.100.20 TFTP 61 Error Code, Code: Not defined, Message: Request failed
8 2026-09-12 10:02:19.359314 192.168.100.20 → 192.168.100.10 TFTP 64 Write Request, File: upload.txt, Transfer type: netascii
9 2026-09-12 10:02:19.367159 192.168.100.10 → 192.168.100.20 TFTP 46 Acknowledgement, Block: 0
10 2026-09-12 10:02:19.367998 192.168.100.20 → 192.168.100.10 UDP 65 52988 → 54084 Len=23
11 2026-09-12 10:02:19.369136 192.168.100.10 → 192.168.100.20 TFTP 46 Acknowledgement, Block: 1
12 2026-09-12 10:02:50.855073 192.168.100.20 → 192.168.100.10 TFTP 63 Read Request, File: hello.txt, Transfer type: netascii
13 2026-09-12 10:02:55.856019 192.168.100.20 → 192.168.100.10 TFTP 63 Read Request, File: hello.txt, Transfer type: netascii
14 2026-09-12 10:03:00.856469 192.168.100.20 → 192.168.100.10 TFTP 63 Read Request, File: hello.txt, Transfer type: netascii
15 2026-09-12 10:03:05.856777 192.168.100.20 → 192.168.100.10 TFTP 63 Read Request, File: hello.txt, Transfer type: netascii
16 2026-09-12 10:03:10.858100 192.168.100.20 → 192.168.100.10 TFTP 63 Read Request, File: hello.txt, Transfer type: netascii
$ tshark -r ubuntu-tftp.pcap -n -V -O tftp -Y 'frame.number == 2'
Frame 2: Packet, 74 bytes on wire (592 bits), 74 bytes captured (592 bits)
Ethernet II, Src: 52:54:00:b7:4d:fd, Dst: 52:54:00:ba:01:74
Internet Protocol Version 4, Src: 192.168.100.10, Dst: 192.168.100.20
User Datagram Protocol, Src Port: 55822, Dst Port: 54144
Trivial File Transfer Protocol
Opcode: Data Packet (3)
[Destination File: hello.txt]
[Read Request in frame 1]
Block: 1
[Full Block Number: 1]
Data (28 bytes)
$ tshark -r ubuntu-tftp-no2.pcap -n -x
0000 52 54 00 ba 01 74 52 54 00 b7 4d fd 08 00 45 00 RT...tRT..M...E.
0010 00 3c 53 dc 00 00 40 11 dd 65 c0 a8 64 0a c0 a8 .<S...@..e..d...
0020 64 14 da 0e d3 80 00 28 08 c3 00 03 00 01 68 65 d......(......he
0030 6c 6c 6f 20 66 72 6f 6d 20 74 66 74 70 20 73 65 llo from tftp se
0040 72 76 65 72 20 73 76 31 0d 0a rver sv1..
$ tshark -r ubuntu-tftp.pcap -n -Y 'tftp.opcode == 5' -T fields -e frame.number -e tftp.error.code -e tftp.error.message
5 1 File not found
7 0 Request failed
$ tshark -r ubuntu-tftp.pcap -n -t ad -Y 'frame.number >= 8 && frame.number <= 11'
8 2026-09-12 10:02:19.359314 192.168.100.20 → 192.168.100.10 TFTP 64 Write Request, File: upload.txt, Transfer type: netascii
9 2026-09-12 10:02:19.367159 192.168.100.10 → 192.168.100.20 TFTP 46 Acknowledgement, Block: 0
10 2026-09-12 10:02:19.367998 192.168.100.20 → 192.168.100.10 UDP 65 52988 → 54084 Len=23
11 2026-09-12 10:02:19.369136 192.168.100.10 → 192.168.100.20 TFTP 46 Acknowledgement, Block: 1
$ tshark -r ubuntu-tftp.pcap -n -Y 'frame.number >= 12' -T fields -e frame.number -e frame.time_delta_displayed -e udp.srcport -e udp.dstport -e tftp.source_file
12 45348 69 hello.txtThe request goes to port 69, but the reply comes from a different port chosen by the server (No.1 is addressed to 69, No.2 comes from 55822).
$ tshark -r ubuntu-tftp.pcap -n -V -O tftp -Y 'frame.number == 2'
Frame 2: Packet, 74 bytes on wire (592 bits), 74 bytes captured (592 bits)
Ethernet II, Src: 52:54:00:b7:4d:fd, Dst: 52:54:00:ba:01:74
Internet Protocol Version 4, Src: 192.168.100.10, Dst: 192.168.100.20
User Datagram Protocol, Src Port: 55822, Dst Port: 54144
Trivial File Transfer Protocol
Opcode: Data Packet (3)
[Destination File: hello.txt]
[Read Request in frame 1]
Block: 1
[Full Block Number: 1]
Data (28 bytes)Being netascii, the payload ends with 0d 0a (CR LF).
$ tshark -r ubuntu-tftp-no2.pcap -n -x
0000 52 54 00 ba 01 74 52 54 00 b7 4d fd 08 00 45 00 RT...tRT..M...E.
0010 00 3c 53 dc 00 00 40 11 dd 65 c0 a8 64 0a c0 a8 .<S...@..e..d...
0020 64 14 da 0e d3 80 00 28 08 c3 00 03 00 01 68 65 d......(......he
0030 6c 6c 6f 20 66 72 6f 6d 20 74 66 74 70 20 73 65 llo from tftp se
0040 72 76 65 72 20 73 76 31 0d 0a rver sv1..The two failures carry different error codes.
$ tshark -r ubuntu-tftp.pcap -n -Y 'tftp.opcode == 5' -T fields -e frame.number -e tftp.error.code -e tftp.error.message
5 1 File not found
7 0 Request failedA successful upload goes WRQ → ACK (Block 0) → DATA → ACK (Block 1).
$ tshark -r ubuntu-tftp.pcap -n -t ad -Y 'frame.number >= 8 && frame.number <= 11'
8 2026-09-12 10:02:19.359314 192.168.100.20 → 192.168.100.10 TFTP 64 Write Request, File: upload.txt, Transfer type: netascii
9 2026-09-12 10:02:19.367159 192.168.100.10 → 192.168.100.20 TFTP 46 Acknowledgement, Block: 0
10 2026-09-12 10:02:19.367998 192.168.100.20 → 192.168.100.10 UDP 65 52988 → 54084 Len=23
11 2026-09-12 10:02:19.369136 192.168.100.10 → 192.168.100.20 TFTP 46 Acknowledgement, Block: 1When refused, five requests from the same source port appear five seconds apart, and the server answers none of them.
$ tshark -r ubuntu-tftp.pcap -n -Y 'frame.number >= 12' -T fields -e frame.number -e frame.time_delta_displayed -e udp.srcport -e udp.dstport -e tftp.source_file
12 45348 69 hello.txt
13 5.000946000 45348 69 hello.txt
14 5.000450000 45348 69 hello.txt
15 5.000308000 45348 69 hello.txt
16 5.001323000 45348 69 hello.txtTest environment and session logs
Two Ubuntu 26.04 LTS Servers and one IOS XE router on CML. The Ubuntu disks were wiped immediately beforehand, because packages left over from earlier testing would stop the procedure from reproducing.
sv1 sv2 R1
ens3 192.168.100.10/24 ens3 192.168.100.20/24 Gi2 192.168.100.1/24
tftpd-hpa (UDP 69) tftp-hpa IOS XE 17.03.08a
| | |
+--------- LAB-SW ----------+---------------------------+
^ captured on this link (UDP to and from sv2 only)The full capture:
Capture of the TFTP traffic (ubuntu-tftp.pcap)| Step | sv1 | sv2 | R1 |
|---|---|---|---|
| Initial state (just wiped) | show / conf / log | show / conf / log | — |
| Installing and checking the file | show / conf / log | — | — |
| Placing a file and downloading it | show / conf / log | show / conf / log | — |
| Uploading with the defaults | — | show / conf / log | — |
| Adding –create | show / conf / log | show / conf / log | — |
| Changing the owner | show / conf / log | show / conf / log | — |
| Restricting the listening address | show / conf / log | — | — |
| Restricting sources | show / conf / log | show / conf / log | — |
| The refusal in the log | show / conf / log | — | — |
| Final state of the files | show / conf / log | — | — |
| Saving from the router | show / conf / log | — | show |
| Removing (final state) | show / conf / log | — | — |
| Analysing the capture | tshark | — | — |
Reference
RFC 1350 - The TFTP Protocol (Revision 2)
Related articles
- Changing the Hostname on Ubuntu 26.04 LTS Server (hostnamectl)
- Updating Packages on Ubuntu 26.04 LTS Server (apt update / upgrade)
- Setting the Timezone and Time Synchronisation on Ubuntu 26.04 LTS Server
- Creating Users and Granting sudo Privileges on Ubuntu 26.04 LTS Server
- Configuring the SSH Server on Ubuntu 26.04 LTS Server
- Setting Up a telnet Server on Ubuntu 26.04 LTS Server
- Configuring inetd (the Super-Server) on Ubuntu 26.04 LTS Server
- Setting Up a TFTP Server on Ubuntu 26.04 LTS Server (tftpd-hpa)
- FTP Server Setup on Ubuntu 26.04 LTS Server (vsftpd)
- Setting Up a syslog Server on Ubuntu 26.04 LTS Server (rsyslog)
- Managing Services with systemctl and Reading Logs with journalctl on Ubuntu 26.04 LTS Server
- Configuring Automatic Updates on Ubuntu 26.04 LTS Server (unattended-upgrades)
- Automating the Initial Setup of Ubuntu 26.04 LTS Server with cloud-init
- Configuring Kernel Parameters on Ubuntu 26.04 LTS Server (sysctl)
- Configuring the Network on Ubuntu 26.04 LTS Server (Netplan)
- Configuring Name Resolution on Ubuntu 26.04 LTS Server (systemd-resolved)
- Changing the NTP Source on Ubuntu 26.04 LTS Server (chrony)
- Static Routes on Ubuntu 26.04 LTS Server (Netplan)
- How to Install the Latest neovim from the Official Site on Ubuntu 26.04 LTS Server