Pi-hole + Unbound + PiVPN (WireGuard) + Zapret (nfqws) on one Linux server.
This guide is written so you can complete setup step-by-step without needing extra documentation.
- Network-wide ad/tracker blocking:
Pi-hole - Recursive DNS (no ISP/Google upstream):
Unbound - VPN access from outside home:
PiVPN (WireGuard) - DPI bypass for traffic that passes through this server:
Zapret (nfqws)
Traffic flow:
LAN client -> Pi-hole:53 -> Unbound:127.0.0.1:5335 -> Root DNS
VPN client -> WireGuard -> same Pi-hole/Unbound DNS
Optional DPI bypass -> only works if client traffic passes through this server
You need:
- Debian/Ubuntu server (Raspberry Pi is fine)
- Static server IP (example:
192.168.1.100) - Router admin access
sudoaccess
Important safety note:
- Do DNS setup first.
- Only enable "server as default gateway" after DNS is confirmed working.
- If clients lose internet, rollback by setting router DHCP gateway back to the router IP.
Set variables once (change values to your network):
export SERVER_IP="192.168.1.100"
export ROUTER_IP="192.168.1.1"Find your active WAN interface (needed later for NAT):
export WAN_IFACE="$(ip route get 1.1.1.1 | awk '{print $5; exit}')"
echo "$WAN_IFACE"sudo apt update && sudo apt upgrade -yInstall base tools:
sudo apt install -y curl git wget dnsutils iptables-persistent unbound ipset nftablessudo curl -sSL https://install.pi-hole.net | bashDuring installer:
- Keep static IP enabled
- Upstream DNS choice is temporary (we switch to Unbound next)
Check Pi-hole service:
pihole statusOpen admin panel:
http://SERVER_IP/admin
Get root hints:
wget https://www.internic.net/domain/named.root -qO- | sudo tee /var/lib/unbound/root.hints >/dev/nullCreate config:
sudo tee /etc/unbound/unbound.conf.d/pi-hole.conf >/dev/null <<'EOF'
server:
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
harden-glue: yes
harden-dnssec-stripped: yes
edns-buffer-size: 1472
prefetch: yes
qname-minimisation: yes
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
EOFValidate and restart:
sudo unbound-checkconf
sudo systemctl restart unbound
sudo systemctl enable unbound
sudo systemctl --no-pager --full status unboundDirect test:
dig +short google.com @127.0.0.1 -p 5335If you get IP addresses, Unbound works.
In Pi-hole UI:
Settings -> DNS
Do this:
- Uncheck all public upstream providers
- Set
Custom 1 (IPv4)to127.0.0.1#5335 - Save
Test Pi-hole local resolver:
dig +short google.com @127.0.0.1If this returns IPs, DNS chain is good.
On router DHCP/LAN settings:
- Primary DNS:
SERVER_IP - Secondary DNS: empty (or same as primary)
Do not change gateway yet. First verify clients can browse and ads are blocked.
Client check:
nslookup pi.holeor check client DNS server shows SERVER_IP.
cd ~
git clone https://github.com/bol-van/zapret.git
cd zapret
sudo ./install_easy.shEdit config:
sudo nano /opt/zapret/configSet (or add):
MODE="nfqws"
NFQWS_OPT="--filter-tcp=80,443 --dpi-desync=fake --dpi-desync-ttl=3"Restart and verify:
sudo systemctl restart zapret
sudo systemctl enable zapret
sudo systemctl --no-pager --full status zapret
ps aux | grep -E "nfqws|zapret" | grep -v grepCritical reminder:
- Zapret affects only traffic that goes through this server.
curl -L https://install.pivpn.io | sudo bashDuring installer:
- Select
WireGuard - Default port
51820is fine - When asked DNS provider, choose Pi-hole/local option if shown
Create client profile:
pivpn add
pivpn -qrOptional:
pivpn listWithout port forwarding, VPN usually works only inside your home LAN.
Add this rule in router NAT / Port Forwarding:
| LAN IP | Start Port | End Port | Source IP | Source Start | Source End | Protocol | Description | Enabled |
|---|---|---|---|---|---|---|---|---|
SERVER_IP |
51820 |
51820 |
0.0.0.0 |
51820 |
51820 |
UDP |
PiVPN |
Yes |
Enable IP forwarding permanently:
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-privacy-server.conf >/dev/null
sudo sysctl --system
sysctl net.ipv4.ip_forwardAdd NAT (idempotent, avoids duplicates):
sudo iptables -t nat -C POSTROUTING -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || \
sudo iptables -t nat -A POSTROUTING -o "$WAN_IFACE" -j MASQUERADEPersist firewall rules:
sudo netfilter-persistent save
sudo netfilter-persistent reload
sudo iptables -t nat -S POSTROUTINGOnly do this after all previous tests pass.
In router DHCP settings:
- Default Gateway/Router:
SERVER_IP - Primary DNS:
SERVER_IP
Renew DHCP on clients or reboot clients.
Verify on client:
- Linux/macOS:
ip route | head -n 2- Windows:
ipconfig /allExpected route:
default via SERVER_IP
Route only selected clients/ports (80/443) via SERVER_IP from router PBR rules.
Run these on server:
dig +short cloudflare.com @127.0.0.1 -p 5335
dig +short cloudflare.com @127.0.0.1
sudo systemctl is-active unbound pihole-FTL zapret
sudo iptables -t nat -S POSTROUTINGRun these on a LAN client:
- DNS server =
SERVER_IP - If DPI mode enabled: default gateway =
SERVER_IP - Confirm normal websites + previously blocked/censored target behavior
Run these for VPN:
pivpn -cConnect with WireGuard client and confirm:
- Internet works
- DNS queries resolve through home server
If clients lose internet after gateway change:
- In router DHCP, set gateway back to router IP (
ROUTER_IP) - Renew DHCP lease on clients (or reboot clients)
- Keep DNS as
SERVER_IPif Pi-hole still works
If Unbound fails:
sudo unbound-checkconf
sudo journalctl -u unbound -n 100 --no-pagerIf Zapret fails:
sudo journalctl -u zapret -n 200 --no-pager- IPv6 is intentionally disabled in this setup for simpler DPI behavior.
- Do not hardcode public DNS on router/clients/VPN if you want single-DNS architecture.
- For best stability, apply changes in two phases:
- DNS phase (Pi-hole + Unbound)
- Traffic phase (gateway/PBR + Zapret)
在一台 Linux 服务器上部署:Pi-hole + Unbound + PiVPN (WireGuard) + Zapret (nfqws)。
本指南按“可直接照做”的方式编写,新手也可以一步一步完成整套部署。
- 全网广告/追踪拦截:
Pi-hole - 递归 DNS(不依赖 ISP/Google):
Unbound - 外网安全接入:
PiVPN (WireGuard) - DPI 绕过(仅对经过本机的流量生效):
Zapret (nfqws)
流量路径:
局域网客户端 -> Pi-hole:53 -> Unbound:127.0.0.1:5335 -> Root DNS
VPN 客户端 -> WireGuard -> 同样走 Pi-hole/Unbound
可选 DPI 绕过 -> 仅对经过本服务器的流量有效
你需要:
- Debian/Ubuntu 服务器(树莓派可用)
- 服务器静态 IP(示例:
192.168.1.100) - 路由器管理权限
sudo权限
安全提示:
- 先完成 DNS,再动网关。
- 只有 DNS 验证成功后,才把服务器设为默认网关。
- 如果客户端断网,先把路由器 DHCP 网关改回路由器本机 IP。
先设置变量(按你的网络修改):
export SERVER_IP="192.168.1.100"
export ROUTER_IP="192.168.1.1"获取当前出口网卡(后面 NAT 需要):
export WAN_IFACE="$(ip route get 1.1.1.1 | awk '{print $5; exit}')"
echo "$WAN_IFACE"sudo apt update && sudo apt upgrade -y安装基础工具:
sudo apt install -y curl git wget dnsutils iptables-persistent unbound ipset nftablessudo curl -sSL https://install.pi-hole.net | bash安装过程中:
- 保持静态 IP
- 上游 DNS 先临时选,后面会切到 Unbound
检查 Pi-hole 状态:
pihole status管理面板:
http://SERVER_IP/admin
下载 root hints:
wget https://www.internic.net/domain/named.root -qO- | sudo tee /var/lib/unbound/root.hints >/dev/null创建配置:
sudo tee /etc/unbound/unbound.conf.d/pi-hole.conf >/dev/null <<'EOF'
server:
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
harden-glue: yes
harden-dnssec-stripped: yes
edns-buffer-size: 1472
prefetch: yes
qname-minimisation: yes
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
EOF检查并重启:
sudo unbound-checkconf
sudo systemctl restart unbound
sudo systemctl enable unbound
sudo systemctl --no-pager --full status unbound直接测试:
dig +short google.com @127.0.0.1 -p 5335有 IP 返回即正常。
在 Pi-hole 界面:
Settings -> DNS
操作:
- 取消勾选所有公共上游 DNS
Custom 1 (IPv4)填127.0.0.1#5335- 保存
测试 Pi-hole 本地解析:
dig +short google.com @127.0.0.1有 IP 返回表示 DNS 链路正常。
在路由器 DHCP/LAN 设置:
- Primary DNS:
SERVER_IP - Secondary DNS: 留空(或同 Primary)
先不要改默认网关。先确认客户端上网和广告拦截都正常。
客户端检查:
nslookup pi.hole或确认客户端 DNS 服务器为 SERVER_IP。
cd ~
git clone https://github.com/bol-van/zapret.git
cd zapret
sudo ./install_easy.sh编辑配置:
sudo nano /opt/zapret/config设置(或新增):
MODE="nfqws"
NFQWS_OPT="--filter-tcp=80,443 --dpi-desync=fake --dpi-desync-ttl=3"重启并验证:
sudo systemctl restart zapret
sudo systemctl enable zapret
sudo systemctl --no-pager --full status zapret
ps aux | grep -E "nfqws|zapret" | grep -v grep关键点:
- Zapret 只会处理经过本服务器的流量。
curl -L https://install.pivpn.io | sudo bash安装时:
- 选择
WireGuard - 端口
51820默认即可 - 如果有 DNS 选项,优先选 Pi-hole/本地 DNS
添加客户端:
pivpn add
pivpn -qr可选:
pivpn list不做端口转发时,VPN 通常只能在家里局域网内可用。
在路由器 NAT / Port Forwarding 中添加规则:
| 内网 IP | 起始端口 | 结束端口 | 来源 IP | 来源起始端口 | 来源结束端口 | 协议 | 说明 | 启用 |
|---|---|---|---|---|---|---|---|---|
SERVER_IP |
51820 |
51820 |
0.0.0.0 |
51820 |
51820 |
UDP |
PiVPN |
是 |
永久开启 IP 转发:
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-privacy-server.conf >/dev/null
sudo sysctl --system
sysctl net.ipv4.ip_forward添加 NAT(幂等,避免重复):
sudo iptables -t nat -C POSTROUTING -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || \
sudo iptables -t nat -A POSTROUTING -o "$WAN_IFACE" -j MASQUERADE持久化规则:
sudo netfilter-persistent save
sudo netfilter-persistent reload
sudo iptables -t nat -S POSTROUTING必须在前面全部验证通过后再做。
路由器 DHCP 设置:
- Default Gateway/Router:
SERVER_IP - Primary DNS:
SERVER_IP
让客户端续租 DHCP 或重启。
客户端验证:
- Linux/macOS:
ip route | head -n 2- Windows:
ipconfig /all期望:
default via SERVER_IP
仅把指定设备/端口(如 80/443)通过 SERVER_IP 转发。
服务器执行:
dig +short cloudflare.com @127.0.0.1 -p 5335
dig +short cloudflare.com @127.0.0.1
sudo systemctl is-active unbound pihole-FTL zapret
sudo iptables -t nat -S POSTROUTING客户端验证:
- DNS 服务器 =
SERVER_IP - 若开启 DPI 模式:默认网关 =
SERVER_IP - 正常网站可访问,目标站点行为符合预期
VPN 验证:
pivpn -c使用 WireGuard 连接后确认:
- 能上网
- DNS 解析走家庭服务器
如果改网关后客户端断网:
- 路由器 DHCP 网关改回
ROUTER_IP - 客户端续租 DHCP(或重启)
- 若 Pi-hole 正常,可保留 DNS 为
SERVER_IP
Unbound 故障排查:
sudo unbound-checkconf
sudo journalctl -u unbound -n 100 --no-pagerZapret 故障排查:
sudo journalctl -u zapret -n 200 --no-pager- 该方案默认关闭 IPv6,以简化 DPI 行为。
- 如果要保持单一 DNS 架构,不要在路由器/客户端/VPN 手动填公共 DNS。
- 推荐按两阶段上线:
- DNS 阶段(Pi-hole + Unbound)
- 流量阶段(Gateway/PBR + Zapret)
Tek bir Linux sunucuda: Pi-hole + Unbound + PiVPN (WireGuard) + Zapret (nfqws).
Bu rehber, teknik geçmişi az olan kullanıcıların bile adım adım sorunsuz kurulum yapabilmesi için yazıldı.
- Ağ genelinde reklam/izleyici engelleme:
Pi-hole - Recursive DNS (ISS/Google yok):
Unbound - Ev ağına dışarıdan güvenli erişim:
PiVPN (WireGuard) - DPI bypass (yalnızca sunucudan geçen trafikte):
Zapret (nfqws)
Trafik akışı:
LAN istemcisi -> Pi-hole:53 -> Unbound:127.0.0.1:5335 -> Root DNS
VPN istemcisi -> WireGuard -> aynı Pi-hole/Unbound DNS
Opsiyonel DPI bypass -> sadece sunucudan geçen trafikte çalışır
Gerekenler:
- Debian/Ubuntu sunucu (Raspberry Pi olur)
- Sunucu için statik IP (örnek:
192.168.1.100) - Router yönetici erişimi
sudoerişimi
Güvenli kurulum notu:
- Önce DNS kur, sonra gateway değiştir.
- DNS doğrulanmadan sunucuyu varsayılan ağ geçidi yapma.
- İstemciler internete çıkamazsa router DHCP gateway ayarını tekrar router IP'sine al.
Önce değişkenleri ayarla (kendi ağına göre değiştir):
export SERVER_IP="192.168.1.100"
export ROUTER_IP="192.168.1.1"Aktif WAN arayüzünü bul (NAT için gerekli):
export WAN_IFACE="$(ip route get 1.1.1.1 | awk '{print $5; exit}')"
echo "$WAN_IFACE"sudo apt update && sudo apt upgrade -yTemel paketleri kur:
sudo apt install -y curl git wget dnsutils iptables-persistent unbound ipset nftablessudo curl -sSL https://install.pi-hole.net | bashKurulum sırasında:
- Statik IP açık kalsın
- Upstream DNS seçimi geçici (bir sonraki adımda Unbound'a geçeceğiz)
Servis kontrolü:
pihole statusYönetim paneli:
http://SERVER_IP/admin
Root hints dosyasını al:
wget https://www.internic.net/domain/named.root -qO- | sudo tee /var/lib/unbound/root.hints >/dev/nullConfig oluştur:
sudo tee /etc/unbound/unbound.conf.d/pi-hole.conf >/dev/null <<'EOF'
server:
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
harden-glue: yes
harden-dnssec-stripped: yes
edns-buffer-size: 1472
prefetch: yes
qname-minimisation: yes
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
EOFDoğrula ve yeniden başlat:
sudo unbound-checkconf
sudo systemctl restart unbound
sudo systemctl enable unbound
sudo systemctl --no-pager --full status unboundDoğrudan test:
dig +short google.com @127.0.0.1 -p 5335IP dönüyorsa Unbound çalışıyor.
Pi-hole arayüzünde:
Settings -> DNS
Şunları yap:
- Tüm varsayılan public upstream DNS kutularını kapat
Custom 1 (IPv4)alanına127.0.0.1#5335yaz- Kaydet
Pi-hole test:
dig +short google.com @127.0.0.1IP dönüyorsa DNS zinciri doğru.
Router DHCP/LAN ayarlarında:
- Primary DNS:
SERVER_IP - Secondary DNS: boş (veya aynı IP)
Henüz gateway değiştirme. Önce istemcilerde internet ve reklam engelleme doğrulansın.
İstemci kontrolü:
nslookup pi.holeveya istemcinin DNS sunucusunun SERVER_IP olduğunu doğrula.
cd ~
git clone https://github.com/bol-van/zapret.git
cd zapret
sudo ./install_easy.shConfig düzenle:
sudo nano /opt/zapret/configAyarla (yoksa ekle):
MODE="nfqws"
NFQWS_OPT="--filter-tcp=80,443 --dpi-desync=fake --dpi-desync-ttl=3"Yeniden başlat ve doğrula:
sudo systemctl restart zapret
sudo systemctl enable zapret
sudo systemctl --no-pager --full status zapret
ps aux | grep -E "nfqws|zapret" | grep -v grepKritik not:
- Zapret sadece sunucudan geçen trafik üzerinde etki eder.
curl -L https://install.pivpn.io | sudo bashKurulum sırasında:
WireGuardseç51820varsayılan port uygundur- DNS sorulursa Pi-hole/yerel DNS seçeneğini seç
İstemci ekle:
pivpn add
pivpn -qrOpsiyonel:
pivpn listPort yonlendirme yoksa VPN genelde sadece ev icindeki LAN'da calisir.
Router NAT / Port Forwarding ekranina su kurali ekle:
| LAN IP | Baslangic Portu | Bitis Portu | Kaynak IP | Baslangic Portu | Bitis Portu | Protokol | Tanim | Etkin |
|---|---|---|---|---|---|---|---|---|
SERVER_IP |
51820 |
51820 |
0.0.0.0 |
51820 |
51820 |
UDP |
PiVPN |
Evet |
IPv4 forwarding'i kalıcı aç:
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-privacy-server.conf >/dev/null
sudo sysctl --system
sysctl net.ipv4.ip_forwardNAT kuralını ekle (tekrar eklemeye dayanıklı):
sudo iptables -t nat -C POSTROUTING -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || \
sudo iptables -t nat -A POSTROUTING -o "$WAN_IFACE" -j MASQUERADEKuralları kalıcılaştır:
sudo netfilter-persistent save
sudo netfilter-persistent reload
sudo iptables -t nat -S POSTROUTINGBunu yalnızca önceki adımlar tamamen doğrulandıktan sonra yap.
Router DHCP ayarlarında:
- Default Gateway/Router:
SERVER_IP - Primary DNS:
SERVER_IP
İstemcilerde DHCP lease yenile veya yeniden başlat.
İstemci doğrulaması:
- Linux/macOS:
ip route | head -n 2- Windows:
ipconfig /allBeklenen:
default via SERVER_IP
Sadece belirli cihaz/portları (80/443) SERVER_IP üzerinden yönlendir.
Sunucuda çalıştır:
dig +short cloudflare.com @127.0.0.1 -p 5335
dig +short cloudflare.com @127.0.0.1
sudo systemctl is-active unbound pihole-FTL zapret
sudo iptables -t nat -S POSTROUTINGLAN istemcisinde doğrula:
- DNS sunucusu =
SERVER_IP - DPI modu açıksa varsayılan gateway =
SERVER_IP - Normal siteler açılıyor, hedeflenen erişim davranışı doğru
VPN doğrulaması:
pivpn -cWireGuard ile bağlanıp doğrula:
- İnternet var
- DNS sorguları ev sunucusundan çözülüyor
Gateway değişiminden sonra internet kesilirse:
- Router DHCP gateway'i tekrar
ROUTER_IPyap - İstemcilerde DHCP yenile (veya yeniden başlat)
- Pi-hole çalışıyorsa DNS'i
SERVER_IPolarak bırakabilirsin
Unbound arıza kontrolü:
sudo unbound-checkconf
sudo journalctl -u unbound -n 100 --no-pagerZapret arıza kontrolü:
sudo journalctl -u zapret -n 200 --no-pager- Bu kurulumda daha sade DPI davranışı için IPv6 devre dışı bırakılır.
- Tek DNS mimarisi istiyorsan router/istemci/VPN tarafında public DNS girme.
- En stabil yaklaşım iki aşamalı geçiştir:
- DNS aşaması (Pi-hole + Unbound)
- Trafik aşaması (Gateway/PBR + Zapret)