把静态路由配进去容易,让它重启后还在就完全是另一回事了。以前做华为ENSP实验的时候,“静态路由指向环回接口”、“ip route-static”敲完保存配置就万事大吉,路由乖乖躺在设备里。到了Ubuntu服务器上,很多人下意识地在终端里敲 route add -net 10.10.20.0/24 gw 192.168.1.254,看着 ip route 有这条路由了,以为大功告成,结果系统一重启,路由消失得无影无踪,业务直接断掉。这篇内容就来解决这个经典问题——Ubuntu永久网络静态路由配置。我会基于自己实际运维多台Ubuntu服务器的经验,从底层原理讲清楚路由为什么会丢,再按不同网络管理栈给出永久配置方案,最后附上几类排错实战,希望能帮你在Ubuntu上配路由时不再踩我趟过的坑。
1. 路由重启即丢的根本原因:Ubuntu网络配置栈在管什么
先别急着写配置,得先弄明白一个核心问题:你敲的 ip route add 到底把路由加到哪里去了?答案是Linux内核维护的路由表(routing table)。这个表是内存里的数据结构,每次系统启动时由网络管理服务根据配置文件重新构建,重启后必然清空。所以你想让路由"永久"存在,本质不是把命令写进某个开机脚本那么简单,而是要让你当前正在用的网络管理工具在初始化时读到你写的路由配置。
Ubuntu和CentOS有个很大的区别,就是网络配置工具更"分裂"。CentOS大多数版本老老实实用 ifcfg-* 文件,Ubuntu则横跨了好几代工具:老版本用 ifupdown(/etc/network/interfaces),18.04之后默认引入 netplan 作为统一配置入口,桌面版标配 NetworkManager,还有不少精简系统直接上 systemd-networkd。这些工具会在启动阶段分别接管网络接口、IP地址、DNS、路由表。你配置静态路由时,必须选对它对应的那套配置,否则写了也白写。
判断自己系统是哪一套,用这几条命令很快:
bash复制ls /etc/netplan/*.yaml
systemctl status NetworkManager --no-pager | head -5
systemctl status systemd-networkd --no-pager | head -5
cat /etc/network/interfaces
实际环境中,netplan和NetworkManager同时存在的概率很高,因为netplan本身只是个翻译层,它会根据配置里的 renderer: 字段把YAML翻译给 networkd 或 NetworkManager 执行。所以判断的时候要结合看:netplan配置文件里写的renderer是谁,谁就在真正管你的网卡。
我见过最典型的翻车场景是:桌面版Ubuntu用户照着网上的教程改 /etc/network/interfaces,改了半天发现网卡完全没反应;或者服务器版用户去找 /etc/NetworkManager/system-connections/,发现目录根本就不存在。所以第一步永远是定位工具,这一步错了,后面全废。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. netplan的routes字段:Ubuntu 18.04+服务器最常用的永久方案
如果你的系统带 /etc/netplan/*.yaml,那就走这一节。这是目前Ubuntu服务器配置静态路由的标准姿势,也是网上搜"Ubuntu 22.04配置网络"时最常见的解决方案,但很多人知道要改netplan,却不知道 routes 这个字段的正确写法和层级。
2.1 找到配置文件并理解routes该挂在哪个节点下
netplan的配置统一放在 /etc/netplan/ 目录,常见文件名有 00-installer-config.yaml、01-network-manager-all.yaml。系统启动时会按照文件名的字典序依次读取合并,同一个字段以最后一个文件为准。这个合并机制很关键,后面讲排错时还要用到。
先看最基本的配置结构:
yaml复制network:
version: 2
ethernets:
ens33:
dhcp4: true
在这份配置里,routes 是挂在具体接口(这里是ens33)下面的子键,不是顶层字段。很多新手把routes写在 ethernets 外面或者 ens33 的同一层级,netplan generate 就会报错。正确写法如下:
yaml复制network:
version: 2
ethernets:
ens33:
dhcp4: true
routes:
- to: 10.10.20.0/24
via: 192.168.1.254
这段配置的含义:去往 10.10.20.0/24 网段的数据包,统一交给 192.168.1.254 这个网关转发。to 是目的网段,via 是下一跳地址,它们是最核心的两个参数。注意 routes 是一个列表,列表项用 - 开头,所以可以在这个缩进下继续追加多条路由,形成一个数组。
2.2 routes条目的进阶参数与真实使用场景
除了 to 和 via,netplan的每条路由还支持几个实用参数,我在表格里列一下:
| 参数 | 作用 | 典型场景 |
|---|---|---|
to |
目标网络,可以是网段或default | 必备 |
via |
下一跳网关地址 | 必备 |
metric |
路由优先级,数值越小越优先 | 多网关、多链路冗余 |
on-link |
强制声明下一跳在同一链路,不检查网段 | 下一跳不在接口子网内但二层可达 |
table |
指定写入哪张路由表,不写默认main | 策略路由、多路由表复杂场景 |
以多路由和metric为例,假设你要同时让服务器能访问两个内网网段,一个走网关A,一个走网关B,配置就是这样:
yaml复制network:
version: 2
ethernets:
ens33:
addresses:
- 192.168.1.10/24
routes:
- to: 10.10.0.0/16
via: 192.168.1.254
- to: 172.16.0.0/12
via: 192.168.1.253
metric: 200
这样 10.10.0.0/16 走第一跳网关,172.16.0.0/12 走第二跳网关,后者还额外设置了 metric 200。metric不写时默认是100,数值小的优先。如果你在同一网段配了两条路由做冗余,一定要显式给出不同的metric,否则Linux的行为会变得不可预测。
2.3 netplan apply前后的验证三部曲
修改netplan配置后,我每次都以同样的流程验证,建议大家也照做:
先跑 sudo netplan generate。这个命令只做语法解析并生成底层配置,不会立即应用,也不会断网。如果YAML格式错了、字段名不对、缩进混乱,它会在这里报错,把问题拦截在生效之前,这是最安全的一道闸门。
然后跑 sudo netplan apply 让配置真正生效。这一步要提醒几句:如果你正在改的是管理网口的IP或默认网关,apply的瞬间SSH连接可能直接断掉。稳妥起见,要么用 sudo netplan try 代替,它会在指定时间内无操作自动回滚;要么确保你有带外管理通道(云控制台、IPMI)能随时上去恢复。
最后验证路由表和连通性:
bash复制ip route show
ping -c 4 10.10.20.1
ip route get 10.10.20.1
ip route show 用来确认路由条目是否在表里,ping 用来确认路由能真的转发业务流量,ip route get 10.10.20.1 则可以直观看到"去往这个IP的包实际走哪个接口、哪条路由"。这三个命令配合使用,能覆盖大部分验证场景。
3. NetworkManager管网的机器:nmcli与dispatcher两个方案
服务或桌面环境正在运行 NetworkManager 时,路由的持久化方式跟纯netplan又不一样。最常见的情况是你会发现 /etc/netplan/ 下的文件存在,但renderer字段标的是 NetworkManager,说明最终执行者是NM。这种状态下你照样可以在netplan里写routes,但NM自身的连接配置也可能参与路由管理,两者同时存在容易打架。我建议的做法是:既然NM在管,就完全用NM的方式去配,干净利落。
3.1 nmcli route追加:把路由写进连接配置文件
先查看当前有哪些网络连接:
bash复制nmcli connection show
默认有线连接的名称一般是 Wired connection 1,企业环境里可能会有自定义名称。用下面的命令给连接追加一条静态路由:
bash复制sudo nmcli connection modify "Wired connection 1" +ipv4.routes "10.10.20.0/24 192.168.1.254"
sudo nmcli connection up "Wired connection 1"
这里的加号 + 是追加路由的意思,不能省。我第一次用nmcli配路由时就是漏了加号,结果NM把该连接原有的路由配置整体覆盖了,业务网络瞬间瘫痪,折腾了半个小时才恢复。这一点值得刻在脑子里:+ipv4.routes 是追加,ipv4.routes 是覆盖。
执行 nmcli connection up 之后,路由会加载进内核,连接配置文件也会持久化保存,重启后NM会自动恢复这些路由。如果想一次追加多条路由,可以连着写:
bash复制sudo nmcli connection modify "Wired connection 1" +ipv4.routes "10.10.20.0/24 192.168.1.254" +ipv4.routes "172.16.30.0/24 192.168.1.253"
3.2 dispatcher脚本:多接口联动与复杂时序场景的兜底
nmcli 虽然好用,但有些场景它搞不定:一是路由需要严格依赖某个接口先up再添加;二是某些云平台或虚拟化环境里,连接配置会被自动刷新覆盖;三是你想根据接口上下线事件执行不同的路由操作。这时候NM的dispatcher脚本最合适。
在 /etc/NetworkManager/dispatcher.d/ 目录下创建一个可执行脚本,命名不要带任何扩展名:
bash复制#!/bin/bash
# /etc/NetworkManager/dispatcher.d/static-routes
if [ "$2" = "up" ]; then
ip route add 10.10.20.0/24 via 192.168.1.254 dev ens33
ip route add 172.16.30.0/24 via 192.168.1.253 dev ens33
fi
然后加执行权限:
bash复制sudo chmod +x /etc/NetworkManager/dispatcher.d/static-routes
脚本里的 $2 是NM传给dispatcher的事件参数,up 表示接口已启动。这样每当接口up时,脚本就会把路由重新加一遍。这种方案的好处是直白可控,缺点是绕过了NM的连接配置管理,路由不在NM的配置文件里,nmcli 里看不到。
我自己的经验是:能用 nmcli connection modify +ipv4.routes 解决的场景,绝不轻易上dispatcher脚本。只有遇到多接口联动、启动顺序依赖等特殊需求时才用后者,因为它本质上是手工管理,容易造成配置管理混乱。
4. 老版本与特殊环境:interfaces文件和systemd-networkd的配置法
不是所有Ubuntu都在跑netplan或NetworkManager。有些虚拟机模板、老系统、容器精简镜像直接裸跑 ifupdown 或 systemd-networkd,这些场景照样要配静态路由,方法不同但思路一致。
4.1 /etc/network/interfaces的up/down老写法
Ubuntu 16.04及更早版本,或者某些定制版系统,网络配置的核心文件是 /etc/network/interfaces。它的静态路由配置方法是在接口块里写 up 和 down 命令:
bash复制auto ens33
iface ens33 inet static
address 192.168.1.10
netmask 255.255.255.0
gateway 192.168.1.1
up route add -net 10.10.20.0/24 gw 192.168.1.254
down route del -net 10.10.20.0/24 gw 192.168.1.254
up 后面的命令在接口启动后执行,down 在接口停用前执行。这样路由就能跟随接口生命周期自动添加和清理。还有 post-up 和 pre-down 变体,语义更严格一点:post-up 确保接口完全就绪后再加路由,适合对时序敏感的场景。
修改完重启网络服务:
bash复制sudo systemctl restart networking
或者逐一重启接口:
bash复制sudo ifdown ens33 && sudo ifup ens33
这个写法的缺点是它跟ifupdown这套工具绑定,如果你改了 /etc/network/interfaces 但系统根本没在用ifupdown,任何修改都不会生效。判断方法很简单:执行 systemctl status networking,服务存在且active,说明ifupdown栈在用;如果提示 Unit networking.service not found,说明这套配置根本没被启用。
4.2 systemd-networkd里用[Route]段定义路由
systemd-networkd是systemd原生的网络管理器,netplan的networkd渲染器生成的就是它的配置。如果你直接管理networkd,配置文件在 /etc/systemd/network/ 目录下,一个接口一个 .network 文件:
ini复制# /etc/systemd/network/10-ens33.network
[Match]
Name=ens33
[Network]
Address=192.168.1.10/24
Gateway=192.168.1.1
[Route]
Destination=10.10.20.0/24
Gateway=192.168.1.254
[Route] 段就是静态路由的声明,支持写多个,也可以拆成多个文件。写完后重启networkd服务:
bash复制sudo systemctl restart systemd-networkd
ip route
注意 [Match] 段里的 Name= 支持通配符,比如 Name=eth*,但如果接口名匹配不上,整个文件会被静默忽略。还有一种常见错误是忘记写 [Match] 或写错接口名,导致networkd根本不知道这个文件该应用到哪块网卡上。配完务必执行 ip route 确认。
如果要用systemd-networkd直接操作的是 [Route] 里的 Metric= 字段,写法和netplan的metric类似:
ini复制[Route]
Destination=10.0.0.0/8
Gateway=192.168.2.254
Metric=200
5. 多网关场景:metric优先级与路由冲突的实战验证
静态路由的配置本身不难,难的是多接口、多网关混合在一起时,路由选路行为变得复杂。真实生产环境里,一台Ubuntu服务器往往有两个网口,一个接业务网段,一个接管理内网,或者一个连主链路、一个连备份链路,这时候路由表能不能按你的预期工作,就是经验的分水岭。
5.1 一个常见的双网卡双网关配置实例
假设服务器有两张网卡,ens33接业务网,ens34接数据中心管理网:
- ens33:192.168.1.10/24,网关192.168.1.1,承载默认出网流量
- ens34:10.10.0.10/24,网关10.10.0.1,用来访问管理网172.16.0.0/12
netplan的配置应该是:
yaml复制network:
version: 2
ethernets:
ens33:
addresses:
- 192.168.1.10/24
routes:
- to: default
via: 192.168.1.1
ens34:
addresses:
- 10.10.0.10/24
routes:
- to: 172.16.0.0/12
via: 10.10.0.1
这种情况下,业务网卡只管默认路由,管理网卡只负责访问172.16.0.0/12,两个接口的路由目标不重叠,互不干扰,是最常见的双网卡接入方式。
但实际环境经常不按理想来。比如两个网关都声明自己"能到达"10.0.0.0/8,这时候必须在两条路由上设置不同的metric,让系统明确知道谁优先,否则内核选路可能随机、甚至只保留其中一条:
yaml复制 routes:
- to: 10.0.0.0/8
via: 192.168.1.1
metric: 100
- to: 10.0.0.0/8
via: 192.168.2.254
metric: 200
metric值越小越优先,100的为主链路,200为备份链路。主链路活着时所有去往10.0.0.0/8的流量都走192.168.1.1;主链路断了,内核会自动切换到metric值更大的那条备份路由。这就是最常见的双链路冗余模型。
5.2 用ip route get验证实际选路而非只看表
多路由配置光看 ip route show 不够,因为表里躺着两条甚至更多条路由,不代表每条都被真正用到。我最常用的验证命令是 ip route get,它直接计算出"去往某个目标IP的包实际走哪条路径"。
bash复制$ ip route get 10.100.50.8
10.100.50.8 via 192.168.1.1 dev ens33 src 192.168.1.10 metric 100
输出里 via 和 dev 指明了实际走的网关和出接口,一目了然。如果结果显示走了备份链路而你期望走主链路,就得检查metric配置和接口状态了。在切换测试时,我习惯临时用 ip route add ... metric 50 插一条高优先级路由先验证连通性,测通后再写进配置文件。这种临时路由不影响持久配置,测完 ip route del 删掉就好。
需要特别提醒的是,在某些版本里netplan会把同网段不同metric的两条路由合并或只生效一条。遇到这种情况,把metric差值拉大(比如100和300)再 netplan apply 一次,然后立刻 ip route show 检查两条是否都在。
6. 配置不生效的排错复盘与避坑清单
静态路由配完不生效,九成集中在下面几类原因。这些坑都是我一个个踩出来的,给你梳理成清单,遇到问题直接对照排查。
6.1 YAML缩进与节点层级错误
netplan对YAML缩进极其敏感,而 routes 又特别容易放错层级。最常见的错误是写在 ethernets 外面:
yaml复制network:
version: 2
ethernets:
ens33:
dhcp4: true
routes: # 错误:routes不在这个层级
- to: 10.10.20.0/24
via: 192.168.1.254
这种写法的直接结果是 netplan generate 时报"unknown key routes"。把routes缩进到 ens33: 下面,问题立刻解决。养成每次改完先 generate 再 apply 的习惯,能从源头拦截大部分语法错误。
6.2 接口名对不上导致配置被静默丢弃
服务器在更换网卡、更新驱动或启动顺序变化后,接口名可能从ens33变成enp3s0,netplan配置里的接口名对不上,整个接口配置包括路由都会被忽略,而且没有任何报错。排查时先执行 ip link show 看清当前接口名,确认与配置文件里的名字一致。多网卡服务器我更推荐用MAC地址匹配,这样接口名怎么变都不怕:
yaml复制network:
version: 2
ethernets:
nic-public:
match:
macaddress: "00:0c:29:xx:yy:zz"
addresses:
- 192.168.1.10/24
用 match.macaddress 加一个自定义名称(比如nic-public),接口名变动时配置依然有效。这种方式在网卡增删频繁的虚拟化环境里尤其重要。
6.3 网关不在同一子网:on-link参数的用处
有些路由的下一跳地址和接口所在的网段对不上,比如服务器网卡是192.168.1.10/24,下一跳网关却是10.10.0.1。Linux默认会做直连判断,认为这个网关不可达,拒绝加入路由。如果你确定这个网关在二层确实是可达的(比如经过某种internal bridge),就用 on-link: true 强制声明:
yaml复制routes:
- to: 192.168.2.0/24
via: 10.10.0.1
on-link: true
on-link 的核心作用是让内核跳过网段前缀判断,直接在该接口上通过ARP去解析下一跳。这个参数能让很多奇怪的组网跑通,但不要滥用。同一子网内不需要它,写上去反而可能引入其他问题。
6.4 多份netplan文件互相覆盖
netplan会按文件名顺序合并所有yaml文件,后面文件覆盖前面文件。假如你在 00-installer-config.yaml 里配了静态路由,但系统同时存在一个 99-cloud-init.yaml,里面定义了同一个接口且没有你的路由,你的配置大概率被覆盖了。想确认合并后的最终配置,直接运行:
bash复制sudo netplan get all
这个命令会输出所有配置合并后的完整结果,一眼就能看出该接口的最终定义来自哪个文件、有没有包含你的路由条目。发现问题后,要么把路由统一归到同一个文件里,要么移除冲突的配置文件。
6.5 重启丢路由但apply就好的特殊原因
还有一种比较隐蔽的坑:netplan apply 后路由立即生效,但重启后又没了。这种情况往往跟启动顺序有关——路由配置在接口尚未完全就绪时就被尝试添加,结果失败。排查步骤是:
bash复制sudo systemctl status systemd-networkd
sudo journalctl -u systemd-networkd -n 50
看networkd启动日志里有没有路由相关错误,尤其是 RTNETLINK answers: Network is unreachable 这类提示。如果是NetworkManager环境,看NM日志:
bash复制journalctl -u NetworkManager -n 50
如果确实是启动顺序问题,解决方案是在netplan服务上增加依赖声明,或者干脆用NetworkManager dispatcher脚本在接口up后再补齐路由。核心思路只有一个:确保路由的添加时机一定晚于网络接口完全就绪。
最后再分享一个我实际工作中的小习惯:所有静态路由配置改完,我都会把命令和配置文件内容记一笔到自己的运维笔记里,标注上修改时间、目的网段、下一跳、涉及接口这四要素。折腾过几次"三个月前配的这条路由现在到底该不该删"的场景之后,你会发现这条习惯比任何技巧都值钱。永久路由这件事本身不难,难的是搞清楚你的系统由谁管网络,把配置写对地方,然后留好审计记录。希望这篇笔记能帮你少走几个弯路。
