资讯详情

Linux 自建 DNS 服务器:Unbound 安装配置与递归解析实战

📅 2026/10/3 2:54:43 | 华诺云谱 👁 阅读
Linux 自建 DNS 服务器:Unbound 安装配置与递归解析实战
如果你在一台 Linux 服务器上排查过“网站能 ping 通但浏览器打不开”“apt update 死活报错”“内网服务名解析不到”这类问题最后大概率都会撞到同一个点上DNS。很多人对 DNS 的理解停留在“配置一下 /etc/resolv.conf 就行”但真正到了生产环境特别是需要自建递归解析、做缓存加速、增强解析隐私的场合一个靠谱的 DNS 服务就变得尤其重要。我这几年的经验是Unbound 在这类场景里几乎是最让人省心的选择。它由 NLnet Labs 开发是一个验证型、递归型、带缓存的 DNS 解析器体积小、配置清晰、性能不错而且和 systemd 的集成非常自然。这篇内容我按自己实际的部署流程来写从安装到配置、从 systemd 服务管理到问题排查全程都基于我在多台 Linux 服务器上跑过的真实操作。不管你是刚接触 Unbound 的新手还是已经在用 bind 想换个轻量方案的老运维这篇都值得一看。1. Unbound 到底是干嘛的它解决的问题和选型理由1.1 从一次“疑难杂症”说起我先讲一个真实案例。之前帮朋友排查一台 Ubuntu 服务器的“怪病”服务器本身网络正常内网 SSH 能连但运行apt update的时候经常超时偶尔又正常。一开始怀疑是网络问题换了清华源、阿里源都一样。后来用dig手动解析 Ubuntu 的源域名发现解析结果不稳定有时候特别慢有时候直接 SERVFAIL。再一看这台机器用的 DNS 是云厂商默认的127.0.0.53systemd-resolved 的本地监听地址而上游转发链路偶尔会丢包。把 systemd-resolved 禁用换成在本地装一个 Unbound直接在服务器上做递归解析问题立刻消失。这件事给我的启发是对于服务器来说把 DNS 解析完全交给外部的公共 DNS 服务器是有风险的。公共 DNS 的响应速度、可用性甚至安全策略都不受你控制而自己维护一个本地递归解析器等于把域名解析的主动权拿回了自己手里。1.2 为什么是 Unbound 而不是 BIND 或 dnsmasq很多人问我做本地 DNS 解析为什么不用 BIND或者用更轻量的 dnsmasq我的实际感受是这样的BIND 功能确实强大支持各种 Zone 管理、视图、主从同步但它属于“重型武器”配置复杂度高学习成本大而且对新手来说一个语法错误可能就让整个服务起不来排查起来比较痛苦。dnsmasq 则走了另一个极端它轻、快、简单但核心定位是“小型内网 DNS 缓存和 DHCP 配合”在递归解析、DNSSEC 验证、缓存精细控制这些方面能力有限。Unbound 正好在这两者之间找到了平衡点完整支持 DNSSEC 验证拿到的解析结果可以验证签名防止被篡改。递归解析能力强不需要依赖上游公共 DNS 就能从根服务器开始解析。缓存机制完善可以对 TTL、缓存大小做精细化控制。配置简洁清晰一份配置文件就能搞定大多数场景不像 BIND 那样还要维护各种复杂的语句结构。和 systemd 集成好服务管理、日志输出、资源限制都非常自然。对于绝大多数自建 DNS 的场景Unbound 是性价比最高的选择。1.3 Unbound 的核心组件和运行模型Unbound 之所以快一个原因是它采用了多线程事件驱动模型用 libevent 处理异步 I/O默认情况下会根据 CPU 核数自动启动线程。你可以通过num-threads参数手动控制线程数不过我实测下来默认值一般就够用了在 4 核 8G 的虚拟机里正常跑满千兆网卡的业务没问题。另一个核心设计是完全递归的工作方式。当客户端向 Unbound 发起一个域名解析请求时Unbound 会代替客户端依次查询根服务器、顶级域服务器、权威服务器最后拿到结果并缓存在本地。这个过程就是“递归解析”。当然你也可以把它配置成“转发模式”即所有请求统一发给上游 DNS比如 223.5.5.5、119.29.29.29Unbound 只做缓存放行。两种模式我都在用后面会详细讲各自的适用场景。2. 安装与初始环境准备2.1 不同发行版的安装命令Unbound 在主流 Linux 发行版的官方软件源里都有直接装就行不需要折腾第三方源。Debian / Ubuntu 系sudo apt update sudo apt install unbound unbound-anchorRHEL / CentOS / Rocky Linux 系sudo dnf install unbound如果是 RHEL 系注意unbound-anchor是安装时自带的依赖如果没装可以手动补上sudo dnf install unbound-anchor装完之后先别急着启服务先确认一下二进制和配置文件的位置which unbound unbound -V ls -l /etc/unbound/如果一切正常你会看到/etc/unbound/unbound.conf存在并且版本号能打印出来。我自己常用的是1.13.x和1.17.x两个版本功能上没有本质差别老版本照样能跑。提示安装后不要立刻systemctl start unbound因为默认配置在多播 DNS 和端口绑定上可能会和你已有的服务冲突建议先把配置改好再启动。2.2 用户、权限和目录规划Unbound 在设计上很注意安全降权。安装完成后系统会自动创建unbound用户和unbound用户组。服务运行在这个低权限用户下即使被利用也很难直接获取系统控制权。还有一个重要概念是chroot。Unbound 可以把自己关在一个特定的目录里根目录都不能离开这个范围。比如默认配置里可能写着chroot: /etc/unbound这个配置在 Debian 系默认是带上的。它会把 Unbound 可以访问的“根”限制为/etc/unbound。这会导致一个坑如果你在配置里加了某些需要访问其他目录的指令比如自定义 root 提示文件、日志文件等路径必须写成 chroot 之后的相对路径否则服务会起不来或者读不到文件。这一点我会在后面配置章节详细讲。在动手改配置之前我习惯先做一次“状态快照”sudo unbound-checkconf /etc/unbound/unbound.conf这一步会校验配置文件的语法。如果输出只有类似“no errors in /etc/unbound/unbound.conf”的信息说明配置基本没问题。这个命令我每次改完配置必跑几乎能拦下一半的启动失败问题。3. 核心配置解析unbound.conf 的关键项3.1 先看懂配置结构Unbound 的配置文件虽然选项很多但核心结构就几个块server、forward-zone、stub-zone、remote-control。其中server是必有的定义全局行为forward-zone是转发模式的配置指定上游 DNSstub-zone用于特定域名的定向查询remote-control是开启远程管理功能时用的。我用得最多的一个最小配置长这样server: verbosity: 1 interface: 127.0.0.1 interface: ::1 port: 53 do-ip4: yes do-ip6: yes do-udp: yes do-tcp: yes access-control: 127.0.0.0/8 allow access-control: ::1 allow access-control: 192.168.1.0/24 allow cache-min-ttl: 300 cache-max-ttl: 86400 hide-identity: yes hide-version: yes这个配置的含义很简单interface: 127.0.0.1和interface: ::1表示只在本机回环地址监听不对外提供服务。access-control用来控制允许哪些网段来查询。注意这行配置是白名单机制没有匹配到的默认拒绝。cache-min-ttl: 300意思是即使某个域名的原始 TTL 是 60 秒我在本地缓存里也至少保留 300 秒减少重复解析的频率。cache-max-ttl: 86400则是强制上限防止一些域名的 TTL 特别长比如某些 CDN 配置了 7 天 TTL导致 IP 变更后你的缓存迟迟不更新。hide-identity和hide-version是安全相关让探测者拿不到 Unbound 的版本号和身份信息。3.2 转发模式 vs 递归模式怎么选这是配置 Unbound 时最关键的一个决策点。纯递归模式是指 Unbound 自己从根服务器开始解析。它需要一个根提示文件root hints以及 DNSSEC 根信任锚root trust anchor。Debian 系安装时unbound-anchor会自动维护信任锚文件一般不需要手动干预。这个模式的好处是解析路径完整不依赖任何第三方公共 DNS不容易被上游污染或“特殊处理”。缺点是对网络链路要求高一点首次解析会更慢因为至少要访问根服务器和顶级域服务器。转发模式则是指 Unbound 把所有查询统一发给指定的上游 DNS 服务器。典型的配置forward-zone: name: . forward-addr: 223.5.5.5 forward-addr: 119.29.29.29 forward-addr: 1.1.1.1name: .表示所有域名都走转发。转发模式的优势是解析速度快、配置简单特别适合网络环境比较特殊、自己难以直接访问根服务器的场景。劣势是把解析信任交给了上游如果你选的上游本身不稳定Unbound 也没辙。我个人习惯是对公网服务器用纯递归模式因为公网链路访问根服务器一般没什么障碍还能最大限度避免上游 DNS 劫持对家里或小办公室的局域网用转发模式首选的转发目标可以填路由器或运营商的 DNS这样内网设备的解析延迟更低。3.3 缓存参数怎么调从 TTL 到内存Unbound 的缓存分为两部分msg-cache完整的响应消息和rrset-cache资源记录集合。理解这个区别有助于精准调参。msg-cache-size缓存的“查询结果消息”的总字节数。默认 4m我建议至少 8m。rrset-cache-size缓存的单独的 DNS 记录集合。默认 4m解析记录多的场景建议 16m 甚至 32m。cache-min-ttl强制的最小缓存时间防止上游返回特别小的 TTL有些 CDN 为了快速切换 IP 会把 TTL 设成 1 秒甚至 0 秒如果完全按这个来缓存Unbound 的命中率会很低。cache-max-ttl强制最大缓存时间避免记录长期不刷新。调缓存的时候要注意一个关系msg-cache-size应该小于等于rrset-cache-size的三倍左右而且两者的总和要小于jostle-timeout相关参数允许的范围。不过普通场景不用太纠结只要不是内存极小的嵌入式设备msg-cache-size: 8m、rrset-cache-size: 32m这套组合基本不会出错。注意不要盲目的把缓存调大。如果某个域名的解析结果被缓存太久而对方的权威服务器已经更新了 IP你这边会一直用到旧 IP导致网站访问异常。我就在一次 CDN 切 IP 的时候吃过这个亏。合理设置cache-max-ttl才是正道。3.4 安全加固谁可以用、谁能管Unbound 默认只监听本机这对大多数场景足够了。如果你想让局域网内其他机器也用它解析除了在interface加上内网网卡的 IP还必须加对应的access-control白名单。这里我想强调一个很多人会踩的坑只改interface不加access-control的话其他机器一样能查——因为 Unbound 的access-control默认规则是“除了明确允许的以外不接收查询”。我理解为白名单机制是默认启用的你没有写进去的网段都会被拒绝。所以你改完配置后记得用unbound-checkconf检查一遍确认access-control里包含了你所有需要放行的网段。remote-control配置是个双刃剑。它默认开在127.0.0.1的 8953 端口用unbound-control命令可以实时查看缓存统计、清除缓存、调整配置。这个功能很好用但如果你把它绑到了外网网卡就等于把 DNS 管理接口暴露在公网风险极大。我只在自己的服务器上通过 SSH 隧道访问管理端口从未直接暴露过 8953 端口。4. 用 systemd 管好 Unbound 服务4.1 理解 unbound.service 这个单元Linux 服务管理现在基本是 systemd 的天下了Unbound 也不例外。安装完后systemd 会注册一个名为unbound.service的服务单元。查看它的实际内容systemctl cat unbound.service不同发行版的 ExecStart 可能略有差异但整体流程是一样的[Unit] DescriptionUnbound DNS Resolver Wantsnetwork.target Afternetwork.target Beforenss-lookup.target [Service] Typesimple ExecStart/usr/sbin/unbound -d -p Restarton-failure ExecReload/usr/sbin/unbound-control reload ...解读一下关键点Typesimple表示启动命令是前台进程systemd 不会等待它 fork 完成这也是 Unbound 推荐的启动方式。ExecStart/usr/sbin/unbound -d -p-d表示 debug 模式但实际作用是“do not fork”保持在前台-p表示打印 PID。这样 systemd 才能正确管理这个进程。Restarton-failure如果服务异常退出systemd 会自动拉起。这个策略对 DNS 服务很重要毕竟没人希望半夜因为 Unbound 挂了导致整个业务解析瘫痪。ExecReload/usr/sbin/unbound-control reload这个命令是热加载配置不需要重启进程就能让配置生效。4.2 常用操作场景与顺序日常维护时我常用的命令无非这几个# 启动服务 sudo systemctl start unbound # 停止服务 sudo systemctl stop unbound # 重启服务 sudo systemctl restart unbound # 重新加载配置不中断服务 sudo systemctl reload unbound # 查看状态 sudo systemctl status unbound # 开机自启 sudo systemctl enable unbound这里有个关键区别要讲清楚restart 和 reload 不是一回事。restart是杀掉进程再重新拉起期间会有短暂的解析中断reload则是用unbound-control reload让 Unbound 重新读取配置文件进程不退出业务不中断。所以在生产环境改配置后我优先用systemctl reload只有遇到需要彻底重置缓存、或者服务卡死的时候才用 restart。4.3 设置开机自启和依赖关系服务器重启后 DNS 服务必须自动起来这是最基本的保障sudo systemctl enable unbound如果希望系统网络完全就绪后再启动 Unbound可以在 systemd 里设置Afternetwork-online.target。Debian 系默认没有这个约束但你完全可以自己写一个 drop-in 配置来覆盖sudo mkdir -p /etc/systemd/system/unbound.service.d然后写一个override.conf[Unit] Afternetwork-online.target Wantsnetwork-online.target写完后执行sudo systemctl daemon-reload这样以后每次开机Unbound 都会在网络完全配置好之后才启动避免因为“启动得太早、网卡还没配好”导致服务起不来。4.4 日志管理和排查Unbound 默认走 syslog在 systemd 环境里就是 journald。看日志的命令很简单sudo journalctl -u unbound -f如果是 Ubuntu 系统可能会被 apparmor 限制有些目录写不进去。我在实际部署中遇到过 Ubuntu 的 apparmor 配置文件阻止 Unbound 写日志的情况报错信息是“Permission denied”。解决方法是编辑/etc/apparmor.d/usr.sbin.unbound把需要访问的目录加进去然后sudo systemctl reload apparmor。提示如果你发现日志里什么都没输出先确认/etc/unbound/unbound.conf里的verbosity是不是 0。默认情况下verbosity: 1只会输出启动信息调试时临时调成 2 或 3 可以看得更细但别长期开着高 verbosity日志量会暴涨。5. 实操从零配置一个可用的本地递归 DNS5.1 明确场景选好模板我这里以一个典型场景为例一台 Ubuntu Server 22.04 虚拟机2 核 2G 内存部署在本地服务器上需要给内网测试机提供 DNS 解析同时这台机器自己也用这个 DNS 来访问外网。我选择纯递归模式 本地 zone的组合方案。第一步备份默认配置sudo cp /etc/unbound/unbound.conf /etc/unbound/unbound.conf.bak然后写入以下配置server: verbosity: 1 interface: 127.0.0.1 interface: 192.168.10.5 port: 53 do-ip4: yes do-ip6: no do-udp: yes do-tcp: yes access-control: 127.0.0.0/8 allow access-control: 192.168.10.0/24 allow cache-min-ttl: 300 cache-max-ttl: 86400 hide-identity: yes hide-version: yes num-threads: 2 msg-cache-size: 8m rrset-cache-size: 32m module-config: validator iterator local-zone: lxd. static local-data: test1.lxd. IN A 192.168.10.100 local-data: test2.lxd. IN A 192.168.10.101这套配置里几个值得掰扯的细节do-ip6: no—— 当前内网没跑 IPv6直接关掉能省一点资源。否则系统如果检测到 IPv6 网络问题反而会输出一些干扰日志。module-config: validator iterator—— 这是默认的模块组合启用了 DNSSEC 验证。如果你想做纯转发不做递归这里可能会不一样但普通场景别动它。local-zone和local-data—— 这是给内网自定义域名用的。比如内网有一台部署服务的主机 test1.lxdIP 是 192.168.10.100直接在 Unbound 里写死解析记录内网其他机器就能直接用test1.lxd访问这台机器不用改每台机器的 hosts。5.2 校验与启动做完配置先检查sudo unbound-checkconf如果输出no errors in /etc/unbound/unbound.conf说明语法OK。启动服务sudo systemctl start unbound sudo systemctl status unbound如果服务起来了可以用dig做基础验证。没装 dig 的先用 dnsutils 或 bind9-utils 装一下sudo apt install dnsutils dig 127.0.0.1 test1.lxd dig 127.0.0.1 baidu.com第一个应该返回192.168.10.100第二个应该能正常解析出百度服务器的 IP。这两条命令同时验证了本地 zone 和递归解析两条链路。5.3 小技巧临时测一下“从根解析”是否正常如果你怀疑根服务器链路有问题可以手动指定根服务器来解析dig 198.41.0.4 www.example.com198.41.0.4 是 a.root-servers.net 的 IP。如果这条命令成功说明从你这台服务器到根服务器的网络路径是通的。如果超时那你可能需要考虑转发模式绕开直连根的障碍。5.4 调优从关联性到并发上限跑了一段时间后我发现一个有趣的现象Unbound 对并发请求的处理能力受到 UDP 缓冲区大小的限制。如果日志里出现类似SO_RCVBUF或out of resource的提示说明内核的 UDP 接收缓冲太小了需要调大sysctl -w net.core.rmem_max1048576 sysctl -w net.core.rmem_default1048576这两个 sysctl 参数建议写进/etc/sysctl.d/99-unbound.conf里持久化net.core.rmem_default 1048576 net.core.rmem_max 1048576 net.core.wmem_default 1048576 net.core.wmem_max 1048576然后重启 net 服务或用sysctl -p生效。另一个值得说的是outgoing-range参数。这个参数控制每个线程能同时维持的对外 UDP 连接数默认是 4096。在大型局域网场景中如果同时解析的请求量特别大这个值可以适当调高到 8192。但如果你的机器内存只有 1G 左右建议保持默认因为这个值越大内存消耗也越大。5.5 用 unbound-control 做日常体检Unbound 自带管理工具配合 remote-control 配置可以实时查看运行状态。启用方式很简单在配置文件里加remote-control: control-enable: yes control-interface: 127.0.0.1 control-port: 8953然后重启服务sudo systemctl restart unbound常用的命令# 查看统计信息 sudo unbound-control stats # 清除所有缓存 sudo unbound-control flush_zone . # 清除某个域的缓存 sudo unbound-control flush_zone baidu.com # 查看缓存中的记录 sudo unbound-control dump_cache | head -20stats输出的内容很长我平时重点关注这几个指标total.num.queries累计查询数total.num.cachehits缓存命中数total.num.cachemiss缓存未命中数cachehitrate如果有的话整体命中率。合理的解析命中率应该在 70%~90% 之间太低说明缓存没起到作用太高则可能缓存过久。6. 常见问题与排查技巧实录6.1 端口 53 被占用systemd-resolved 的“占地盘”Ubuntu 系统使用 systemd-resolved 时默认会在 127.0.0.53:53 上监听 DNS。如果你尝试用systemctl start unbound很可能直接失败因为端口冲突。我的处理办法分几步sudo systemctl stop systemd-resolved sudo systemctl disable systemd-resolved sudo systemctl mask systemd-resolved然后修改/etc/resolv.conf。原来的文件可能是个 symlink 指到 systemd 生成的动态文件先删掉再新建sudo rm -f /etc/resolv.conf sudo nano /etc/resolv.conf写入nameserver 127.0.0.1如果你用的是内网网卡 IP 做监听也可以写nameserver 192.168.10.5改完后再systemctl start unbound就能正常起来了。这一步做完之后系统里所有本机 DNS 请求都会先进 Unbound 的缓存效率和稳定性都更好。注意mask是禁用服务的“终极手段”它会把服务的启动链接指向 /dev/null即使有其他服务依赖 systemd-resolved 也无法启动它。如果你的环境依赖 graphql、某些桌面组件或网络管理工具可能反而会出问题。这种情况下可以不用 mask只是 disable 就够了。反正我现在大多数 server 环境都是最小化安装mask 掉没毛病。6.2 更新配置后解析还是旧 IP如果你发现某个域名解析出来的 IP 和实际上游返回的不一致先别急大概率是缓存还没过期。可以手动刷新sudo unbound-control flush_zone example.com然后再用dig 127.0.0.1 example.com确认结果。但如果反复刷新后还是不对就要检查你有没有同时开着多个 DNS 服务比如 dnsmasq、systemd-resolved 又在监听本地 53 端口。用ss -tulnp | grep :53看一下 53 端口被哪些进程占用了把所有无关的监听都停掉只留 Unbound 一个。6.3 DNSSEC 验证失败的迷局Unbound 默认启用 DNSSEC 验证。如果根信任锚配置有问题或者上游服务器返回的签名信息残缺会产生 SERVFAIL。这个时候有两个排查方向确认信任锚文件存在且没损坏Debian 系检查/etc/unbound/root.key或/var/lib/unbound/root.key。如果损坏可以用sudo unbound-anchor -a /etc/unbound/root.key重建。检查服务器系统时间。DNSSEC 签名对时间窗口要求比较严格如果系统时间偏差太大验证会失败。建议保证 NTP 同步正常别让服务器时间漂移太离谱。如果确实觉得 DNSSEC 验证在某些内网场景造成了太多麻烦可以在配置里暂时关掉module-config: iterator但我不建议长期关闭毕竟 DNSSEC 是防止 DNS 劫持的重要机制。生产环境我都是保持验证开启只在临时排障时关一下。6.4 高并发下的性能优化技巧最后分享几个我实测有效的高并发优化方案。第一开启聚合查询。在 server 块里加qname-minimisation: yes。这是 RFC 7816 推荐的“查询名最小化”它能让 Unbound 在递归过程中只发送必要的域名部分减少信息泄露同时也有一定的性能提升。第二调高接收缓冲区。前面说的net.core.rmem_max记得写上持久化配置文件。第三根据 CPU 核数设置线程数。2 核机器设num-threads: 24 核机器可以设num-threads: 4。不过线程数超过 4 后收益递减不用盲目加。第四合理使用serve-expired。这是让我很惊喜的一个功能。开启动后即使某条缓存已经过期Unbound 也可以在后台尝试刷新同时先返回旧结果避免客户端等待。配置serve-expired: yes serve-expired-ttl: 86400这个功能对“感觉上更快”的提升非常明显。但记得它只是兜底策略真正稳定的解析还是要靠正常 TTL 刷新。6.5 诊断小抄常用的排错命令我平时排错用的命令就那么几条整理成一个速查表方便你对照场景命令预期结果配置校验unbound-checkconf输出无 error服务状态systemctl status unboundactive (running)端口监听ss -tulnp | grep 53unbound 在 listenDNS 查询dig 127.0.0.1 test.lxd返回对应 IP缓存统计unbound-control stats命中率 70%日志追踪journalctl -u unbound -f无 error 密集输出清空缓存unbound-control flush_zone .返回 ok7. 我的切身体会和后续可以怎么玩Unbound 我前后用了差不多四年最深的体会是它足够轻、足够稳但也需要你理解 DNS 的基础原理才能玩得转。很多人上来就把verify开着然后抱怨 SERVFAIL或者盲目调大缓存导致域名映射延迟这些都是对参数理解不到位造成的。最后再分享一个小技巧如果你在局域网里多台机器都需要统一 DNS同时希望不同的内网域名区段由不同机器负责可以用stub-zone把特定域名“踢”给指定权威服务器。比如stub-zone: name: office.lxd stub-addr: 192.168.20.2这样*.office.lxd的解析请求会交给192.168.20.2那台服务器Unbound 不自己递归。这个玩法非常适合公司内部有独立 DNS 服务器的场景。你在实际部署中如果遇到其他怪问题多看一下journalctl -u unbound的日志大多数困惑都能在里面找到答案。
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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

↑