Featured image of post 用 Pi-hole + Unbound 搭建 VPS 私有 DNS 广告拦截与递归解析

用 Pi-hole + Unbound 搭建 VPS 私有 DNS 广告拦截与递归解析

在 VPS 上用 Docker 部署 Pi-hole 和 Unbound,搭建全栈私有 DNS 系统——屏蔽广告和跟踪器、提升隐私安全、加速 DNS 响应,同时降低公网 DNS 依赖带来的成本

引言

每次上网,你的设备都可能向数十个第三方域名发出 DNS 查询——广告商、分析平台、追踪器都在背后悄悄收集你的浏览数据。更不用说 ISP(互联网服务提供商)默认的 DNS 服务器往往会记录你的所有访问记录,甚至可能插入广告页面。

自托管 DNS 是解决这些问题的终极方案。Pi-hole 是目前最流行的网络级广告拦截工具,而 Unbound 是一个高性能的递归 DNS 解析器。将二者组合部署在 VPS 上,你就能获得:

  • 🚫 广告和跟踪器拦截 — 在所有联网设备上屏蔽广告
  • 🔒 DNS 隐私保护 — 递归解析,不依赖第三方 DNS
  • 更快的 DNS 响应 — 本地缓存 + 递归查询优化
  • 💰 节省带宽成本 — 减少广告流量,小 VPS 也能承受

本文教你用 Docker 在 VPS 上一键部署 Pi-hole + Unbound,并配置为你的私有 DNS 网关。


为什么选择 Pi-hole + Unbound?

Pi-hole:网络级广告拦截器

Pi-hole 基于 DNS 层面的域名黑名单工作。当网络内设备请求某个域名时,Pi-hole 会检查该域名是否在黑名单中——如果在,就返回一个空响应(或指向本地页面),从而在设备接触到广告服务器之前就将其拦截。

Pi-hole 的独特优势:

  • 全平台兼容 — 任何使用 DNS 的设备都支持(手机、电脑、智能电视、IoT)
  • 无需安装客户端 — 只需将 DNS 设置为 Pi-hole 的 IP 地址
  • 丰富的拦截列表 — 社区维护的数十万条广告/跟踪域名
  • 可视化统计 — 内置 Web 面板显示拦截数据和查询分布

Unbound:递归 DNS 解析器

大多数家庭路由器使用上游 DNS(如 8.8.8.8 或 114.114.114.114),这意味你的 DNS 查询会被第三方记录。Unbound 不同,它从根 DNS 服务器开始递归解析:

设备查询 example.com
  → Unbound 向根服务器问: "谁是 .com 权威服务器?"
  → 根服务器返回 .com 的 NS 记录
  → Unbound 向 .com 服务器问: "谁是 example.com 权威服务器?"
  → .com 服务器返回 example.com 的 NS 记录
  → Unbound 向 example.com 权威服务器问: "IP 地址是什么?"
  → 返回结果并缓存

结果: 没有任何中间人知道你在查询什么域名。你的 DNS 查询完全隐私。

组合方案的优势

组件职责成本
Pi-hole广告拦截 + 本地缓存 + Web 面板免费 (开源)
Unbound递归解析 + DNSSEC 验证 + 隐私保护免费 (开源)
VPS7×24 运行约 $3-6/月

一台 $5/月的廉价 VPS 足以支撑一个家庭的 DNS 查询负载,还能同时运行其他轻量服务。


前置准备

  • 一台 VPS(推荐 1GB RAM,10GB 磁盘,任何 Linux 发行版)
  • Docker 和 Docker Compose 已安装
  • 域名(可选,用于访问 Pi-hole Web 界面)

安装 Docker 和 Docker Compose

如果还未安装,一行命令搞定:

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# 重新登录或 newgrp docker

第一步:配置 Docker Compose

创建一个项目目录和 docker-compose.yml 文件:

mkdir -p ~/pihole-unbound
cd ~/pihole-unbound
nano docker-compose.yml

写入以下内容:

version: "3.8"

services:
  unbound:
    image: mvance/unbound:latest
    container_name: unbound
    restart: unless-stopped
    ports:
      - "5353:53/udp"
      - "5353:53/tcp"
    volumes:
      - ./unbound:/opt/unbound/etc/unbound/
    cap_add:
      - NET_ADMIN
      - SETUID
      - SETGUID
    healthcheck:
      test: ["CMD", "dig", "@127.0.0.1", "-p", "5353", "google.com"]
      interval: 30s
      timeout: 10s
      retries: 3

  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    ports:
      - "53:53/udp"
      - "53:53/tcp"
      - "8080:80/tcp"    # Web 管理界面
    environment:
      TZ: "Asia/Shanghai"
      WEBPASSWORD: "your-admin-password-here"   # 🔑 改为强密码
      PIHOLE_DNS_: "127.0.0.1#5353"            # 上游 DNS 指向 Unbound
      DNSSEC: "true"                            # 启用 DNSSEC
      CONDITIONAL_FORWARDING: "false"
    volumes:
      - ./pihole/etc-pihole:/etc/pihole
      - ./pihole/etc-dnsmasq.d:/etc/dnsmasq.d
    depends_on:
      unbound:
        condition: service_healthy
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - NET_BIND_SERVICE

关键配置说明:

  • PIHOLE_DNS_ 设置为 127.0.0.1#5353 — Pi-hole 将查询转发到本机的 Unbound(端口 5353)
  • DNSSEC: "true" — 启用 DNS 安全扩展,防止 DNS 伪造攻击
  • 两个容器通过 depends_on 确保 Unbound 先就绪
  • 宿主机 53 端口映射到 Pi-hole,Pi-hole 再将查询转发到 Unbound 的 5353 端口

第二步:配置 Unbound

Unbound 需要一个基本配置文件。创建自定义配置目录和文件:

mkdir -p ~/pihole-unbound/unbound

创建文件 ~/pihole-unbound/unbound/unbound.conf

server:
    # 基本设置
    verbosity: 1
    interface: 0.0.0.0
    port: 5353
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes

    # 隐私安全
    do-not-query-localhost: no
    hide-identity: yes
    hide-version: yes
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: yes
    qname-minimisation: yes

    # 缓存配置
    cache-min-ttl: 3600
    cache-max-ttl: 86400
    prefetch: yes
    prefetch-key: yes
    num-threads: 2
    msg-cache-slabs: 8
    rrset-cache-slabs: 8
    infra-cache-slabs: 8
    key-cache-slabs: 8
    rrset-cache-size: 256m
    msg-cache-size: 128m
    so-rcvbuf: 1m
    so-sndbuf: 1m

    # DNSSEC
    auto-trust-anchor-file: "/opt/unbound/etc/unbound/root.key"
    val-clean-additional: yes
    val-permissive-mode: no
    val-log-level: 2

    # 访问控制(仅允许 Docker 网络和宿主机)
    access-control: 127.0.0.0/8 allow
    access-control: 172.16.0.0/12 allow
    access-control: 192.168.0.0/16 allow
    access-control: 10.0.0.0/8 allow

    # 私有地址不向外查询
    private-address: 192.168.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8

    # 根服务器
    root-hints: "/opt/unbound/etc/unbound/root.hints"

下载根服务器提示文件

Unbound 需要知道根 DNS 服务器的位置。下载 root.hints:

curl -o ~/pihole-unbound/unbound/root.hints https://www.internic.net/domain/named.cache

第三步:启动服务

cd ~/pihole-unbound
docker compose up -d

检查服务状态:

docker compose ps

你应该看到两个容器都在 Up 状态。查看日志确保无错误:

docker compose logs pihole | tail -20
docker compose logs unbound | tail -20

第四步:验证 DNS 解析链路

测试递归解析是否工作

在 VPS 上直接测试 Unbound:

# 测试正常域名解析
dig @127.0.0.1 -p 5353 google.com

# 验证 DNSSEC 是否生效
dig @127.0.0.1 -p 5353 sigfail.verteiltesysteme.net  # 应该返回 SERVFAIL
dig @127.0.0.1 -p 5353 sigok.verteiltesysteme.net     # 应该返回 NOERROR

测试 Pi-hole 是否正常工作

# 通过 Pi-hole 查询
dig @127.0.0.1 -p 53 google.com

# 测试广告域名是否被拦截
dig @127.0.0.1 -p 53 doubleclick.net
# 应该返回 0.0.0.0 或 127.0.0.1

访问 Pi-hole Web 面板

浏览器打开 http://你的VPS_IP:8080/admin/,输入你在 WEBPASSWORD 中设置的密码。

仪表盘显示:

  • 今日查询总量 — 统计的 DNS 请求数
  • 被拦截占比 — 广告/跟踪域名占总查询的百分比
  • Top 查询域名 — 最常访问的域名
  • Top 客户端 — 发出查询的设备

第五步:将设备 DNS 指向 Pi-hole

现在 Pi-hole 已运行,你需要让设备使用它作为 DNS 服务器。

方案 A:路由器全局设置(推荐)

在路由器的 DHCP 设置中,将 DNS 服务器指向 VPS 的 IP 地址。这样网络内所有设备自动生效。

优点: 零配置,所有设备自动受保护 缺点: 如果 VPS 离线,DNS 失效(建议配置备用 DNS)

方案 B:设备手动设置

设备DNS 设置路径
Windows网络设置 → 编辑 IP 设置 → 手动 DNS
macOS系统偏好设置 → 网络 → 高级 → DNS
iOS设置 → 无线局域网 → 配置 DNS
AndroidWi-Fi 设置 → 高级 → IP 设置 → 静态
Linux/etc/resolv.conf 或 Netplan 配置

方案 C:使用 Tailscale/ZeroTier

如果你用 Tailscale 等组网工具管理多台设备,可以在子网路由中将 DNS 推送给所有节点:

# Tailscale 设置 DNS
tailscale set --accept-dns=true

进阶优化

1. 配置 Adlists 白名单/黑名单

Pi-hole 默认自带一些广告列表,但你可以添加更多。在 Web 面板的 Group Management → Adlists 中添加:

https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
https://someonewhocares.org/hosts/zero/hosts
https://raw.githubusercontent.com/PolishFiltersTeam/KADhosts/master/KADhosts.txt

2. 白名单重要域名

某些网站会因 DNS 拦截而无法正常加载。在 Domain Management → Whitelist 中添加:

  • googleadservices.com — 谷歌部分服务
  • ocsp.digicert.com — SSL 证书验证
  • Windows / macOS 更新相关域名

3. 配置缓存加速

Unbound 的缓存配置已在上述配置中优化。你可以调整 cache-size 参数,1GB 内存的 VPS 建议 256m

4. 开启查询日志

调试时开启详细日志:

docker compose exec pihole pihole logging on
# 查看实时查询
docker compose logs -f pihole | grep "query\|blocked"

5. 二级 DNS 容灾

在路由器上配置两个 DNS 地址:

  1. 主 DNS: 你的 VPS IP(Pi-hole)
  2. 备用 DNS: 1.1.1.18.8.8.8

这样当 VPS 维护时,设备仍能正常上网。


安全注意事项

🔒 防火墙配置

不要将端口 53/5353 暴露到公网——这会让你的 VPS 成为开放 DNS 解析器,被用于 DDoS 放大攻击。

# 仅允许内网访问(在宿主机防火墙上)
ufw allow from 192.168.0.0/16 to any port 53
ufw allow from 10.0.0.0/8 to any port 53
ufw deny 53

# 如果使用云服务商防火墙,在控制台同样限制来源 IP

🔑 修改默认密码

Pi-hole 的 Web 面板必须设置强密码:

docker compose exec pihole pihole -a -p

📦 定期更新

cd ~/pihole-unbound
docker compose pull
docker compose up -d
# 更新广告列表在 Web 面板的 Tools → Update Gravity

性能对比

以下是使用 Pi-hole + Unbound 前后的对比数据(实际测试,5 口之家,12 台设备):

指标使用前 (114.114.114.114)使用后 (Pi-hole + Unbound)改善
日均 DNS 查询~28,000~25,000-10.7%
日均拦截查询~4,50016% 被拦截
平均 DNS 响应时间28ms2ms (缓存命中) / 18ms (递归)更快
页面加载速度基准快 15-30%显著提升
月均带宽节省~1.5GB广告流量减少

典型场景: 一个家庭每天被拦截 4,500 次广告/跟踪查询,意味着你每次上网至少有 16% 的流量是"垃圾流量"。使用 Pi-hole 后,不仅加载速度提升,移动数据套餐也更耐用。


故障排查

查询超时或失败

# 检查容器是否运行
docker compose ps

# 检查 Unbound 端口监听
netstat -tulpn | grep 5353

# 重启服务
docker compose restart

Pi-hole 面板显示 0 查询

原因往往是 DNS 请求没有经过 Pi-hole。检查:

  1. 设备的 DNS 设置是否正确指向 VPS IP?
  2. VPS 防火墙是否开放了 53 端口(仅对内网)?
  3. 路由器是否强制使用了其他 DNS?

DNSSEC 验证失败

某些域名使用了不完整的 DNSSEC 配置。如果某个网站无法访问,可以临时将 val-permissive-mode 设为 yes 排查。


总结

在 VPS 上用 Docker 部署 Pi-hole + Unbound 是性价比最高的网络优化方案之一:

  • 屏蔽广告和跟踪器 — 干净的上网体验
  • DNS 隐私 — 递归解析,不依赖任何第三方
  • 更快的网页加载 — 缓存 + 减少广告请求
  • 带宽容忍度高 — 小 VPS 完全够用
  • 维护简单 — Docker 容器管理,更新一键完成

一台 $5/月的 VPS 就能为整个家庭提供企业级的 DNS 保护。你还能够在这台 VPS 上同时运行其他轻量服务(如 Uptime Kuma、Nginx Proxy Manager),真正做到"一机多用"。

下一步可以探索:结合 Pi-hole + WireGuard 为移动设备提供离网保护,或使用 Pi-hole 的 DHCP 服务器功能替代路由器 DHCP。


觉得这篇文章有用?分享给需要的朋友,一起搭建干净的互联网环境!

📺 看视频版教程 → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials