Routing
The routing table is the kernel data structure that decides which interface and next-hop a packet uses. ip route shows it, ip route get asks the kernel which route wins for a specific destination, and the default route (0.0.0.0/0) is the catch-all gateway.
You can SSH into a machine and see its interface has an IP — but why can the machine reach the internet at all? Why can it reach 10.0.0.20 but not 172.16.0.5? The answer is always the routing table. Every packet the kernel sends out goes through a lookup: which route matches this destination? The match with the longest prefix wins, and that route specifies the exit interface and the next-hop address. When there is no specific route, the default gateway — 0.0.0.0/0 — catches it. When that default route is missing, the machine can talk to its local subnet but nothing else — a classic incident where “internet works from my laptop but not the VM.”
After this lesson you can read ip route output and identify the default gateway, use ip route get <ip> to ask the kernel exactly which route wins for a destination, understand longest-prefix match, add and remove a static route, and diagnose a missing default route.
ip route show — reading the routing table.
ip route show
# default via 10.0.0.1 dev ens3 proto dhcp src 10.0.0.10 metric 100
# 10.0.0.0/24 dev ens3 proto kernel scope link src 10.0.0.10
# Line breakdown:
# default = 0.0.0.0/0 — the catch-all route
# via 10.0.0.1 = next-hop: send the packet to this gateway first
# dev ens3 = egress interface
# proto dhcp = this route was installed by a DHCP client
# src 10.0.0.10 = preferred source address for traffic leaving this route
# metric 100 = cost; lower metric wins when two routes match equally
#
# 10.0.0.0/24 dev ens3 = "for destinations in 10.0.0.0/24, send directly on ens3"
# scope link = no gateway needed — peer is on the same link
# proto kernel = auto-generated when ip addr add created the addressThe 10.0.0.0/24 link-scoped route means the kernel knows hosts in that subnet are reachable without a gateway — it will ARP for their MAC and send the frame directly. Everything outside that range hits the default route.
Longest-prefix match: the rule that decides which route wins.
When a packet is sent to a destination, the kernel compares the destination against every route and picks the most specific match — the one with the longest matching prefix.
# Routing table on host-a (10.0.0.10):
# default via 10.0.0.1 dev ens3
# 10.0.0.0/24 dev ens3 scope link
# 192.168.5.0/24 via 10.0.0.254 dev ens3
# Sending to 10.0.0.20:
# Candidates: default (0 bits matched), 10.0.0.0/24 (24 bits matched)
# Winner: 10.0.0.0/24 — longest prefix → send directly on ens3
# Sending to 192.168.5.100:
# Candidates: default (0 bits), 192.168.5.0/24 (24 bits)
# Winner: 192.168.5.0/24 → next-hop 10.0.0.254
# Sending to 8.8.8.8 (internet):
# Candidates: default (0 bits) — only match
# Winner: default → next-hop 10.0.0.1 (the gateway)/32 is the most specific possible route (one host). A host route like 192.0.2.1/32 via 10.0.0.1 wins over any subnet route containing that address.
ip route get <destination> — ask the kernel which route wins.
Instead of doing the longest-prefix logic in your head, ask the kernel directly:
# Which route does the kernel use for 8.8.8.8?
ip route get 8.8.8.8
# 8.8.8.8 via 10.0.0.1 dev ens3 src 10.0.0.10 uid 1000
# cache
# Which route for a machine on the local subnet?
ip route get 10.0.0.20
# 10.0.0.20 dev ens3 src 10.0.0.10 uid 1000
# cache
# Note: no "via" — direct delivery, no gateway
# Which route if no default exists and we try an external IP?
# ip route get 8.8.8.8
# RTNETLINK answers: Network is unreachable
# → the missing default route is confirmedip route get is the fastest way to diagnose “why can’t this host reach X” — it shows exactly what the kernel will do, including source address selection, before a single packet is sent.
Adding and removing static routes.
DHCP supplies the default gateway automatically. Static routes are needed for additional subnets reachable through a specific gateway:
# Add a static route: reach 192.168.10.0/24 via 10.0.0.254
sudo ip route add 192.168.10.0/24 via 10.0.0.254
# Verify:
ip route show
# default via 10.0.0.1 dev ens3
# 10.0.0.0/24 dev ens3 scope link
# 192.168.10.0/24 via 10.0.0.254 dev ens3
# Remove it:
sudo ip route del 192.168.10.0/24 via 10.0.0.254
# Add with explicit interface:
sudo ip route add 192.168.10.0/24 via 10.0.0.254 dev ens3
# Add a host route:
sudo ip route add 192.0.2.100/32 via 10.0.0.1These changes are not persistent. On reboot the kernel routing table starts empty, then network init (DHCP client, netplan, systemd-networkd) rebuilds it. Persist static routes in /etc/network/interfaces with up ip route add ... lines, or in netplan under routes:.
Missing default route: the classic incident.
A VM can reach 10.0.0.20 but not 8.8.8.8 or anything outside the subnet. The symptom: ping 10.0.0.20 works, ping 8.8.8.8 fails with “Network is unreachable.”
# Confirm: no default route
ip route show | grep default
# (empty — nothing printed)
# Confirm with route get:
ip route get 8.8.8.8
# RTNETLINK answers: Network is unreachable
# Fix temporarily:
sudo ip route add default via 10.0.0.1
# Confirm the fix:
ip route get 8.8.8.8
# 8.8.8.8 via 10.0.0.1 dev ens3 src 10.0.0.10
# Root causes:
# 1. DHCP client failed or was not running — check: systemctl status dhclient or systemd-networkd
# 2. Static config missing the gateway — check /etc/network/interfaces or /etc/netplan/
# 3. Route was manually deleted and not restoredDiagnosing why host-a cannot reach a staging subnet.
host-a (10.0.0.10) needs to reach 192.168.20.0/24 (staging). Connections time out.
# Step 1: check the routing table — does a route exist?
ip route show | grep 192.168.20
# (empty)
# Step 2: ip route get confirms no route
ip route get 192.168.20.5
# RTNETLINK answers: Network is unreachable
# Note: no default route would give the same error for 192.168.20.5
# but we already see a default in ip route show, so it is specifically missing
# Step 3: add the route (gateway 10.0.0.254 serves staging)
sudo ip route add 192.168.20.0/24 via 10.0.0.254
# Step 4: verify
ip route get 192.168.20.5
# 192.168.20.5 via 10.0.0.254 dev ens3 src 10.0.0.10
# Step 5: test connectivity
ping -c 3 192.168.20.5
# Step 6: persist in netplan (/etc/netplan/01-netcfg.yaml):
# network:
# ethernets:
# ens3:
# routes:
# - to: 192.168.20.0/24
# via: 10.0.0.254▸Common mistake
Adding a route without checking if the gateway is reachable. If you run sudo ip route add 192.168.20.0/24 via 10.0.0.254 but 10.0.0.254 is not on the local subnet (not reachable via a link-scope route), the kernel accepts the command but every packet is undeliverable. The symptom: ip route get 192.168.20.5 succeeds but ping times out. Always verify the gateway itself is reachable with ip route get 10.0.0.254 before adding routes that use it.
▸Why this works
On macOS, the routing table is read with netstat -rn (or route -n get <ip> to query a specific destination). The logic — longest-prefix match, default gateway, link-scope routes — is identical. macOS uses BSD routing sockets, not Linux netlink; the concepts transfer perfectly even though the commands differ.
host-a has these routes: default via 10.0.0.1 dev ens3, and 10.0.0.0/24 dev ens3 scope link. You run: ip route get 10.0.0.50. Which route wins and what happens next?
The routing table is the kernel data structure every outbound packet consults. Longest-prefix match selects the most specific route — a /32 beats a /24 beats the default 0.0.0.0/0. ip route show reads the table; ip route get <destination> asks the kernel which route actually wins for a specific address, including source address selection. ip route add/del manages static routes, but changes are lost on reboot — persist them via netplan or /etc/network/interfaces. A missing default route leaves the host with local-subnet access only and is the most common cause of “internet unreachable” on newly provisioned VMs.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.