A Linux routing table contains both `10.0.0.0/8 via 192.168.1.9 dev eth0 metric 500` and `default via 192.168.1.1 dev eth0 metric 100`. Which route does the kernel use for destination 10.1.2.3, and what does the metric actually decide?
answer
- specificity beats preference
- most significant bits win
- metric only compares like with like
- two defaults, one wins
- ask the kernel for the answer
basics
~20 sThe 10.0.0.0/8 route wins: the kernel matches the longest (most specific) prefix first, and 10.1.2.3 falls inside it. The metric never compares different prefixes — it only breaks ties between routes to the same prefix, where the lowest value wins.
solid answer
~40 sThe kernel picks `10.0.0.0/8 via 192.168.1.9`, because route lookup is longest-prefix match: among all entries whose prefix contains the destination, the one with the most significant bits wins, and /8 is more specific than the default's /0. Metric is not a global cost that competes across prefixes — it is a tie-break used only when two routes cover the *same* prefix, where the lower number is preferred. So a metric of 500 on a /8 still beats a metric of 100 on 0.0.0.0/0 for any 10.x destination. That is exactly why adding a low-metric default route does not "override" a bad specific route someone left behind. Rather than reason it out, ask the kernel: `ip route get 10.1.2.3` performs a real lookup and prints the nexthop, interface and source address it would use.
go deeper
Remember the order: most specific prefix first, metric only afterwards. Be able to point at which of two routes covers a given address.
Explain longest-prefix match and that metric is a preference number scoped to identical prefixes, lower being better. Know why a second identical default route is rejected with "File exists".
Diagnose the classic case where one internal range is dark because a stale specific route outranks a healthy default, and confirm with ip route get instead of reading the table. Distinguish metric-based failover from true multipath.
Decide fleet-wide how route preference is produced — DHCP, router advertisements, a network manager's per-connection metrics, or a routing daemon — so that failover is deterministic rather than an accident of which interface came up first.
## The lookup algorithm, in order For every packet the kernel must produce a routing decision: a nexthop, an egress interface, and a source address. Reduced to essentials, and ignoring policy rules for the moment, the sequence is: 1. **Select the routing table.** Policy rules (`ip rule show`) decide which table is consulted; on a default install this is effectively always `main`. 2. **Longest-prefix match within that table.** Of every entry whose prefix contains the destination, the one with the greatest prefix length wins. /32 beats /24 beats /8 beats /0. 3. **Tie-break among equal prefixes by metric.** Only if two or more entries share the same prefix does the metric matter, and the *lowest* value is preferred. Specificity therefore dominates preference. This ordering is the single most commonly muddled point about Linux routing, and it is why the question's answer is the /8: 10.1.2.3 is inside 10.0.0.0/8, that is a longer prefix than 0.0.0.0/0, and the comparison stops there. The metrics 500 and 100 are never compared with each other, because the two entries do not describe the same prefix. ## What the metric really is Despite the name, the metric is not a distance or a cost in any protocol sense. In the kernel it is the route's `RTA_PRIORITY`: a plain unsigned preference number, lower meaning better. Its usual role is picking between redundant paths to the same destination — two default routes, one over a wired link and one over Wi-Fi: ``` default via 192.168.1.1 dev eth0 proto dhcp metric 100 default via 192.168.1.1 dev wlan0 proto dhcp metric 600 ``` Both are 0.0.0.0/0, so here the metric decides, and eth0 is used. NetworkManager assigns metrics by connection type exactly so wired links win over wireless without anybody hand-editing routes. The metric also lets two routes with identical prefixes coexist at all. The kernel treats prefix plus metric (plus table and a few other attributes) as the identity of a route, so adding a duplicate with the same metric is rejected: ``` # ip route add default via 192.168.1.2 dev eth0 RTNETLINK answers: File exists ``` Give it a different metric and it installs fine, sitting as an inactive alternative until the preferred one is removed or its link goes down. ## Where multipath fits If you genuinely want traffic *split* across two nexthops rather than one preferred and one standby, that is equal-cost multipath, and it is a single route with several nexthops rather than several routes: ``` ip route add default \ nexthop via 192.168.1.1 dev eth0 weight 1 \ nexthop via 192.168.2.1 dev eth1 weight 1 ``` Modern kernels hash per flow so that a given connection stays on one path; the sysctl `net.ipv4.fib_multipath_hash_policy` controls what goes into that hash. Multipath is a deliberate configuration, not something that happens because two routes look equally good. ## Why this matters operationally The practical consequence is that you cannot fix a wrong specific route by adding a better default. A stale `10.0.0.0/8 via <dead-router>` line — left over from a VPN, a container runtime, or a decommissioned network — swallows every 10.x destination on the box no matter what metric the default carries. The symptom is the confusing one where the internet works and one internal range does not, and the cause is invisible unless you look for a longer prefix rather than at the default route everyone checks first. The same logic explains host routes: a /32 entry for a single address is the most specific match possible, which is how a VPN client or a monitoring agent pins one destination onto its own path while leaving everything else alone. ## Verify, do not deduce On a table with dozens of entries, eyeballing prefixes is error-prone. `ip route get` runs the actual FIB lookup, including the policy-rule step, and prints the result: ``` $ ip route get 10.1.2.3 10.1.2.3 via 192.168.1.9 dev eth0 src 192.168.1.50 uid 1000 ``` It reports the chosen nexthop, the egress device and the source address that would be used. In an interview, saying "I would confirm with `ip route get` rather than argue about the table" is itself a good answer, because it is what the kernel will actually do.
- Why does `ip route add default via 192.168.1.2 dev eth0` fail with "File exists" when a default route is already present?Because a route's identity includes its prefix and metric, and both entries would be 0.0.0.0/0 with the same (default) metric. The kernel refuses a duplicate. Give the new one a distinct metric and it installs as a lower-preference alternative, or use `ip route replace` if you meant to substitute the existing route.
- Internet access works but one internal /8 range is unreachable. Where do you look first?For a more specific route covering that range, not at the default route. A stale entry such as `10.0.0.0/8 via <dead-gateway>` — often left by a VPN or a container runtime — wins over any default regardless of metric. `ip route get <internal-ip>` names the offending nexthop immediately.
- How do you make the kernel actually load-balance across two uplinks rather than prefer one?Install a single multipath route with several nexthops, for example `ip route add default nexthop via A dev eth0 weight 1 nexthop via B dev eth1 weight 1`. Two separate default routes with different metrics give failover, not balancing. Modern kernels hash per flow so a connection does not get split mid-stream.
saying these in an interview costs you the question
- Saying the lowest metric always wins regardless of prefix length
- Treating metric as a bandwidth or latency cost the kernel measures
- Believing routes are evaluated top to bottom in listed order
- Expecting two equal default routes to load-balance automatically
- Checking only the default route when one subnet is unreachable