UDP: no connection, no guarantees
How UDP works, what you trade for speed, and why DNS, video, and games pick it.
How UDP works, what you trade for speed, and why DNS, video and games pick it.
Why this matters
DNS, statsd metrics, syslog, VPN over WireGuard, calls and HTTP/3 all run
over UDP. When something goes wrong with them, your TCP instincts mislead
you: there's no "connect to the port", the sender never learns about a lost
datagram, and an nc check prints succeeded even when nobody received a
single datagram. This lesson covers how UDP is built and how to tell what is
actually happening to a port.
Datagrams instead of a stream
UDP doesn't establish a connection. The sender just fires a datagram at an address and port: no handshake, no acknowledgments, no retries. If the datagram doesn't arrive, UDP never finds out.
There are no guarantees at all: a datagram can get lost, arrive twice, or overtake the one sent before it. If an application cares about order or delivery, it has to handle that itself.
A datagram (UDP) is an independent message: one send call from the application becomes one datagram, and the receiver reads it whole. TCP has no message boundaries; it's a stream of bytes.
The UDP header
The whole UDP header is 8 bytes: four fields of 16 bits each.
0 15 16 31 (bits)
+------------------+------------------+
| source port | dest port |
+------------------+------------------+
| length | checksum |
+------------------+------------------+
| data ... |
+-------------------------------------+
The length field counts the header plus the data, so it's never below 8. The checksum is optional in IPv4 (zero means "not computed") and mandatory in IPv6. Compare that to TCP, whose header starts at 20 bytes and carries sequence numbers, flags, windows and options.
Here's what it looks like in tcpdump: a datagram carrying the text ping
plus a newline, 5 bytes of data.
tcpdump -nv -r /tmp/udp.pcap
IP (tos 0x0, ttl 64, id 23697, offset 0, flags [DF], proto UDP (17), length 33)
10.209.99.20.59493 > 10.209.99.10.9101: UDP, length 5
length 5 in the UDP line is just the data. length 33 in the IP line is
the whole packet: 20 bytes of IP header, 8 bytes of UDP header, and 5 bytes
of data.
Why this transport exists
UDP pays for skipping guarantees with speed and simplicity. There's no
handshake, so no extra round trip before the first byte. There's no
connection state in the server's kernel either: one socket accepts
datagrams from any number of clients, with no connection table and no
TIME-WAIT.
Who this suits:
- DNS: a normal query and response fit in one datagram; if it's lost, the client just asks again;
- video and calls: a frame that arrives late is useless, and resending it the way TCP would makes no sense, so it's simpler to show the next one;
- online games: what matters is the player's current position, not the history of old ones;
- metrics (statsd) and logs (syslog): losing one data point doesn't hurt, and the application doesn't stall if the collector is unreachable;
- QUIC (the HTTP/3 transport): runs on top of UDP and implements its own reliability, encryption and streams, so it doesn't depend on TCP in the kernel.
dig +short izidevops.com @1.1.1.1
5.35.11.49
That query went out over UDP to port 53. DNS can also use TCP: if the
answer doesn't fit in a datagram, the server sets the tc (truncated) flag
in the response, and the client repeats the query over TCP on the same port
53. TCP support for DNS is mandatory (RFC 7766).
How to check a UDP port
With TCP it's simple: a SYN gets either a SYN-ACK or an RST back. UDP has no "port is open" reply. From outside, there are three possible outcomes:
- a reply came back from the service: the port is definitely open;
- an ICMP port unreachable came back: the port is closed, nobody is listening on it, and the receiver's kernel reports exactly that;
- silence: either a service received the datagram and just doesn't reply, or a firewall silently dropped it. From outside, you can't tell these two apart.
The ICMP shows up in the sender's traffic capture:
IP 10.209.99.20.39878 > 10.209.99.10.7003: UDP, length 5
IP 10.209.99.10 > 10.209.99.20: ICMP 10.209.99.10 udp port 7003 unreachable, length 41
nc with the -uz flags is misleading. It sends probe datagrams and
prints succeeded whenever ICMP unreachable doesn't come back. So "success"
here only means "wasn't rejected":
nc -uzv -w 2 10.0.0.5 514; echo $?
Connection to 10.0.0.5 514 port [udp/syslog] succeeded!
0
That same output shows up both for a working syslog service and for a port
a firewall is dropping. An exit code of 1 and no succeeded only happen
when an ICMP unreachable comes back.
So "checking a UDP port with telnet" just doesn't work, and scanners like
nmap mark silent UDP ports as open|filtered. The only reliable way to
tell the two apart is either a reply from the protocol itself (send a real
DNS query, not an empty datagram), or checking on the server itself:
ss -uln shows whether anyone is listening on the port, and the firewall rules
show whether packets are being dropped.
ss -uln
State Recv-Q Send-Q Local Address:Port Peer Address:Port
UNCONN 0 0 0.0.0.0:7002 0.0.0.0:*
UDP sockets show up in ss with state UNCONN, not LISTEN: there's
nothing to listen for in the TCP sense here, the socket just accepts
datagrams.
Common mistakes
- Trusting
nc -uz ... succeeded. For UDP that means "no ICMP came back", not "the service received the data". - Trusting the sender's log. A "sent" line only means the datagram was
handed to the kernel. An application sending metrics or logs over UDP
will log "sent" even if nobody's on the other end. Check on the
receiving side:
tcpdumpon its interface, andss -uln. - Opening only TCP in the firewall. A service on UDP needs a rule with
udp, and a DNS server needs both protocols on port 53. - Assuming nothing tracks UDP along the way. UDP itself keeps no connections, but firewalls and NAT keep conntrack entries for UDP with a timeout. On Linux the default is 30 seconds for a single exchange and 120 seconds for a stream where packets went both ways. If the client stays quiet longer than that, the entry disappears, and the server's reply never reaches the client behind the NAT. That's why long-lived UDP protocols (VPN, VoIP) send keepalives.
What you'll need for the tasks
docker exec <container> <command>: lab commands run inside the lab environment's containers, nosudoneeded;echo ping | nc -u -w 2 <address> <port>: send a datagram and wait 2 seconds for a reply;nc -uzv -w 2 <address> <port>: the port "check" we don't trust;tcpdump -n -r <file>andtcpdump -n -i eth0 -c 4 udp or icmp: read a traffic capture, or listen on an interface for up to four packets;ss -uln: which UDP ports are open and on which addresses;iptables -S INPUT: inbound firewall rules (covered in the firewall lesson);- the "How to check a UDP port" and "Common mistakes" sections.
Next lesson: ICMP, what ping and traceroute run on.
Interview-style questions
Interview questions on this topic: 1
Included with practice access, $49 one-time. Get access
Answer out loud or in writing, then reveal the answer and honestly mark whether you knew it.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson networking-basics-09-udp
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress