2023 年,一台服务器的网络问题让我对着屏幕坐了一下午。现象很简单,也很怪:这台机器,能 ping 通它自己的网关,能 ping 通同一个局域网里的别的机器,可它就是【上不了外网】——ping 一个外网 IP,全部超时;curl 任何外网地址,全部卡死。我第一反应是 DNS 出问题了,可我 ping 的是裸 IP,根本不经过 DNS。我又怀疑是防火墙,把 iptables、firewalld 翻了个底朝天,规则干干净净,没有一条挡外网的。我甚至怀疑是机房的物理线路,可同机房同网段的别的机器,外网好得很。问题就死死地卡在这台机器上:它和"近处"的世界通信完全正常,网卡是好的、IP 是对的、线是通的;可一旦目标是"远处"的外网,它就像突然变成了哑巴,发出去的包石沉大海。能通近的、不通远的——这中间一定有一个东西,在决定"一个网络包,该往哪儿走"。近处的包,它知道往哪走;远处的包,它好像【不知道该把包交给谁】。我盯着那个超时的 ping,第一次开始认真想:一台机器,它到底是【凭什么】知道,一个发给外网的包,下一步该递到哪只手上的?这件事逼着我把路由表、默认路由、网关、选路规则这一整套彻底理清了。本文复盘这次实战。
问题背景
环境:CentOS 7,内网服务器,单网卡
事故现象:
- ping 网关:通
- ping 同网段其它机器:通
- ★ ping 外网 IP(如 8.8.8.8):全部超时
- ★ curl 任何外网地址:全部卡死
- 防火墙规则干净,DNS 不影响(ping 的是裸 IP)
现场排查:
# 1. 分层 ping,定位"从哪一跳开始不通"
$ ping -c2 192.168.1.1 # 网关
64 bytes from 192.168.1.1 ... # ★ 通
$ ping -c2 192.168.1.50 # 同网段另一台机器
64 bytes from 192.168.1.50 ... # ★ 通
$ ping -c2 8.8.8.8 # 外网
(全部超时,0 received) # ★ 不通
# 2. ★ 看这台机器的路由表
$ ip route
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.20
# ★ 就这一行!只有一条。
# ★ 它的意思:发往 192.168.1.0/24 这个网段的包,
# 从 eth0 直接送出去。
# ★ 注意:这里【没有】default 那一行!
# 3. 对比一台正常机器的路由表
$ ssh 正常机器 ip route
default via 192.168.1.1 dev eth0 # ★ 它有这一行!
192.168.1.0/24 dev eth0 proto kernel scope link src ...
# 4. ★ 验证:手动加上默认路由试试
$ ip route add default via 192.168.1.1
$ ping -c2 8.8.8.8
64 bytes from 8.8.8.8 ... # ★ 立刻就通了!
# 5. 看为什么开机后这条路由没了
$ cat /etc/sysconfig/network-scripts/ifcfg-eth0
...
# GATEWAY=192.168.1.1 # ★ 网关这行被注释掉了
# ★ 配置文件里没配网关 -> 开机自然不会生成默认路由
根因(后来想清楚的):
1. ★ 一台机器知道"一个包该往哪走",靠的是
【路由表】。路由表里每一条,管一个目的地范围。
2. ★ 这台机器的路由表里,只有一条:192.168.1.0/24
走 eth0 直连。所以发给【这个网段内】的包,
它知道怎么走 —— 这就是"ping 网关、ping 同
网段都通"的原因。
3. ★ 但发给【这个网段之外】的包(比如 8.8.8.8),
路由表里【没有任何一条】能匹配上它。
4. ★ 一个包,如果路由表里找不到任何一条规则
匹配它,内核就【不知道该把它交给谁】——
这个包根本发不出去(Network is unreachable)。
5. ★ "兜底"的那条规则,叫【默认路由(default)】,
意思是"所有没被前面规则匹配上的包,统统交给
这个网关"。这台机器,恰恰【缺了这条兜底】。
6. 缺的原因:ifcfg-eth0 里 GATEWAY 那行被注释了,
开机时就没人去生成那条 default 路由。
不是网络坏了,是这台机器的"路由表"里,缺了那条
"其它的包都交给网关"的兜底规则。
修复 1:路由表是什么——每个包的"路标"
# === ★ 先看清:机器靠什么决定"包往哪走" ===
# === ★ 路由表:一张"目的地 -> 下一步去哪"的表 ===
# 每台机器,内核里都有一张【路由表】。每当要发出
# 一个网络包,内核就拿这个包的【目的 IP】,去
# 路由表里【查】:这个目的地,我该把包从哪个
# 网卡送出去?要不要先交给某个网关?
# ★ 没有路由表,机器就是个"不认路的人" —— 手里
# 攥着信,却不知道往哪儿寄。
# === ★ 看路由表 ===
$ ip route
default via 192.168.1.1 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.20
# ★ 现代 Linux 用 ip route 看。老命令 route -n 也行:
$ route -n
Destination Gateway Genmask Flags Iface
0.0.0.0 192.168.1.1 0.0.0.0 UG eth0
192.168.1.0 0.0.0.0 255.255.255.0 U eth0
# === ★ 逐行读懂一条路由 ===
# 192.168.1.0/24 dev eth0 ... src 192.168.1.20
# ★ 192.168.1.0/24 :这条规则【管的目的地范围】——
# 所有 IP 在 192.168.1.0 ~ 192.168.1.255 的包。
# ★ dev eth0 :匹配上的包,从 eth0 这块网卡送。
# ★ scope link :这个网段是【直连】的 —— 不用
# 经过任何网关,网卡直接就能送到。
# ★ src 192.168.1.20:用这个源 IP 发出去。
# === ★ 两种路由:直连 vs 经网关 ===
# 1. ★ 直连路由(scope link):目的地就在"隔壁",
# 本网卡所在的网段,包直接送达,不需要中间人。
# 例:192.168.1.0/24 dev eth0
# 2. ★ 网关路由(via X):目的地在"远方",本机
# 够不着,必须把包先【交给一个网关】X,由它
# 转发出去。例:default via 192.168.1.1
# ★ "via" 这个词,就是"经由某个网关"的意思。
# === ★ 把路由表想成门口的指路牌 ===
# 一个人(包)要出门,门口立着几块牌子:
# - "去 192.168.1.x 的:直走,就在本院里"
# - "去其它任何地方的:都先到门卫(网关)那报到"
# ★ 机器发包,就是在门口照着这些牌子做决定。
# 牌子缺了哪一块,对应方向的人就【出不去】。
# === 认知 ===
# ★ 路由表是机器的"路标系统",每一条规则管一个
# 目的地范围,告诉内核"发往那里的包,该从哪个
# 网卡走、要不要先交给网关"。机器认不认得路,
# 全看这张表 —— 表里缺一条,就有一片地方去不了。
修复 2:默认路由——那条"其它都走这"的兜底
# === ★ default:路由表里最关键的"兜底规则" ===
# === ★ 默认路由是什么 ===
$ ip route | grep default
default via 192.168.1.1 dev eth0
# ★ 这一行里的 default,等价于网段写法 0.0.0.0/0。
# 0.0.0.0/0 的含义是:★【匹配所有 IP 地址】。
# ★ 所以这条规则的意思就是:"任何目的地的包,
# 都交给网关 192.168.1.1"。
# === ★ 它为什么叫"默认"/"兜底" ===
# 路由表里,具体的网段规则(如 192.168.1.0/24)
# 优先级更高,会先被匹配。
# ★ default(0.0.0.0/0)是【最宽、最不挑】的规则,
# 它排在最后。一个包,如果前面所有具体规则都
# 没匹配上,就【落到】default 这条 —— 由它兜底。
# ★ 它就是那块写着"其它所有方向,一律走门卫"的牌子。
# === ★ 没有默认路由,会发生什么(本文的病)===
$ ip route
192.168.1.0/24 dev eth0 ... # ★ 只有这一条
$ ping 8.8.8.8
connect: Network is unreachable # ★ 网络不可达!
# ★ 8.8.8.8 不在 192.168.1.0/24 里 -> 唯一那条
# 规则匹配不上 -> 又没有 default 兜底 -> 内核
# 找不到任何一条路 -> 直接判"这包没法发"。
# ★ 注意这个报错 "Network is unreachable" —— 它
# 不是"对方不回应",是"我根本不知道往哪发"。
# === ★ 有默认路由,外网包就有地方去了 ===
$ ip route add default via 192.168.1.1
$ ping 8.8.8.8
64 bytes from 8.8.8.8 ... # ★ 通了
# ★ 加上 default 后:8.8.8.8 -> 不匹配 192.168.1.0/24
# -> 落到 default -> 包交给网关 192.168.1.1
# -> 网关负责把它转发到外网。
# === ★ "网关"到底是什么角色 ===
# 网关(gateway),就是你这个局域网通往"外面"的
# 那道门。本机够不着的目的地,就把包丢给网关,
# 由网关(通常是路由器)继续往外送。
# ★ 默认路由 = "把够不着的一切,都委托给网关"。
# ★ 前提:网关地址必须是【和本机同网段、能直连】
# 的 —— 你总得先够得着门卫,才能托他办事。
# === ★ 临时加 vs 永久加默认路由 ===
$ ip route add default via 192.168.1.1 # ★ 临时,重启失效
# ★ 永久:写进网卡配置文件(下一节的解法细讲)。
# === 认知 ===
# ★ 默认路由 default(0.0.0.0/0)是路由表的兜底规则:
# 凡是没被具体网段规则匹配上的包,统统交给网关。
# 没有它,机器就只能在自己的直连网段里打转,
# 一步也迈不出去 —— 本文外网不通,就缺这一条。
修复 3:一个包是怎么"选路"的——最长前缀匹配
# === ★ 路由表有多条规则时,内核按什么挑 ===
# === ★ 一张稍复杂的路由表 ===
$ ip route
default via 192.168.1.1 dev eth0
10.0.0.0/8 via 192.168.1.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.20
# ★ 三条规则。现在发一个包,内核怎么决定走哪条?
# === ★ 规则:最长前缀匹配(longest prefix match) ===
# 内核拿目的 IP,去和每条路由的"网段"比对,
# ★ 在【所有能匹配上的】规则里,选那个
# 【网段最具体(前缀最长 /xx 数字最大)】的。
# "前缀越长" = "范围越小、越精确"。
# === ★ 三个例子,走一遍选路过程 ===
# 例 1:目的 192.168.1.50
# - 192.168.1.0/24 ✓ 匹配(前缀 /24)
# - default 0.0.0.0/0 ✓ 也匹配(前缀 /0)
# ★ 两个都匹配,选前缀最长的 /24 -> 走直连。
#
# 例 2:目的 10.1.2.3
# - 10.0.0.0/8 ✓ 匹配(前缀 /8)
# - default /0 ✓ 也匹配
# ★ 选 /8 -> 交给网关 192.168.1.254。
#
# 例 3:目的 8.8.8.8
# - 192.168.1.0/24 ✗、10.0.0.0/8 ✗ 都不匹配
# - 只有 default /0 ✓
# ★ 只能落到 default -> 交给 192.168.1.1。
# === ★ 所以 default 是"最后的兜底",名副其实 ===
# default 的前缀是 /0 —— 全世界最短的前缀,范围
# 最大、最不精确。所以它永远【最后】才被选中:
# 只有当一个包,把所有具体规则都试过、全不匹配,
# 才会落到它头上。这正是"兜底"二字的由来。
# === ★ 查"一个特定目的地,内核会怎么走" ===
$ ip route get 8.8.8.8
8.8.8.8 via 192.168.1.1 dev eth0 src 192.168.1.20
# ★ ip route get 是排查选路的利器 —— 它直接告诉
# 你"发往这个 IP,内核【实际】会选哪条路、
# 交给哪个网关、从哪块网卡走"。
$ ip route get 192.168.1.50
192.168.1.50 dev eth0 src 192.168.1.20 # ★ 直连,没有 via
# ★ 出问题时,先 ip route get 目标IP,一眼看清
# 内核到底打算把这个包往哪送。
# === ★ 多网卡时还有个 metric(优先级) ===
$ ip route
default via 192.168.1.1 dev eth0 metric 100
default via 192.168.2.1 dev eth1 metric 200
# ★ 如果有【两条一样网段】的路由(常见于多网卡),
# 内核选 metric【小】的那条。metric = 优先级,
# 数字越小越优先。多网卡选错网关,常常是 metric
# 没配对。
# === 认知 ===
# ★ 包选路的规则是"最长前缀匹配":在所有匹配的
# 路由里,挑网段最具体的那条。default(/0)范围
# 最大,永远垫底。排查选路,ip route get 目标IP
# 是最快的一招 —— 它直接给你答案。
修复 4:让默认路由开机就在——别只临时加
# === ★ 临时加的路由,重启就没 —— 必须落到配置 ===
# === ★ 现象:ip route add 加的路由,重启后消失 ===
$ ip route add default via 192.168.1.1 # 临时加上,现在通了
# ... 重启机器 ...
$ ip route | grep default
(空) # ★ 又没了!
# ★ ip route add / ip addr add 这类命令,都是
# 【只改当前运行状态】,不写任何文件。一重启,
# 系统重新按【配置文件】初始化网络 —— 临时加的
# 东西,荡然无存。
# === ★ 根治:把网关写进网卡配置文件 ===
# CentOS 7 / RHEL 系,网卡配置在:
$ vim /etc/sysconfig/network-scripts/ifcfg-eth0
# ★ 确保有这一行(本文的病就是它被注释了):
GATEWAY=192.168.1.1
# 完整的一个静态 IP 配置长这样:
TYPE=Ethernet
BOOTPROTO=static
DEVICE=eth0
ONBOOT=yes
IPADDR=192.168.1.20
NETMASK=255.255.255.0
GATEWAY=192.168.1.1 # ★ 这一行 -> 开机生成 default 路由
DNS1=192.168.1.1
# === ★ 改完,重启网络让它生效 ===
$ systemctl restart network # CentOS 7
# 或针对单个网卡:
$ ifdown eth0 && ifup eth0
$ ip route | grep default
default via 192.168.1.1 dev eth0 # ★ 开机就有了
# === ★ Ubuntu / Debian 新系统:netplan ===
# 较新的系统用 netplan,配置在 /etc/netplan/*.yaml:
# routes:
# - to: default
# via: 192.168.1.1
$ netplan apply # 应用配置
# === ★ 用 nmcli 配(NetworkManager 管理的系统)===
$ nmcli connection modify eth0 ipv4.gateway 192.168.1.1
$ nmcli connection up eth0
# ★ 不管用哪种,核心都一样:把网关写进【持久配置】,
# 而不是只用 ip route add 临时加。
# === ★ 顺手检查:别配了多个默认路由 ===
$ ip route | grep -c default
1 # ★ 正常应该是 1 条
# ★ 如果出来 2 条甚至更多(多网卡各配了网关),
# 内核会按 metric 选,容易选到错的那个,导致
# "时通时不通"。多网卡环境,要么只留一个默认
# 网关,要么用 metric 明确分出主次。
# === 认知 ===
# ★ ip route add 只改运行时状态,重启即失效。要让
# 默认路由永久存在,必须把 GATEWAY 写进网卡的
# 持久配置文件(ifcfg / netplan / nmcli),再重启
# 网络验证。本文的根因,就是这一行被注释掉了。
修复 5:路由问题的完整排查解法
# === ★ 一套"网络不通"的标准排查流程 ===
# === ★ 第一步:分层 ping,定位"从哪一跳断" ===
$ ping -c2 127.0.0.1 # ① 本机环回 —— 测 TCP/IP 协议栈
$ ping -c2 192.168.1.20 # ② 本机 IP —— 测本网卡
$ ping -c2 192.168.1.1 # ③ 网关 —— 测到网关这一跳
$ ping -c2 192.168.1.50 # ④ 同网段他机 —— 测本地网络
$ ping -c2 8.8.8.8 # ⑤ 外网 IP —— 测出网关之后
# ★ 哪一步开始不通,问题就在那一层。本文是 ③④ 通、
# ⑤ 不通 —— 问题恰好卡在"出了本网段"这个边界,
# 矛头直指【默认路由 / 网关】。
# === ★ 第二步:看路由表,找 default ===
$ ip route
$ ip route | grep default
# ★ 没有 default 那一行 -> 根因锁定:缺默认路由。
# === ★ 第三步:ip route get 看内核实际怎么选路 ===
$ ip route get 8.8.8.8
# ★ 报 "Network is unreachable" -> 确实没路可走。
# 能给出 via X -> 路由是有的,问题在更后面。
# === ★ 第四步:确认网关本身够得着 ===
$ ping -c2 192.168.1.1
# ★ 网关必须能 ping 通。连网关都不通,加默认路由
# 也没用 —— 先解决"够不着门卫"的问题。
$ ip route get 192.168.1.1
192.168.1.1 dev eth0 ... # ★ 应是直连(同网段)
# === ★ 第五步:临时加路由验证猜想 ===
$ ip route add default via 192.168.1.1
$ ping -c2 8.8.8.8 # ★ 通了 -> 根因确认就是它
# ★ 先临时加、验证通了,再去改配置文件根治 ——
# 这样不会"改了半天配置发现根因猜错了"。
# === ★ 第六步:根治 + 验证 ===
$ vim /etc/sysconfig/network-scripts/ifcfg-eth0 # 加 GATEWAY=
$ systemctl restart network
$ ip route | grep default # ★ 确认还在
# === ★ 进阶:traceroute 看包到底走到哪一跳断 ===
$ traceroute 8.8.8.8
1 192.168.1.1 1ms # 到网关
2 * * * # ★ 从这跳开始没回应
# ★ traceroute 能看清包在哪一跳"走丢",
# 区分"本机路由问题"和"上游网络问题"。
# === 验证 ===
$ ip route | grep default # ★ 默认路由在
$ ip route get 8.8.8.8 # ★ 能给出 via 网关
$ ping -c2 8.8.8.8 # ★ 外网通
$ curl -I http://example.com # ★ 业务层也通
# ★ 路由表有 default + ping 外网通 + 重启后仍在 ——
# 才算真修好。
口诀放进脑子:网络不通先分层 ping 定位断在哪一跳,出本网段不通就查 ip route 有没有 default。
修复 6:路由与网络连通性排查纪律
# === 这次事故暴露的认知盲区,定几条纪律 ===
# === 1. ★ 机器靠路由表决定每个包往哪走,表里缺一条就有一片地方去不了 ===
# === 2. ★ 网络不通先分层 ping:环回-本机-网关-同网段-外网,断在哪层问题在哪层 ===
# === 3. ★ "通近不通远" 几乎总是默认路由/网关问题 ===
# === 4. ip route 看路由表,必须有 default via 网关 那一行 ===
$ ip route | grep default
# === 5. ★ 默认路由 default 即 0.0.0.0/0,是"没被具体规则匹配的包都交给网关"的兜底 ===
# === 6. 包选路是最长前缀匹配,default(/0)范围最大永远垫底 ===
# === 7. ★ ip route get 目标IP 直接告诉你内核实际会怎么选路,排查选路第一招 ===
$ ip route get 8.8.8.8
# === 8. ★ ip route add 只改运行时,重启就没,根治要写进网卡配置文件 GATEWAY= ===
# === 9. 多网卡别配多个默认路由,要么只留一个,要么用 metric 分主次 ===
# === 10. 排查"能通内网上不了外网"的步骤链 ===
$ ping 网关 / ping 同网段 / ping 外网IP # ① 分层定位断点
$ ip route | grep default # ② 有没有默认路由
$ ip route get 外网IP # ③ 内核实际怎么选路
$ ip route add default via 网关 # ④ 临时加,验证根因
$ 网卡配置文件写 GATEWAY= 再重启网络 # ⑤ 根治
# 按这个顺序,"内网通外网不通"基本能定位、能根治。
命令速查
需求 命令
=============================================================
看路由表 ip route / route -n
只看默认路由 ip route | grep default
看内核对某 IP 怎么选路 ip route get 目标IP
临时加默认路由 ip route add default via 网关
临时删默认路由 ip route del default
临时加一条网段路由 ip route add 网段/掩码 via 网关
看网卡 IP ip addr
分层测连通性 ping 环回/本机/网关/外网
看包走到哪一跳断 traceroute 目标IP
网卡配置文件(CentOS7) /etc/sysconfig/network-scripts/ifcfg-eth0
重启网络 systemctl restart network
口诀:网络不通先分层 ping 定位断在哪一跳
出本网段不通就查 ip route 有没有 default,加了验证再写进配置文件
避坑清单
- 机器靠路由表决定每个网络包往哪走,路由表里每条规则管一个目的地范围,缺一条就有一片去不了
- 网络不通先分层 ping,按环回到本机到网关到同网段到外网的顺序,断在哪层问题就在哪层
- 能通内网通不了外网这种"通近不通远",几乎总是缺默认路由或网关配错
- 路由有直连和经网关两种,scope link 是直连,via 网关 是要先把包交给中间人转发
- 默认路由 default 就是 0.0.0.0/0,意思是没被具体规则匹配的包统统交给网关兜底
- 没有默认路由时发往外网的包会报 Network is unreachable,这是"不知道往哪发"不是"对方不回"
- 包选路按最长前缀匹配,在所有匹配的路由里选网段最具体的,default 的 /0 范围最大永远垫底
- ip route get 目标IP 直接告诉你内核实际会怎么选路走哪个网关,是排查选路最快的一招
- ip route add 只改运行时状态重启就没,根治必须把 GATEWAY 写进网卡持久配置文件
- 多网卡别同时配多个默认路由,要么只留一个网关,要么用 metric 数字小的优先来分主次
总结
这次"能通内网、上不了外网"的事故,纠正了我一个关于"连通"的、过于笼统的想象。在我出问题之前的认知里,一台机器的网络,是一个【整体性】的开关:它要么"通",要么"不通"。网卡亮着、IP 配着、线插着、能 ping 通隔壁机器——这一切都告诉我"网络是好的"。所以当外网 ping 不通时,我下意识地把它当成一个矛盾:一个"网络是好的"的机器,怎么会"网络不通"?我于是去怀疑那些会让网络【整体性】崩坏的东西——防火墙在拦、DNS 挂了、物理线路断了。我反复地查这些"全局开关",因为在我的模型里,网络的好坏就是一个全局的状态。可现场把这个"整体开关"的模型,彻底打碎了。这台机器的网络,根本不是"好"或"坏"这么一个简单的二元状态。它是好的——对近处而言;它又是坏的——对远处而言。同一台机器、同一块网卡、同一根线,在同一个瞬间,既"通"又"不通"。连通性,原来根本不是一个开关,它是一张【地图】:机器对世界上的每一个去处,是分别地、一个一个地"知道"或"不知道"怎么走的。它对 192.168.1.x 这一片了如指掌,因为它的路标上明明白白写着这条;它对 8.8.8.8 一无所知,因为没有任何一块路标提到过那个方向。复盘到根上,我才明白,我一直以为的那个"网络是好的",其实只是"通往近处的那几条路是好的"。我把"我能到达的那部分世界",误当成了"整个世界都能到达"。而真正的破绽,藏在一个我从没去看过的地方——那张路由表里,缺了一块本该兜底的路标。那块路标不写具体地名,它只写一句话:"凡是我没单独列出来的地方,统统从这个门出去。"正是这块"兜底"路标的缺失,让这台机器变成了一个"只认得家门口几条街、出了街区就彻底失明"的人。它不是瞎了,它只是从来没有人告诉过它:"陌生的地方,该往哪个方向迈第一步。"这次最大的收获,是我学会了用"地图"而不是"开关"去理解连通性——不只是网络的连通性。一个系统能不能访问某个依赖,一个服务能不能调用到另一个服务,甚至一个人能不能触及某个资源——这些"通不通",几乎从来都不是一个整体的开关,而是一张由【一条条具体的、分目的地的规则】拼出来的地图。它在某个方向上通,不代表它在所有方向上都通;它在大多数方向上通,也完全可能在某一个特定方向上,缺着那条唯一的路。所以下一次,当某个东西表现出"一部分能、一部分不能"的怪象时,我不会再把它当成"那它到底是好是坏"的矛盾去纠结。我会立刻意识到:这正是"地图"的特征——一定是这张地图上,通往那个"不能"的方向,缺了一条规则。我要做的,不是去拨弄那个根本不存在的"总开关",而是把这张地图摊开,一条一条地看:去往出问题的那个方向的【那一条具体的路】,到底还在不在。很多"时灵时不灵"的困局,都不是因为整个系统坏了,而是因为我们一直盯着那个想象中的总开关,却从没低头看一眼,脚下那张真实的地图上,是不是恰好缺了通往那个方向的、那唯一的一笔。
—— 别看了 · 2026