cilium/cilium · error
route to destination %s contains gateway %s, must be directl
Error message
route to destination %s contains gateway %s, must be directly reachable. Add `direct-routing-skip-unreachable` to skip unreachable routes
What it means
When building a direct route, createDirectRouteSpec requires the kernel route to the peer node IP to be directly connected (no gateway), because Cilium would otherwise install a route spec pointing at the wrong next-hop. If routes[0].Gw is set (non-nil, non-unspecified, and different from the node IP itself) and skip-unreachable is not enabled, it returns this error telling the operator the route is gatewayed and how to skip such routes.
Source
Thrown at pkg/datapath/linux/node.go:191
routes, err = netlink.RouteGet(nodeIP)
if err != nil {
err = fmt.Errorf("unable to lookup route for node %s: %w", nodeIP, err)
return
}
if len(routes) == 0 {
err = fmt.Errorf("no route found to destination %s", nodeIP.String())
return
}
if routes[0].Gw != nil && !routes[0].Gw.IsUnspecified() && !routes[0].Gw.Equal(nodeIP) {
if skipUnreachable {
log.Debug("route to destination contains gateway, skipping route as not directly reachable",
logfields.NodeIP, nodeIP,
logfields.GatewayIP, routes[0].Gw)
addRoute = false
} else {
err = fmt.Errorf("route to destination %s contains gateway %s, must be directly reachable. Add `direct-routing-skip-unreachable` to skip unreachable routes",
nodeIP, routes[0].Gw.String())
}
return
}
linkIndex := routes[0].LinkIndex
// Special treatment if the route points to the loopback, lookup the
// local route and use that ifindex
if linkIndex == 1 {
family := netlink.FAMILY_V4
dst := &net.IPNet{IP: nodeIP, Mask: net.CIDRMask(32, 32)}
if nodeIP.To4() == nil {
family = netlink.FAMILY_V6
dst.Mask = net.CIDRMask(128, 128)
}
filter := &netlink.Route{View on GitHub (pinned to ac7b90affa)
Solutions
- Add --direct-routing-skip-unreachable=true (helm: --set directRoutingSkipUnreachable=true) so gatewayed (non-directly-reachable) node routes are skipped instead of failing
- Make peer nodes directly reachable at L2: put nodes on the same subnet/VLAN, or set up direct routes without a gateway
- Switch back to encapsulation (tunnel mode, e.g. vxlan/geneve) if nodes are genuinely routed through a gateway — direct routing is not the right mode for this topology
- Verify the host route with `ip route get <nodeIP>`; if a gateway is expected, skip-unreachable is the correct configuration
Example fix
// before: direct routing across a gatewayed route fails # cilium-agent --direct-routing=true --auto-direct-node-routes=true // after: skip gatewayed routes # cilium-agent --direct-routing=true --auto-direct-node-routes=true --direct-routing-skip-unreachable=true
Defensive patterns
Strategy: validation
Validate before calling
// detect gatewayed routes to peer nodes before enabling direct routing
const { execSync } = require('child_process');
function routeIsDirect(ip) {
const out = execSync(`ip route get ${ip}`).toString();
return !/\bvia\s+/.test(out); // a 'via X' means gatewayed, not directly connected
}
if (!routeIsDirect('10.0.2.15')) {
// enable --direct-routing-skip-unreachable or switch to tunnel mode
console.warn('peer node reachable only via gateway; direct routing will fail');
} Type guard
function isDirectlyConnected(routeGetOutput) {
return typeof routeGetOutput === 'string' && !/\bvia\s+\S+/.test(routeGetOutput);
} Try / catch
try {
await installDirectRoute(nodeIP, linkIndex);
} catch (err) {
if (/contains gateway/.test(err.message)) {
log.warn('gatewayed node route skipped; enable direct-routing-skip-unreachable or use tunneling', { nodeIP });
return skipRoute(nodeIP);
}
throw err;
} Prevention
- Only enable direct routing when all nodes share L2 adjacency (same subnet/VLAN)
- Set --direct-routing-skip-unreachable=true when some node pairs are gateway-separated
- Prefer tunnel mode (vxlan/geneve) for routed topologies (cloud VPCs, VPNs)
- Document the topology requirement for operators deploying with autoDirectNodeRoutes
- Test `ip route get <peerIP>` on each node pair during cluster bring-up
When it happens
Trigger: installDirectRoute -> createDirectRouteSpec with native/direct routing where `ip route get <nodeIP>` resolves via a gateway (e.g. 'via 10.0.0.1'), and the agent was NOT started with --direct-routing-skip-unreachable=true.
Common situations: Nodes on different L2 segments or subnets while autoDirectNodeRoutes/direct routing expects L2 adjacency; cloud environments where inter-node traffic traverses a router (VPC gateway) rather than being directly connected; VPN/WireGuard or SDN setups that present a gatewayed route to peer nodes; migrating from overlay mode to native routing without checking topology.
Related errors
- unable to lookup route for node %s: %w
- no route found to destination %s
- unable to find local route for destination %s which is route
- unable to list %s chain: %s (%w)
- unable to add %s chain: %s (%w)
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/04bdfad1b9670361.
Report an issue: GitHub.