资讯详情

Linux DNS服务实战:从Bind安装到Unknown host故障排查

📅 2026/10/21 2:11:37 | 华诺云谱 👁 阅读
Linux DNS服务实战:从Bind安装到Unknown host故障排查
前几天帮某公司排查一个内网域名解析故障应用一直报“Unknown host”折腾了大半天。后来复盘发现问题既不在网络也不在服务进程而是 DNS 服务器上“监听地址”和“查询权限”的语义没理清。那次之后我把 Linux DNS 服务从安装到实战配置完整重做了一遍也记录了非常多容易忽略的细节。这篇文章就围绕“Linux DNS 服务详解”和“DNS 实战配置”这两个关键词展开从 Bind 安装、zone 文件编写、dig 验证、故障排查一直到访问控制、内网转发和视图分流把一整条链路串起来讲。无论你是刚碰 Linux 的小白还是配过几次 DNS 但总在排错时卡壳的人这篇文章都值得存一份作为参考。1. 一次“域名突然解析不了”的故障现场让我决定动手重写配置1.1 表象应用报 Unknown hostping IP 却完全正常某公司有一套部署在内网的系统某天 web 服务突然连不上数据库日志里反复出现Unknown host database.internal。第一反应基本都是检查网络我先 ping 数据库服务器的 IP通了再用 curl 访问内网接口的 IP也正常。这就很经典了——能通 IP不能通域名说明问题十有八九出在域名解析环节。我跑到出问题的机器上看/etc/resolv.confnameserver指向的是 192.0.2.10正是内网自建 DNS 服务器。接着用dig手动查了一下发现返回状态是REFUSED。服务进程在跑端口也在监听但查询被拒绝说明这台 DNS 根本不是“挂了”而是“不让我查”。1.2 我的第一轮误判以为服务没起来或者端口不通看到REFUSED之前我先做了两件事systemctl status named看进程状态再用ss -lntp看 53 端口。进程是active (running)端口也监听在 192.0.2.10 和 127.0.0.1 上。端口通、进程活着为什么查询会被拒再翻配置才发现options里写了allow-query { localhost; };。这个配置表达的意思是只有本机能向这台 DNS 发起查询内网其他客户端全部被拒之门外。而问题在于上一任配置只在listen-on里写了0.0.0.0没在allow-query里放开内网网段。1.3listen-on管“收包”allow-query管“回答”两者不是一回事很多人容易把listen-on和allow-query混在一起。listen-on决定 named 进程把 UDP/TCP 53 端口绑定到哪个本地 IP影响的是“网络层让不让数据包到达”。allow-query是应用层的查询白名单决定“到达了之后named 愿不愿意处理这个查询”。可以这么理解电话线接通到前台不代表前台愿意给你查资料。就算监听在 0.0.0.0只要allow-query设置不对该拒绝一样拒绝。这个故障修起来倒也快把allow-query改成内网网段就行但暴露出的问题值得警惕配置 DNS 时每一项参数的管理边界必须清楚不能靠“看着像”去猜。2. 动手前必须分清楚权威区、递归查询与缓存定位2.1 一台 DNS 服务器可能同时在扮演两个角色配置 Bind 之前必须想清楚这台服务器在解析链路里的角色。最简单也最容易踩坑的就是把“权威服务器”和“递归解析器”混为一谈。权威服务器负责回答自己名下 zone 的查询类似“户籍窗口”。你说www.lab.example是 192.0.2.30只有我这个窗口说了算。递归解析器则像“问路中介”客户端把域名丢给它它替你去根服务器、顶级域服务器、权威服务器层层问最后把答案拿回来可能还会缓存。一台 DNS 可以同时干这两件事。在named.conf里定义了type master的 zone它就拥有权威区开启了recursion yes并配置allow-recursion它就提供递归服务。两个角色混在一起时故障排查的复杂度会明显上升所以每写一个参数前先问自己我现在是在配权威、配递归还是配缓存2.2 最小实验环境如何设计为了避免污染真实网络我建议用文档保留地址段来搭实验环境。这样即使配置写错也不会把网内现有设备搞崩。我这里用一个虚构的模拟项目 X 做底子域名用lab.example网段用 192.0.2.0/24DNS 服务器 IP 是 192.0.2.10客户端是 192.0.2.20。为什么选这类地址192.0.2.0/24、198.51.100.0/24、203.0.113.0/24 是专门留给文档和示例用的网段不会和真实公网地址冲突。内网实验用它们既安全又能完整复现各种解析流程。2.3 zone 文件与资源记录的最小知识在 zone 文件里最常见的记录类型有 SOA、NS、A、CNAME、MX。SOA 是整个区的“版本信息”包含序列号、刷新时间、重试时间、过期时间和负缓存时间NS 声明哪个服务器是权威A 记录把域名映射成 IPv4 地址。举个例子一条简单的 A 记录就是www IN A 192.0.2.30它表示www.lab.example解析到 192.0.2.30。这里的IN是类表示 Internet 类绝大多数场景都是它。CNAME 是别名MX 是邮件交换记录。刚开始配置一个内网解析服务把 SOA、NS、A 这几种记录写对基本就能跑起来。2.4 TTL 与缓存为什么改完记录不是马上生效TTL 是资源记录在解析器缓存里的存活时间。比如一条记录的 TTL 是 86400 秒客户端或递归解析器拿到后会缓存一天。在这个时间内即使权威服务器上已经改了 IP客户端拿到的还是旧答案。所以排错时有个很实用的技巧直接dig 权威服务器 域名绕过客户端本地缓存但递归解析器那一层的缓存不容易绕过只能等 TTL 过期或者在测试环境重启服务刷新缓存。理解了这一点才不会被“明明改了 DNS 却不生效”这种问题困住。3. Bind 从安装到首个 A 记录生效一份基本可以直接抄的配置3.1 安装 Bind 与命令行工具在绝大多数 Linux 发行版上安装 Bind 都很简单。用包管理器直接装# apt 系列 sudo apt install bind9 bind9-utils # dnf/yum 系列 sudo dnf install bind bind-utils我建议把bind9-utils或bind-utils一并装上因为后面要用的named-checkconf、named-checkzone、dig、nslookup都在这里面。没有这几个工具排错效率会直线下降。装完后先看看主配置文件位置。常见路径是/etc/bind/named.conf或/etc/named.conf不同发行版可能会有named.conf.options、named.conf.local这样的 include 文件。为了便于管理我会单独建一个 zones 目录存放所有 zone 文件例如/var/lib/bind/zones避免把一堆记录堆在主目录里。3.2 named.conf 主配置骨架我给出的是一份适合内网实验环境的配置。先把全局options写好options { directory /var/lib/bind; listen-on port 53 { 127.0.0.1; 192.0.2.10; }; listen-on-v6 { none; }; allow-query { any; }; allow-recursion { 192.0.2.0/24; 127.0.0.1; }; allow-transfer { none; }; recursion yes; dnssec-validation auto; }; zone lab.example { type master; file /var/lib/bind/zones/db.lab.example; };listen-on我只写了本机回环和内网地址没有写0.0.0.0。如果这台机器有公网网卡写0.0.0.0等于把 DNS 服务暴露到公网被扫描后可能被人当作反射放大攻击的跳板。allow-query { any; }表示允许所有来源查询我这个权威 zone对于权威区来说通常没问题但allow-recursion只允许内网网段限制递归解析的适用范围。allow-transfer { none; }则禁止 zone 传输避免别人把整个解析记录拖走。3.3 zone 文件逐行拆解在/var/lib/bind/zones/db.lab.example里写入以下内容$TTL 86400 IN SOA ns1.lab.example. admin.lab.example. ( 2024111201 ; serial 7200 ; refresh 3600 ; retry 1209600 ; expire 300 ) ; negative cache TTL IN NS ns1.lab.example. IN A 192.0.2.10 ns1 IN A 192.0.2.10 www IN A 192.0.2.30 api IN A 192.0.2.31 db IN A 192.0.2.40第一行$TTL 86400给后面所有没有显式写 TTL 的记录一个默认值。SOA 记录里的数字依次代表序列号、刷新时间、重试时间、过期时间、负缓存时间。重点强调两点第一serial 是我手动维护的版本号每次修改 zone 都必须递增否则从服务器或下游解析器可能认为“区没有变化”不重新加载数据。第二域名末尾的点不能丢。admin.lab.example.写成admin.lab.example没有点会被当成admin.lab.example.lab.example这是新手最容易犯的错。这里的邮箱不是真邮箱只是表示管理员联系方式用点替代。3.4 启动服务并用 dig 验证写完后先做语法检查sudo named-checkconf sudo named-checkzone lab.example /var/lib/bind/zones/db.lab.example如果看到OK说明配置基本没问题。然后启动服务并设置开机自启sudo systemctl enable --now named在客户端 192.0.2.20 上查询dig 192.0.2.10 www.lab.example预期ANSWER SECTION里会出现www.lab.example. 86400 IN A 192.0.2.30。这就是第一个 A 记录生效了。再查一个不存在的域名会看到NXDOMAIN这也能反证权威区工作正常。3.5 我最容易踩的坑目录权限和安全策略有一段时间我喜欢把 zone 文件放在自己建的新目录里结果服务一 restart日志报open: permission denied。问题不一定是你配置写错而是系统的安全策略限制了 named 进程读取自定义目录。在部分发行版上默认策略只允许 named 访问/var/named、/var/lib/bind等特定目录。放在/root或某个家目录里的 zone 文件即使权限是 644也可能被拦。最稳妥的做法是把 zone 文件放到系统认可的 zone 目录再做named-checkzone。如果实在要自定义路径需要同时处理安全策略生产环境建议优先走标准路径省得日后排查麻烦。4. 解析异常时的取证手段dig、named-checkzone、日志三件套4.1 dig 的几种查询方式每个都用得上排查 DNS 问题时dig是使用率最高的工具。它的基本用法是dig 服务器 域名 类型。比如dig 192.0.2.10 www.lab.example A如果只想要答案不加头信息可以用shortdig 192.0.2.10 www.lab.example short如果要跟踪递归查询的完整路径用tracedig trace www.example.com它会从根服务器开始一级一级展示查询过程。还有一个参数很有用norecursedig 192.0.2.10 www.lab.example norecurse这个命令要求服务器不要做递归解析只看它自己的权威数据。配合“这台服务器到底有没有这个 zone”的判断非常高效。4.2 把 named-checkconf 和 named-checkzone 当格式化工具用修改 Bind 配置前我都会跑一遍named-checkconf它不只会检查语法还能帮你发现括号忘关、分号缺失这类低级问题。named-checkzone则用来检查 zone 文件用法是sudo named-checkzone lab.example /var/lib/bind/zones/db.lab.example如果返回OK再 reload。很多线上事故都发生在“改完配置直接 reload结果语法报错导致全部域名不可用”的场景。正确流程应该是先named-checkconf再named-checkzone然后systemctl reload named最后看journalctl确认没有新报错。4.3 NXDOMAIN、SERVFAIL、REFUSED 分别意味着什么查询 DNS 时返回的 status 字段是排错的核心线索。下面这张表是我在实际工作中反复对照的响应码含义常见原因排查方向NXDOMAIN域名不存在名称写错、zone 里缺这条记录、查的域根本不是这台服务器负责确认域名是否存在确认查询的域是否在服务器权威区内SERVFAIL服务器处理失败zone 文件加载失败、上游解析器不可达、文件权限不足查 named 日志用norecurse验证权威区数据REFUSED服务器拒绝响应allow-query限制、递归权限不足、不支持的查询类型检查访问控制确认监听地址与查询来源网段理解这些状态码能省掉大量“瞎试”的时间。遇到SERVFAIL先怀疑服务端遇到REFUSED先怀疑权限遇到NXDOMAIN先确认这个域名是否真的存在。4.4 一个从 SERVFAIL 开始的完整排查实例有一次某开发者在模拟项目 X 里新增了一个bbs.lab.example的记录named-checkzone显示 OK但 reload 后dig一直返回SERVFAIL。我当时没有直接去翻记录而是按下面的链路一步步看第一步确认服务状态systemctl status named显示 active排除进程问题。第二步用dig 192.0.2.10 bbs.lab.example norecurse照样SERVFAIL说明不是递归的问题而是本机加载 zone 失败。第三步打开日志sudo journalctl -u named --since 5 minutes ago -f日志里出现了一行关键信息loading from file failed: permission denied。第四步检查 zone 文件属性发现文件被放在了自定义目录下属主是 root权限 600named 进程根本读不了。第五步把文件移动到标准目录改成bind:named属主和 640 权限重新 reload问题立刻消失。这个案例里最花时间的其实是“没想到权限会卡住 zone 加载”。事后总结遇到SERVFAIL一定要优先去看服务日志而不是反复改配置。5. 把服务收进内网访问控制、视图分流与转发规则5.1 allow-query 和 allow-transfer 的正确姿势对内网 DNS 来说访问控制不能只图省事。如果allow-query写成any而服务器又开了递归就容易被外部扫到变成开放递归解析器。这在真实环境里是要尽量避免的。常规做法是allow-query只放行内网网段或者为权威区单独放行allow-recursion也只放行内网网段。allow-transfer更敏感它控制的是 zone 传输权限。默认如果写any任何客户端都能向服务器请求传输整个 zone 文件等于把内网所有主机的域名和 IP 分布全盘托出。生产环境建议写成{ none; }或者只允许指定的从服务器 IP。5.2 view按来源 IP 返回不同的解析结果企业中很常见的需求是“内外网解析结果不同”。比如内网访问www.lab.example应该得到 192.0.2.30外部访问则应该得到 203.0.113.30。Bind 的view就是干这个的。一个简化配置如下view internal { match-clients { 192.0.2.0/24; 127.0.0.1; }; recursion yes; zone lab.example { type master; file /var/lib/bind/zones/db.lab.example.internal; }; }; view external { match-clients { any; }; recursion no; zone lab.example { type master; file /var/lib/bind/zones/db.lab.example.external; }; };match-clients决定哪些来源属于这个 view。内部 view 里的db.lab.example.internal文件把www指向内网地址外部 view 里的文件指向对外地址。要注意Bind 中一旦启用 view所有 zone 都必须定义在 view 内部否则会报 “out of view” 的错误。5.3 forwarders内网环境到底该把解析请求往哪转很多企业的内网出口并不允许 UDP 53 直接访问外界根服务器。这时如果客户端想解析公网域名内网 DNS 自身又无法完成递归就需要配置转发器。options { forwarders { 10.0.0.1; 10.0.0.2; }; forward only; };这段配置意思是本地解析不了的域名统一交给 10.0.0.1 和 10.0.0.2 这两台上游 DNS 处理。forward only表示自己不做完整递归只转发。这里有个细节如果这台服务器同时拥有权威区本机负责的域名还是会直接从本地 zone 回答不会拿去转发。配置时要把权威区和转发范围分开理解避免误伤。5.4 用 systemd 管理 Bind 服务配置改完日常操作离不开 systemd。常用命令是sudo systemctl enable --now named sudo systemctl reload named sudo systemctl status named sudo journalctl -u named -freload是平滑重载配置不会中断正在进行的查询但如果你改了多个 zone 且只想逐个验证更稳妥的方式还是先named-checkconf再reload最后看日志。不要每次改完配置都restart生产环境里这会丢失缓存还会带来短暂的中断窗口。6. 留给自己的交付前检查清单和两个小习惯6.1 我可以直接照着跑的检查顺序每次配完 DNS我都会按顺序跑一遍以下检查确认没有遗漏sudo named-checkconf sudo named-checkzone lab.example /var/lib/bind/zones/db.lab.example dig 192.0.2.10 www.lab.example short dig 192.0.2.10 notexist.lab.example dig 192.0.2.10 www.example.com short sudo journalctl -u named --since 10 minutes ago第一、第二条检查配置语法第三条验证本地权威记录第四条验证 NXDOMAIN 行为第五条验证递归或转发是否正常最后一条看有没有隐藏的加载错误或权限报错。这套流程走完基本可以放心交接。6.2 两个让我少踩坑的操作习惯第一个习惯是每次修改 zone 文件后先递增 serial再执行named-checkzone。不要小看这一步很多“改了半天不生效”的怪问题最终都指向 serial 没变。第二个习惯是在生产环境不要直接修改原 zone 文件先备份再改动。这样即使改错也能迅速回滚到上一个可用版本。DNS 配置这件事说难不难说简单也不简单。大部分故障不是协议太深奥而是配置语义没理清、验证顺序乱了。把每一层的职责边界搞清楚再配合一套固定的检查流程Linux 下的 DNS 服务其实可以非常稳定。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑