traceroute: Discovering the Path Hop by Hop
traceroute sends probes with TTL 1, 2, 3… and collects the ICMP Time Exceeded replies each router returns when it discards them, revealing the forward path one hop at a time — and the same mechanism is why the output is full of honest-looking lies.
The problem
TTL as a probe
Every IP packet carries a TTL (hop limit in IPv6). Each router decrements it by one before forwarding; a router that decrements it to zero discards the packet and sends an ICMP Time Exceeded (type 11, code 0; ICMPv6 type 3) back to the source. The mechanism exists to kill packets caught in a routing loop. traceroute abuses it: send a probe with TTL 1 and the first router replies; TTL 2 and the second router replies; keep going until the destination itself answers — with an ICMP Port Unreachable for a UDP probe, an echo reply for an ICMP probe, or a SYN-ACK/RST for a TCP probe.
The source address of each Time Exceeded message is the router’s interface that received the probe (the ingress interface, usually), so what you see is a list of ingress addresses along the forward path, with the RTT to each measured as probe-out to ICMP-back. Three probes per TTL by default give three RTTs per hop; 30 TTLs is the default maximum.
- Probe TTL=1first router decrements to 0, discards, replies Time Exceeded — hop 1 and its RTT↓
- Probe TTL=2passes router 1; router 2 discards and replies — hop 2↓
- Probe TTL=3 … neach TTL reveals one more ingress interface↓
- Probe reaches destinationUDP → ICMP Port Unreachable; ICMP → Echo Reply; TCP → SYN-ACK or RST; the trace ends
UDP, ICMP or TCP probes — platform differences
Linux and macOS traceroute send UDP datagrams to high ports starting at 33434, incrementing per probe, so the destination answers with Port Unreachable. Windows tracert sends ICMP echo requests, so the destination answers with an echo reply. Linux traceroute -I uses ICMP echo and -T uses TCP SYN (needs root; tcptraceroute is the standalone version); -p 443 sets the port. The choice matters because firewalls treat them differently: a firewall that drops UDP to random high ports kills the Linux default at the last hop while tcptraceroute -p 443 sails through to the real service port. mtr (winmtr on Windows) runs the trace continuously and shows per-hop loss and RTT distributions — better than a single traceroute for intermittent problems.
The intermediate hops reply with Time Exceeded regardless of probe type, so the middle of the path looks the same with every variant; the probe type mainly decides whether the last hop answers.
| Tool / flag | Probe | Destination answers with | Typical failure |
|---|---|---|---|
Linux/macOS traceroute (default) | UDP to 33434+ | ICMP Port Unreachable | Firewall drops UDP to high ports → * * * at the end |
Windows tracert, Linux traceroute -I | ICMP echo | ICMP Echo Reply | Destination filters ICMP echo → * * * at the end |
Linux traceroute -T -p 443, tcptraceroute | TCP SYN to the service port | SYN-ACK (open) or RST (closed) | Rarely fails at the end; needs root; some middleboxes intercept SYNs |
mtr / winmtr | ICMP (default) or UDP/TCP | As above, continuously | Same blind spots, but per-hop loss over time is visible |
Reading a trace
Read RTT jumps between consecutive hops, not absolute values: the hop where RTT goes from 15 ms to 95 ms is where the packet crossed an ocean or a satellite link. Read * * * as "no reply from this hop", which is not the same as "this hop dropped my traffic" — if later hops reply, the silent one forwarded fine and merely declined to generate ICMP. A trace that ends in * * * forever means either the destination filters the probe type or the path really is broken; switch probe type before concluding.
Hostnames from reverse DNS often encode location and role (ae-3.r21.frankfurt.de style), which is how you learn that a Berlin-to-Munich packet went via Amsterdam. Private addresses (10.x, 172.16–31.x, 192.168.x) in the middle of a trace are normal inside carriers and clouds.
$ traceroute -n api.example.com traceroute to api.example.com (203.0.113.10), 30 hops max, 60 byte packets 1 192.168.1.1 0.8 ms 0.7 ms 0.7 ms <- home/office router 2 100.64.12.1 9.3 ms 8.9 ms 9.1 ms <- ISP access (CGNAT range) 3 62.115.40.7 10.2 ms 10.0 ms 10.4 ms 4 62.115.118.1 11.7 ms 11.6 ms 11.9 ms 5 80.91.246.33 18.4 ms 18.1 ms 18.3 ms <- Amsterdam 6 213.155.135.9 94.6 ms 94.2 ms 94.8 ms <- +76 ms: crossed the Atlantic 7 198.51.100.2 95.1 ms 95.0 ms 95.3 ms 8 * * * <- forwards fine, no ICMP 9 198.51.100.77 96.0 ms 95.8 ms 96.1 ms 10 203.0.113.10 96.4 ms 96.2 ms 96.3 ms <- destination
Why traces lie
Silent hops. Routers generate ICMP on their control-plane CPU, not in forwarding hardware, and rate-limit it aggressively. A hop showing * * * or 30% loss in mtr while every later hop shows 0% loss is rate-limiting, not path loss. The rule: loss that does not persist to the destination is not real loss. Similarly a latency spike at one hop that disappears at the next means the router was slow to generate the ICMP, not slow to forward.
Asymmetric paths. Each RTT is forward-path-to-that-hop plus that hop’s return path to you, and the return path is chosen by the hop’s own routing, which may differ entirely from yours. A sudden RTT increase can come from a bad return route from that router, invisible in the forward trace. Tracing from the other end shows the other half; Internet Routing: Autonomous Systems and BGP explains why the halves differ.
MPLS and tunnels. Inside carrier networks packets are label-switched; providers frequently disable TTL propagation into the MPLS tunnel, so a dozen physical routers show up as one hop, or several hops show the same RTT because the ICMP travelled to the tunnel end first. ECMP. Load-balanced paths hash each probe differently, so probes for TTL n and n+1 may traverse different routers, producing a trace that interleaves two paths — paris-traceroute and mtr keep flow identifiers stable to avoid this.
None of this makes traceroute useless. It reliably shows: the rough geography of the path, the hop where a large RTT is added and persists, and the last hop that responded before silence. It does not reliably show per-hop loss, the return path, or the hops inside a carrier’s core.
- Loss or latency that appears at a hop and vanishes at later hops is control-plane behaviour, not forwarding behaviour.
- Loss or latency that appears at a hop and persists to the destination is real and started at or before that hop.
- A trace that ends in silence is inconclusive until you change probe type (
-I,-T -p 443). - The last responding hop before permanent silence is usually a firewall, or the boundary of a network that drops the probe type.
Key points
- traceroute discovers the forward path by sending probes with increasing TTL and collecting ICMP Time Exceeded replies from each router.
- Linux/macOS default to UDP probes, Windows tracert to ICMP echo;
-I/-Tchange the probe type and decide whether the last hop answers. - Read RTT jumps between hops, not absolute values; a persistent jump is distance or a detour.
* * *and per-hop loss that do not persist downstream are ICMP rate-limiting on the router’s control plane, not real loss.- RTTs include the hop’s own return path, which may differ from yours; MPLS hides hops; ECMP interleaves paths.
mtrshows loss and latency distribution per hop over time, which single traces cannot.
Why does this exist?
Mechanisms are answers to constraints. Open each question before reading the answer.
▸Why does traceroute work at all, if routers were never asked to cooperate?
Because every router must implement TTL expiry to kill looping packets, and the standard says to report it with ICMP Time Exceeded. traceroute is a side effect of loop protection.
▸Why three probe types?
Firewalls filter by protocol and port. A probe that looks like real traffic to the real service port (TCP SYN to 443) survives filters that kill UDP to random ports or ICMP echo — so the choice of probe decides whether the end of the path is visible.
▸Why does a hop show loss when the path is fine?
Forwarding happens in hardware at line rate; generating an ICMP error happens on a small CPU that rate-limits itself. The probe was forwarded; the reply was never created.
traceroute
$ traceroute -n example.com # Linux/macOS default: UDP to ports 33434+ 1 192.168.1.1 (_gateway) 1.0 ms 1.0 ms 1.4 ms
How it fails
What the failure looks like from inside real software.
- Escalating to a carrier about "30% loss at hop 8" that is ICMP rate limiting and does not persist to the destination.
- Concluding the destination is unreachable from a trace that ends in
* * *when the destination merely filters UDP;-T -p 443would have completed. - Blaming the hop where RTT jumped 80 ms for congestion when it is an ocean crossing.
- Reading a single trace as the path when ECMP hashes each probe onto different routers.
- Diagnosing the return path from a forward trace — the return route belongs to the other end and needs a trace from there.