UDP — When Speed Beats Safety
UDP trades reliability and ordering for speed and simplicity — use it when fresh data beats complete data.
The Problem
How does a transport protocol deliver data with minimal latency and overhead when occasional packet loss is acceptable or handled at the application layer?
Mental Model
Like sending postcards — no confirmation of delivery, but fast and simple. Drop them in the mailbox and hope for the best.
Architecture Diagram
How It Works
The entire UDP specification (RFC 768) is three pages long. That's not an exaggeration — it's literally three pages. Compare that to TCP's RFC 793 at 85 pages. This simplicity is UDP's superpower.
UDP takes the data, slaps on an 8-byte header, and sends it as an IP datagram. That's it. No handshake. No connection. No acknowledgment. No retransmission. No ordering. No flow control. No congestion control. What gets sent is what goes on the wire, and if it doesn't arrive, nobody gets notified.
The UDP Header
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data (payload) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Four fields. Eight bytes. Source port, destination port, length, checksum. The checksum is optional in IPv4 (set to zero to skip) and mandatory in IPv6. There's no sequence number, no acknowledgment number, no window size, no flags — all the machinery TCP uses for reliability is absent.
Sending and Receiving
On the sender side:
# Pseudocode — sending a UDP datagram
sock = socket(AF_INET, SOCK_DGRAM)
sock.sendto(b"Hello", ("10.0.1.50", 9000))
# That's it. No connect(). No handshake. Data is on the wire.
On the receiver side:
# Pseudocode — receiving UDP datagrams
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(("0.0.0.0", 9000))
data, addr = sock.recvfrom(65535)
# Each recvfrom() returns one complete datagram
Each sendto() produces exactly one IP datagram. Each recvfrom() returns exactly one complete datagram. There's no stream abstraction — UDP preserves message boundaries, unlike TCP which is a byte stream.
TCP vs UDP: The Real Trade-offs
The internet loves to oversimplify this as "TCP = reliable, UDP = fast." The real story is more nuanced.
| Aspect | TCP | UDP |
|---|---|---|
| Connection setup | 1 RTT (3-way handshake) | None |
| Header overhead | 20-60 bytes | 8 bytes |
| Ordering | Guaranteed | Not guaranteed |
| Reliability | ACKs + retransmission | None |
| Flow control | Sliding window | None |
| Congestion control | cwnd-based | None (app's responsibility) |
| Message boundaries | No (byte stream) | Yes (datagram) |
| Kernel state | Per-connection state machine | Stateless |
| Latency | Higher (handshake + HOL blocking) | Lower |
The cost of TCP isn't just the handshake — it's head-of-line blocking. If segment 5 out of 10 is lost, TCP holds back segments 6-10 until segment 5 is retransmitted and received. For real-time applications, those held-back segments are stale by the time they arrive.
When UDP Wins: Real Use Cases
DNS: The Canonical Example
A DNS query is typically 30-100 bytes. The response is 50-500 bytes. Making a TCP connection (3-way handshake + query + response + teardown = 4 RTTs minimum) for 100 bytes of data is absurd. UDP does it in 1 RTT: send query, receive response.
# DNS query over UDP — 1 RTT
dig @8.8.8.8 example.com
# Force DNS over TCP (for comparison)
dig @8.8.8.8 example.com +tcp
# Adds ~100ms on a typical connection
DNS falls back to TCP for responses over 512 bytes (or 4096 with EDNS0) and for zone transfers (AXFR/IXFR). But 99% of DNS traffic is UDP.
Video Conferencing: Freshness Over Completeness
During a Zoom call, when a packet is lost, retransmitting it is pointless. By the time the retransmitted video frame arrives, the stream is already 3-4 frames ahead. Displaying a stale frame would be worse than skipping it.
Video calls use RTP (Real-time Transport Protocol) over UDP. RTP adds sequence numbers and timestamps so the application can detect loss and reorder, but it deliberately does NOT retransmit. Instead, video codecs use techniques like forward error correction (FEC) and reference frame management to handle loss gracefully.
Gaming: The Latest State Wins
In a multiplayer game, the server sends player positions 30-60 times per second. If update #47 is lost, there's no point retransmitting it — update #48 already has newer position data. UDP lets the game engine always process the freshest data without waiting for retransmissions.
StatsD / Metrics: Don't Block the App
Application metrics libraries like StatsD use UDP because the monitoring system should never slow down the application. If a metric packet is lost, the dashboards show a tiny gap — completely acceptable. If the metrics library blocked on a TCP send and the metrics server was slow, the application would stall. That's unacceptable.
# Sending a StatsD metric over UDP — fire and forget
echo "api.request.count:1|c" | nc -u -w0 statsd-host 8125
Building Reliability on Top of UDP
The most important modern development in transport protocols is building reliability at the application layer on top of UDP. QUIC is the prime example.
Why not just use TCP? Two reasons:
- TCP is in the kernel. Updating TCP's congestion control or adding features means updating operating systems — a process that takes years. QUIC runs in userspace and can be updated with an app deployment.
- TCP has connection-level head-of-line blocking. If one stream's packet is lost, ALL streams on that connection stall. QUIC multiplexes streams independently — a lost packet on stream A doesn't block stream B.
QUIC on UDP provides:
- Reliable, ordered delivery (per stream)
- Built-in TLS 1.3 encryption
- 0-RTT connection establishment
- Connection migration across network changes
- Independent stream multiplexing
This is the future of transport: UDP as the thin layer, application-level protocols providing exactly the reliability guarantees needed.
The Responsibility Problem
UDP's lack of congestion control is both its strength and its danger. TCP backs off when the network is congested. UDP doesn't. A UDP application that blasts packets at maximum rate will:
- Fill router buffers, increasing latency for all traffic
- Cause packet drops for TCP flows (which then back off, giving UDP even more bandwidth)
- Potentially trigger ISP rate limiting or filtering
This is why RFC 8085 (UDP Usage Guidelines) exists: applications using UDP MUST implement their own congestion control or rate limiting. QUIC does this. RTP does this (via RTCP feedback). Any custom UDP protocol must do this too.
# Testing UDP throughput with iperf3
# Server
iperf3 -s
# Client — send 100 Mbps of UDP traffic
iperf3 -c server -u -b 100M -t 10
# Output shows: sent, received, loss %, jitter
Fragmentation: The Silent Killer
UDP datagrams can be up to 65,535 bytes (the IP packet size limit). But if a datagram exceeds the path MTU (typically 1500 bytes for Ethernet, minus 20 bytes IP header, minus 8 bytes UDP header = 1472 bytes max payload), IP fragments it.
IP fragmentation is terrible:
- If ANY fragment is lost, the entire datagram is lost
- Fragments consume reassembly buffers on the receiver
- Many firewalls and NATs drop fragments
- Fragment reassembly is a known DDoS amplification vector
The practical limit for UDP payloads is 1472 bytes (Ethernet) or 1232 bytes (safe for any path including IPv6 tunnels). QUIC uses 1200 bytes as its minimum MTU to be safe everywhere.
# Test path MTU
ping -M do -s 1472 target-host
# If this fails with "message too long", the path MTU is smaller
When NOT to Use UDP
- File transfers: Every byte must be delivered in order. Use TCP.
- Database queries: Queries and results must be complete and ordered. Use TCP.
- HTTP APIs: Unless QUIC/HTTP/3 is in play, HTTP requires TCP's reliability.
- Any bulk data transfer: Without congestion control, the network takes the hit.
The decision framework is simple: if losing a packet means the entire operation fails and must be retried, use TCP. If losing a packet just means slightly degraded quality that the user barely notices, UDP is the right protocol.
Key Points
- •UDP has no connection setup — no handshake, no state to maintain. A single sendto() call puts a packet on the wire
- •The 8-byte UDP header (vs TCP's 20-60 bytes) means less overhead per packet — critical for small, frequent messages like DNS queries
- •UDP provides no ordering, no retransmission, no flow control, and no congestion control. The application handles all of this (or accepts the loss)
- •UDP is the foundation for protocols that need speed over reliability: DNS, DHCP, NTP, gaming, VoIP, video streaming
- •QUIC is proof that reliable, multiplexed transport can be built on top of UDP — doing so enables innovation at the application layer without waiting for OS kernel updates
Key Components
| Component | Role |
|---|---|
| UDP Header | Minimalist 8-byte header with source port, destination port, length, and checksum — nothing else |
| Datagram | Each UDP message is an independent, self-contained unit — no concept of a stream or connection |
| Checksum | Optional in IPv4, mandatory in IPv6 — detects corruption but does not correct or retransmit |
| Port Multiplexing | Uses port numbers to multiplex multiple applications on the same IP address, just like TCP |
| No State Machine | Unlike TCP, there are no connection states — the OS doesn't track connections, saving memory and CPU |
When to Use
Use UDP when latency matters more than reliability (real-time media, gaming), when messages are small and self-contained (DNS, NTP), when building custom reliability on top (QUIC), or when the application can tolerate loss (metrics, logging).
Tool Comparison
| Tool | Type | Best For | Scale |
|---|---|---|---|
| iperf3 | Open Source | UDP throughput and jitter testing between two endpoints | Any |
| tcpdump | Open Source | Capturing and analyzing UDP packets on the wire | Any |
| netcat (nc) | Open Source | Quick UDP send/receive testing from the command line | Any |
| Wireshark | Open Source | Deep protocol analysis of UDP-based protocols (DNS, QUIC, RTP) | Any |
Debug Checklist
- Verify UDP traffic is reaching the destination: tcpdump -i eth0 udp port 53 — if outbound packets appear but no inbound, check firewalls
- Check for UDP packet loss with iperf3 -c host -u -b 100M — compare sent vs received datagrams
- Monitor socket buffer overflows: cat /proc/net/udp and check the drops column — non-zero means the app isn't reading fast enough
- Look for MTU issues: if large UDP datagrams fail but small ones succeed, IP fragmentation or MTU mismatch is the cause
- Check firewall rules: many firewalls block UDP by default or have different rules for UDP vs TCP
Common Mistakes
- Saying 'UDP is unreliable, never use it.' UDP is unreliable by design — the question is whether the application needs reliability at the transport layer
- Sending UDP datagrams larger than the path MTU. This causes IP fragmentation, which is far worse than the original packet loss problem
- Not implementing application-level rate limiting. Without TCP's congestion control, UDP can flood the network and harm other traffic
- Assuming UDP datagrams arrive in order. Networks reorder packets — if order matters, the application must handle it
- Using UDP for large file transfers without building reliability on top. This inevitably leads to a poor reimplementation of TCP
Real World Usage
- •DNS queries use UDP by default (port 53) because a single request-response fits in one datagram — no handshake overhead
- •Video conferencing (Zoom, Teams, Meet) uses UDP via RTP/SRTP because a dropped frame is better than a delayed frame
- •Online gaming sends player position updates over UDP because the latest state matters more than retransmitting stale data
- •QUIC (HTTP/3) builds reliable, multiplexed transport on top of UDP to avoid kernel TCP stack limitations
- •DHCP uses UDP because clients don't have IP addresses yet — they can't establish TCP connections