发布时间:2026-09-21 04:31 更新时间:2026-09-21 04:31 阅读量:0
在 Linux 服务器上排查网络问题时,很多人遇到过这种场景:明明把 /etc/resolv.conf 里的 nameserver 改成了内网 DNS,ping 域名还是解析到旧地址;或者过了几分钟,文件又自己变回去了。多数情况下,这背后是 systemd-resolved 在接管 DNS,而 /etc/resolv.conf 只是一个被它管理的入口。本文把两者的关系、优先级和修改方法讲清楚,让你在服务器上改 DNS 时知道改哪里、改完怎么验证。
传统 Linux 上,glibc 的域名解析函数(如 getaddrinfo)会读取 /etc/resolv.conf,从中拿到 nameserver 列表、search 域和 options。这个约定多年没变,所以大家习惯“改 resolv.conf 就是改 DNS”。
但引入了 systemd 的发行版(Debian/Ubuntu、CentOS/RHEL 较新版本等)通常默认启用 systemd-resolved 服务。它本质上是一个本地 DNS 缓存与转发器,监听 127.0.0.53:53。安装后,/etc/resolv.conf 往往被替换成一个软链接,指向 /run/systemd/resolve/stub-resolv.conf,里面只有一行 nameserver 127.0.0.53。也就是说:
你的程序查域名 → 走 glibc → 读 resolv.conf → 连到 127.0.0.53 → 由 systemd-resolved 按它自己记录的“上游 DNS”再去查询。真正的上游 DNS 不在你手改的那个文件里,而在 systemd-resolved 的配置和网络管理器的记录里。这就是改了不生效、还会被覆盖的原因。
ls -l /etc/resolv.conf
cat /etc/resolv.conf
resolvectl status
systemctl status systemd-resolved --no-pager
如果 ls -l 显示 resolv.conf 是指向 /run/systemd/resolve/ 的软链接,基本可以确认 systemd-resolved 在接管。也可以看 resolvectl status 输出里每个网卡的“DNS Servers”和“Current DNS Server”,这才是当前真正使用的上游 DNS。具体字段以本机实际输出为准。
一台服务器上,DNS 相关配置可能来自好几个地方,优先级大致如下(不同发行版与网络管理方式会有差异,具体以官方文档与实际环境为准):
第一层是 systemd-resolved 的运行时状态,由 networkd、NetworkManager 或 DHCP 下发的 DNS 写入,resolvectl status 看到的就是它。第二层是 /etc/systemd/resolved.conf,用于设置全局 DNS、DNSSEC、缓存等兜底项。第三层才是 /etc/resolv.conf,它可能被软链接接管,也可能被 NetworkManager 的 dispatcher 重写。第四层是应用自己带的解析配置,例如容器里的 resolv.conf、Nginx 的 resolver 指令、Java 的 JVM DNS 缓存等。
排查时建议按“先看当前生效,再找来源”的顺序:
# 看当前实际使用的 DNS 与每个网卡的解析状态
resolvectl status
看某张网卡上解析走得通不通
resolvectl query example.com
看域名解析路径与耗时
resolvectl statistics
如果没装 resolvectl,可用 systemd-resolve --status(旧版本)
注意 resolvectl query 是直接问 systemd-resolved 的,结果和 ping、curl 可能一致也可能不一致:如果某个应用绕过 systemd-resolved 直接读 resolv.conf,那它看到的又是另一套。判断“到底走谁”,最直接的办法是查 /etc/nsswitch.conf 里 hosts 那一行,以及 resolv.conf 是否被链接接管。
做法一:继续用 systemd-resolved,改它的上游 DNS。这是推荐方式。编辑 /etc/systemd/resolved.conf,在 [Resolve] 段里写 DNS 和 FallbackDNS,然后重启服务。这样无论网卡怎么变,全局 DNS 都有兜底。
[Resolve]
DNS=10.0.0.2 10.0.0.3
FallbackDNS=223.5.5.5 1.1.1.1
Domains=~.
Cache=yes
systemctl restart systemd-resolved
resolvectl status
resolvectl query example.com
其中 Domains=~. 表示这条 DNS 作为全局默认路由域使用,写法与作用以 systemd-resolved 官方文档为准。改完如果 resolvectl status 里 Global 段的 DNS Servers 出现你写的地址,说明生效。
做法二:让 resolv.conf 变成普通文件,彻底绕开 systemd-resolved。适合你明确不需要本地缓存、希望自己完全掌控解析的场景。先停用服务,再删掉软链接并新建文件:
systemctl disable --now systemd-resolved
rm -f /etc/resolv.conf
cat > /etc/resolv.conf <<'EOF'
nameserver 10.0.0.2
nameserver 10.0.0.3
options timeout:2 attempts:2
EOF
chattr +i /etc/resolv.conf # 可选:防止被其他程序改写
删链接前请确认系统里的域名解析不依赖 systemd-resolved,否则可能影响 apt/yum 更新。用 chattr +i 锁文件虽然能防覆盖,但后续要改 DNS 时记得先 chattr -i,否则会报“Operation not permitted”。
做法三:保留 systemd-resolved,但让 resolv.conf 指向静态文件。把软链接改成指向 /run/systemd/resolve/resolv.conf 或 /etc/resolv.conf 自己的静态副本,可以兼顾缓存与手动控制。不同发行版对这个链接的默认处理不同,操作前建议先备份原链接。
遇到 DNS 解析和预期不一致,建议按这个顺序走一遍:先 ls -l /etc/resolv.conf 判断是否被接管;再 resolvectl status 看当前上游;然后用 resolvectl query 与 dig @10.0.0.2 example.com 分别验证本地缓存与上游 DNS 的结果;最后检查 /etc/nsswitch.conf、NetworkManager 或 netplan 配置里有没有下发的 DNS 覆盖了你手改的值。容器环境还要额外看容器内 resolv.conf 与 Docker 网络设置,这属于另一层,不在 systemd-resolved 管辖范围内。
一句话总结:新版 Linux 上 systemd-resolved 才是 DNS 的实际决策者,/etc/resolv.conf 多数时候只是它的一个展示窗口。要改 DNS,优先改 resolved.conf 或对应网卡配置;要彻底接管,就停用服务并恢复普通文件。改完别只看文件内容,用 resolvectl status 和实际域名查询确认一次,才算真正生效。
| 📑 | 📅 |
|---|---|
| MySQL索引失效排查:EXPLAIN执行计划与隐式转换定位 | 2026-09-21 |
| Docker 容器健康检查实战:HEALTHCHECK 与 unhealthy 自动重启 | 2026-09-21 |
| Nginx location 匹配优先级实战:=、^~、~ 命中顺序验证 | 2026-09-21 |
| Docker容器缺命令:精简镜像补装与临时容器调试 | 2026-09-20 |
| Nginx 上传大文件报 413 与超时:参数与缓冲区排查 | 2026-09-20 |
| Nginx与PHP上传目录权限:www-data、umask与0777的坑 | 2026-09-21 |
| MySQL 授权与远程访问配置实战:user@host 匹配规则、bind-address 与防火墙三层放行检查 | 2026-09-21 |
| Docker容器时间漂移与crond定时任务错乱:TZ与宿主时间源协同 | 2026-09-21 |
| systemd timer 替代 crontab 实战:OnCalendar、随机延迟与失败重试 | 2026-09-22 |
| Nginx 代理 WebSocket 频繁断连:Upgrade 头、超时与保活配置 | 2026-09-22 |