去年冬天的一个晚上,我在上海金山的出租屋里躺着刷短视频,开屏广告五秒,滑下一条又是五秒。我妈在客厅用平板看剧,也一直在念叨"怎么老是弹东西出来"。
一开始我以为是某个 App 太脏,后来发现不对——手机、平板、电视盒子,连她那个只能看天气的老年机,全都在弹。这就不是设备的问题了,是网络这一层有人在往里塞东西。
想明白这一点之后,我盯上了 DNS。
所有网络请求的第一步都是域名解析。广告 SDK 要连自己的服务器,也得先解析域名。如果能在解析这一层把广告域名直接掐掉,设备端压根不知道有广告这回事——比在每个设备上装拦截插件干净得多。
于是有了这套 AdGuard Home。
为什么不用 Pi-hole
这两个东西定位差不多,都是局域网 DNS 过滤。我最后选 AdGuard Home,三个原因:
一是自带 Web 后台,查询日志、拦截统计、规则管理都在一个界面里,不用敲命令看报表。Pi-hole 也有面板,但 AdGuard 的日志筛选更细,能按设备、按域名、按时间段查。
二是规则管理更省事,可以直接订阅现成的过滤列表,也能自己加自定义规则,改完立刻生效,不用重启服务。
三是它对 DoH、DoT 的支持是内置的,后面想给手机做加密 DNS 不用另外折腾。
Pi-hole 的优势是更轻、社区更老,老玩家多。但对家用来说,AdGuard Home 少折腾。
装哪儿:先说说 53 端口这道坎
AdGuard Home 的核心工作就是接管 53 端口(DNS 的标准端口)。所以第一个问题不是配置,是谁占着 53。
我一开始把容器跑在 Mac mini 上。Mac mini M4 平时当家庭服务器用,上面跑了九个容器(之前写过那篇)。再开一个 AdGuard,内存占用一百兆左右,对它毫无压力。
结果容器起来就报错:
listen udp 0.0.0.0:53: bind: address already in use
查一下:
sudo lsof -i :53
输出里能看到两个进程在抢——一个是系统自带的 mDNSResponder,一个是 Docker 自己的 DNS 代理。macOS 上这个东西关不掉,你 kill 了它系统会立刻拉起来。
我试了三轮:
- 第一轮:把 AdGuard 的 DNS 端口改成 5353。容器能起,但路由器 DHCP 只认标准 53,没法指定端口,手机根本不走它。作废。
- 第二轮:关掉 Mac 的系统 DNS 服务。mDNSResponder 还是占着,白折腾。
- 第三轮:换机器。
家里还有一台 Debian 13 的小主机(主机名就叫 macmini,192.168.1.200),平时跑些杂活。Linux 上 53 端口好处理得多,直接装那儿。
sudo ss -lnptu | grep :53
如果占着的是 systemd-resolved:
sudo systemctl disable --now systemd-resolved
然后确认 /etc/resolv.conf 里的 nameserver 不是 127.0.0.53。
这里有个连锁反应要注意:systemd-resolved 关掉之后,这台主机自己的域名解析就没人管了。如果你的 AdGuard 还没配好上游,那主机连 apt 都跑不动,docker pull 也全挂。所以顺序必须是:先配好 AdGuard 的上游 DNS,再关系统解析。
docker-compose 配置
services:
adguardhome:
image: adguard/adguardhome:latest
container_name: adguardhome
network_mode: host
restart: unless-stopped
volumes:
- ./adguard/work:/opt/adguardhome/work
- ./adguard/conf:/opt/adguardhome/conf
logging:
driver: json-file
options:
max-size: "10m"
max-file: "2"
两个关键点:
network_mode: host。不用 host 模式的话,容器里的 53 端口要映射出来,UDP 映射在部分环境下会丢包,表现是"能解析但很慢"。host 模式最省事,也最稳。
日志限制。DNS 日志会疯涨,不加 max-size 一个月能吃满硬盘。我第一周就吃了 3G。
初始化
容器起来后访问 http://192.168.1.200:3000,进向导:
- 设置管理端口,默认 3000,我留着没改
- DNS 监听地址,勾 53
- 设置后台账号密码
- 配上游 DNS
上游 DNS:别迷信 1.1.1.1
这一步我踩过。
一开始照着网上的教程,上游填了 Cloudflare 的 1.1.1.1 和 8.8.8.8,看着很专业。结果配完发现家里网速反而变慢了,网页打开转圈时间变长。
原因很简单:国内访问境外 DNS 要走国际链路,延迟高,还容易被污染。一半的域名解析绕了一圈境外再回来,能快才怪。
换成国内的上游:
223.5.5.5(阿里)119.29.29.29(腾讯)114.114.114.114(备用)
延迟从两百多毫秒掉到二十几毫秒,效果立竿见影。
国内自用的 DNS 过滤,上游就用国内的。 这跟爱不爱国没关系,纯粹是链路问题。
过滤规则
AdGuard Home 的「过滤器」页面里可以订阅列表。我用的是这几个:
| 列表 | 作用 |
|---|---|
| AdGuard DNS filter | 官方维护,覆盖面广,误杀少 |
| AdGuard Base filter | 网页横幅、弹窗类广告 |
| 针对国内统计域名的自定义规则 | 拦友盟、腾讯广点通这类埋点 |
自定义规则的写法:
||umeng.com^
||gdt.qq.com^
||*.doubleclick.net^
前面 || 表示按域名前缀匹配,后面 ^ 是分隔符。
建议一开始别加太多规则。 我第一版加了七条列表,结果微信支付页面打不开,银行 App 提示网络异常。查日志才发现,某些 App 把风控域名和广告域名放同一个父域下面,一起被拦了。
正确做法是:先只加推荐的基础列表,跑两天看日志,哪台设备哪个功能不对,再从日志里找到被拦的域名加白名单。
让设备真的走它
配置再好,设备不来查也是白搭。
进路由器后台,找 DHCP 设置里的 DNS 服务器,填 AdGuard 的 IP(192.168.1.200)。
我家是电信光猫桥接 + 华硕路由器拨号,DHCP 里能直接改 DNS。有些运营商送的光猫没这个选项,那就在光猫里改,或者把路由器做成二级 NAT。
改完之后,设备不会立刻生效——DHCP 租约还在旧的。最快的办法是把路由器重启一次,或者每台设备手动断开重连 WiFi。
Mac 上还要刷一次 DNS 缓存:
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
效果
跑了一周,后台的数据:
- 日均查询 8 万多次
- 拦截率稳定在 17%~19%
- 拦截最多的域名是各家 App 的开屏广告、视频贴片、统计埋点
开屏广告不是全没了,大概少了六成。有些 App 的广告和内容走同一个域名,DNS 这一层拦不住,这是原理上的限制。
但有个好处特别明显:所有设备自动生效,不用装任何软件。我爸妈的平板、老人的手机、电视盒子,什么都没装,广告就少了一半。
踩过的四个坑
坑一:单点故障,一挂全家断网
AdGuard 挂掉,所有设备解析不了,表现就是"WiFi 连着但上不了网"。
处理:容器设 restart: unless-stopped;路由器主 DNS 填 AdGuard,备用 DNS 填 223.5.5.5,主的不通设备会自己切。
坑二:微信、银行 App 白屏
前面说过,规则太激进。这类 App 的风控域名很敏感,被拦了会直接判定网络异常。
处理:日志里搜被拦域名,加白名单。宁可少拦,也别把支付搞坏。
坑三:DNS 缓存导致改配置不生效
改了上游或规则,网页还是一样慢。因为设备和浏览器有缓存。
处理:AdGuard 后台点「清除查询缓存」,设备侧刷一遍系统 DNS 缓存。
坑四:日志涨满硬盘
默认日志不限制。我第一次没注意,一周吃掉 3G。
处理:compose 里加日志轮转(上面配置里那两行),AdGuard 后台也把查询日志保留天数改成 7 天。
值不值
对我来说值。
- 家里老人小孩的设备零配置,自动干净
- 查询日志能看到每台设备在访问什么,比路由器插件透明
- 顺手把家里 DNS 统一了,以后换设备不用一个个配
缺点也得说清楚:
- 只管局域网。手机用 4G/5G 出门在外,完全不生效
- 单点风险,得留备用 DNS
- 维护规则要花时间,尤其国内 App 的域名经常换
- 对 App 内置广告效果有限
最后
装完这套,最大的感受是:家里那些弹窗不是 App 的错,是解析这一层根本没人管。设备出厂默认就是运营商 DNS,运营商不管你弹不弹广告。
自己接管 DNS,把该拦的掐掉,整个网络会安静很多。
如果你也想搞,先把 53 端口这事儿想明白,别像我一样在 Mac 上折腾三轮才发现该换机器。
如果你也在折腾这套东西,碰到卡住的地方,可以加我微信 lige0552 聊聊。白天要上班,消息回得慢,看到都会回。