使用Technitium DNS Server自建公网DNS服务器
该文章只探讨技术实现,具体能否使用请遵守当地法律法规
背景
中国大陆的所有正规运行的公共DNS服务器,只要后端处于大陆境内,则必然有DNS污染,并且这个污染可能是地域性的,即部分省市运营商DNS实现的污染。而公共加密DNS污染程度可能会小点,但仍存在污染可能性,例如当年的dockerhub污染是全国范围内的污染。
理论上依靠境内外分流可以实现去污染,即使用代理软件分流DNS请求,境内DNS只负责解析已知境内域名(可依靠geosite:cn实现),已知被完全阻断的域名通过远端节点解析,未知的域名由境内外并发比对geoip:cn判断对端是否是国内服务器或直接采用境外服务器解析实现。
但为了防止DNS泄露或实现其他目的,自建DNS并非一个坏选择。但自建DNS需要注意在部分国家或地区向外提供DNS解析是非法的,因此53端口的传统DNS和853端口的DoT或DoQ并非是个好选择,前者是明文传输更容易被劫持篡改,后者端口特征明显非常容易被阻断。
由此背景,我们可知,如果想自建公网DNS服务器,最好选用DoH这种毫无特征的加密请求,因为从第三者看来,DoH的查询请求与普通的网站访问无异。
Technitium服务器的安装
服务器选型
硬件资源
其实Technitium DNS Server服务器的资源需求并不高,以我个人的实践看来,1核心1GiB内存20GiB硬盘足够搭建一个Technitium DNS Server了,甚至你想还能再套娃一个MosDNS在上游分流,而且完美支持ECS。
网络资源
网络最好是三网优化线路,比如高富帅专用三网CN2GIA。当然如果你个人使用的网络运营商比较单一,那使用单网优化线路也可以,例如联通可以选用日本软银或9929乃至4837线路,移动可选用CMIN2。至于流量一个月500GiB左右差不多了,除非你还有其他用途,只用作个人DNS一个月500GiB绰绰有余。
安装前准备
个人选用的是Debian12最小化安装,相比Ubuntu更节省资源。
如果使用RedHat系Linux可参考步骤和思路,命令除了防火墙一般大差不差。
开启BBR+FQ
BBR能有效提升网络吞吐量并增强高延迟下TCP的表现。
Debian12内核默认带有BBR但是没有开启,通过以下命令开启BBR:
echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf通过sysctl -p应用变更。
root@dns:~# sysctl -p
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr通过lsmod | grep bbr验证变更是否生效。
root@dns:~# lsmod | grep bbr
tcp_bbr 20480 47放行防火墙
Debian12默认未安装防火墙,但是强烈建议安装一个,系统防火墙是设备的最后一道防线,希望各位对自己的设备负责。当然你就是不想装或者像国内云有面板防火墙那就跳过下一个吧
Debian12安装ufw防火墙:
root@dns:~# apt install -y ufwufw防火墙放通必要端口:
root@dns:~# ufw allow 22/tcp
Rules updated
Rules updated (v6)
root@dns:~# ufw allow 80/tcp
Rules updated
Rules updated (v6)
root@dns:~# ufw allow 443
Rules updated
Rules updated (v6)请确保ssh端口放开后再启用ufw防火墙:
root@dns:~# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup查看ufw防火墙状态:
root@dns:~# ufw status
Status: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
80/tcp ALLOW Anywhere
443 ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6) 安装步骤
说实话不是很想重复造轮子了,可参考我另外一篇文章进行安装和初始化。
如果使用docker安装初学者推荐host模式安装,与宿主机共享网络栈会减少很多问题,老手随意。
安装完毕后,会提示你使用http://vps_ip:5380/进行登入,但是此时应该是无法登入的,因为被系统防火墙拦截了。
此时你需要一个中间件进行反向代理,推荐NginxProxyManager或者Caddy。
域名解析记录配置
请先在DNS托管平台增加以下的DNS配置:
dns.xxx.com IN A VPS_IP
nginx.xxx.com IN A VPS_IP(仅NginxProxyManager需要)
中间件配置
Caddy
Debian源中默认带有caddy,可以使用apt install -y caddy直接安装:
root@dns:~# apt update
…………
…………
root@dns:~# apt install -y caddy
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
caddy is already the newest version (2.9.1).使用nano编辑caddy配置文件,添加以下配置:
root@dns:~# nano /etc/caddy/Caddyfile
dns.xxx.com {
log {
output file /var/log/caddy/dns.xxx.com/access.log {
roll_size 100MiB
roll_local_time
roll_keep 10
roll_keep_for 2160h
}
}
encode gzip zstd
header {
Strict-Transport-Security "max-age=31536000; preload"
}
@static {
path /js/* /css/* /img/* /images/* /fonts/* /static/* /favicon.ico
}
@api {
path /api/*
}
@myself_query {
path /test-query*
}
@dns_query {
path /dns-query*
}
handle @test_query {
rewrite * /dns-query
reverse_proxy 127.0.0.1:8880 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
}
}
handle @dns_query {
respond "Not Found" 404 {
close
}
}
handle_path /dashboard* {
reverse_proxy 127.0.0.1:5380 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
}
}
handle @static {
reverse_proxy 127.0.0.1:5380 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
}
}
handle @api {
reverse_proxy 127.0.0.1:5380 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
}
}
handle {
respond "Not Found" 404 {
close
}
}
}解释一下代码,上述的配置文件将访问后缀/dns-query的请求和根路径/的请求全部返回404,以防止针对DNS服务器的主动探测,如果不加这两条配置,很可能被他人主动探测出来。我知道这个配置代码写的很shit,但是能用就行了。
配置修改完后使用systemctl reload caddy重载配置后即可生效。
在上述的配置中后续要访问Technitium DNS Server的WebUI需要使用https://dns.xxx.com/dashboard来进行访问,而DoH请求要发送到https://dns.xxx.com/test-query才能正常解析。
NginxProxyManager
NginxProxyManager推荐使用docker compose安装,docker compose配置文件如下:
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
network_mode: "host"
#ports:
# - '80:80'
# - '81:81'
# - '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt创建docker-compose.yaml文件后使用docker compose up -d命令启动容器:
root@gc:~/docker/nginxproxymanager# docker compose up -d
[+] Running 1/1
✔ Container nginxproxymanager-app-1 Started 0.2s 使用netstat -ntlp命令查看监听情况:
root@gc:~/docker/nginxproxymanager# netstat -ntlp | grep 443
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 3869096/nginx: mast
tcp6 0 0 :::443 :::* LISTEN 3869096/nginx: mast此时需要使用ufw allow 81/tcp放通81端口,使用http://vps_ip:81的方式访问WebUI,默认用户名是[email protected],默认密码是changeme 。
登入后会要求你更改用户名和密码,修改完毕后将进入以下界面。

请先添加一个针对NginxProxyManager本身的反代,点击Hosts-Proxy Hosts-Add Proxy Host。

反代配置参考如下,点击save后会自动申请证书:


等待反代配置生效后,尝试访问https://nginx.xxx.com/测试能否访问NginxProxyManager的WebUI,能访问即可ufw delete allow 81/tcp取消防火墙配置。
通过账户名密码登入后继续按照下图添加反代配置,并且在Advanced中添加下列代码:


location = / {
return 404;
}
# myself-query 配置
location /test-query {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8880/dns-query;
}
# pannel 配置
location /dashboard/ {
proxy_pass http://127.0.0.1:5380/; # 自定义修改
#proxy_redirect / /pannel/; # 同步修改uri
#proxy_cookie_path / /pannel/; # 同步修改uri
proxy_set_header Host $host;
proxy_ssl_server_name on;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 按需配置
client_max_body_size 1m;
client_body_buffer_size 256k;
client_body_timeout 60s;
send_timeout 60s;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_buffer_size 32k;
proxy_buffers 4 16k;
proxy_busy_buffers_size 32k;
proxy_temp_file_write_size 32k;
proxy_ignore_client_abort on;
}
# api 配置
location /api {
proxy_pass http://127.0.0.1:5380/api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# robots.txt 配置
location = /robots.txt {
default_type text/html;
add_header Content-Type "text/plain; charset=UTF-8";
return 200 "User-Agent: *\nDisallow: /";
}
# 禁止扫描探测隐藏文件
location ~ /\.(?!well-known) {
deny all;
}此时可以通过https://dns.xxx.com/dashboard 来访问Technitium DNS Server的WebUI了。
Technitium DNS Server具体配置
大致的配置步骤与文章基本类似,在上游添加8.8.8.8的DoT和9.9.9.11的DoT即可正常使用,但仍然需要注意以下配置:
(推荐)打开ECS支持
上游选用8.8.8.8和9.9.9.11(不可选用9.9.9.9)的理由就是因为这两个DNS拥有完整的ECS支持,即Technitium DNS Server会将请求携带的ECS信息透传至上游,上游将根据ECS信息进行最优的DNS解析回复,如果你的ECS是在大陆的,就会给你解析回复大陆IP,如果在境外则解析回复境外IP,这个功能对在境内没有后端的DNS提升解析境内域名准确性有极大帮助。其他的例如CloudFlare DNS不支持ECS,OpenDNS对于ECS的支持也并不完全,只根据请求的来源IP进行ECS缓存而忽视透传的ECS信息。
对于ECS支持的测试,V2ex中有大佬做过完整测试,可以参考该文章。
Technitium DNS Server可以在Settings-General中打开EDNS Client Subnet (ECS)支持,IPv4前缀长度和IPv6前缀长度保持默认即可,/24和/56保证了隐私性的同时也保证了准确性。

(必须)启用DNS-over-HTTP
有心急的小伙伴在服务启动后就尝试使用DoH进行递归解析了,但是这个时候应该是不通的,因为DNS-over-HTTP是非加密协议默认不开启。
在Settings-Optional Protocols中必须勾选Enable DNS-over-HTTP,随后修改DNS-over-HTTP Port到8880,当然你也可以修改到其他端口,但反代配置也需要配合修改。

(必须)添加DNS-over-HTTP的ACL
在上述配置添加完后,DNS-over-HTTP协议已经启用了,但是依旧无法通过https://dns.xxx.com/test-query递归域名解析,是因为必须将127.0.0.1放入Reverse Proxy Network ACL中,不然连VPS本地都无法访问DNS-over-HTTP协议。
在Settings-Optional Protocols中,将127.0.0.1填入Reverse Proxy Network ACL即可。

(必须)允许公网递归
必须打开公网递归功能才能正常从公网解析域名,不然请求会被DROP掉。
在Settings-Recursion中选择Allow Recursion 并保存即可。
