XTLS/Xray-core · error

failed to add system route {cidr}

Error message

failed to add system route {cidr}

What it means

After parsing a CIDR, Xray installs a netlink route (LinkIndex = TUN, Priority 1) via netlink.RouteAdd. Failure rolls back previously added routes and returns this wrapped error. Common base causes: EEXIST (route already present with same metric), EPERM (no CAP_NET_ADMIN), ENETUNREACH (prefix not maskable/routable), or link index invalid.

Source

Thrown at proxy/tun/tun_linux.go:285

	if len(t.options.AutoSystemRoutingTable) == 0 {
		return nil
	}
	tunIndex := t.tunLink.Attrs().Index
	for _, cidr := range t.options.AutoSystemRoutingTable {
		prefix, err := netip.ParsePrefix(cidr)
		if err != nil {
			return errors.New("invalid system route ", cidr).Base(err)
		}
		prefix = prefix.Masked()
		_, ipNet, _ := net.ParseCIDR(prefix.String())
		route := netlink.Route{
			LinkIndex: tunIndex,
			Dst:       ipNet,
			Priority:  1,
		}
		if err := netlink.RouteAdd(&route); err != nil {
			_ = t.unsetSystemRoutes()
			return errors.New("failed to add system route ", cidr).Base(err)
		}
		t.systemRoutes = append(t.systemRoutes, route)
	}
	return nil
}

func (t *LinuxTun) unsetSystemRoutes() error {
	var errs []error
	for i := len(t.systemRoutes) - 1; i >= 0; i-- {
		route := t.systemRoutes[i]
		if err := netlink.RouteDel(&route); err != nil {
			errs = append(errs, errors.New("failed to delete system route").Base(err))
		}
	}
	t.systemRoutes = nil
	return errors.Combine(errs...)
}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Run with root/CAP_NET_ADMIN privileges
  2. Remove stale routes before start: `ip route del <cidr> dev <tun>` or flush the table
  3. Deduplicate overlapping CIDRs in autoSystemRoutingTable

Example fix

# before: stale route
ip route  # 0.0.0.0/1 dev tun0 metric 1 still present

# after
ip route del 0.0.0.0/1 dev tun0 2>/dev/null || true
# then start xray
Defensive patterns

Strategy: fallback

Validate before calling

// pre-flight: drop stale routes that xray will install
for _, cidr := range cfg.AutoSystemRoutingTable {
	_ = exec.Command("ip", "route", "del", cidr, "dev", tunName).Run()
}

Try / catch

if err := tun.Start(); err != nil {
	if strings.Contains(err.Error(), "failed to add system route") {
		// clean routes, then fall back to a single retry
	}
}

Prevention

When it happens

Trigger: Routing table already contains the same prefix pointing at the TUN (leftover from a crashed run); running unprivileged; overlapping more-specific route that kernel merges differently; duplicates inside autoSystemRoutingTable.

Common situations: Restart after an unclean shutdown that left routes behind; concurrent VPN clients fighting over default routes; containers missing NET_ADMIN; duplicating 0.0.0.0/0 twice.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/7c77bfc5408eb556. Report an issue: GitHub.