Building a Pi-Hole DNS Firewall with Clash VPN Routing - Wireless, Leak-Free, No-Cable Setup
⚠️ WARNING: If you’re using a non-jailbroken iPhone, you need to use
Shadowrocket
to fit this Pi + Clash setup.
📱 Make sure your phone or router allows IPv6 to be disabled before you commit to this setup.
🪛 check this post for more troubleshooting.
So here’s the deal.
I’m still pretty fresh to Raspberry Pi and all that terminal kung fu, but I made the damn thing my personal digital sniper-silently blocking ads, hijacking DNS, and routing all my traffic through encrypted tunnels like a stealthy lil fox in black hoodie mode.
No more phucking ISP tracking.
No more random DNS leaks.
No more lame wired setups or HDMI tethering like it’s 2009.
I’m not even gonna dignify that rotten excuse of a firewall with a name. Starts with G, ends in get the PHUCK outta my packets. 🖕
We’re playin’ nice here - for the blog. But you know. You know.
This blog is for my fellow rebels who wanna:
- 📖 Actually understand how DNS works and why it’s not just some invisible ghost
- 🛠️ Make Pi-hole and Clash.Meta CLI run side-by-side like baddies with knives
- 🚫 Keep ad-blocking strong on DIRECT traffic, even when things get tangled with proxies
- 🔌 SSH into the Pi wirelessly, like a damn boss-no HDMI, no cables, no mess
- 🧠 Build a setup that routes traffic smart, not blindly through your PHREAKIN’ ISP
- 🔒 Avoid DNS leaks like the plague and keep your data where it belongs - with you
Trust me - once you taste this kind of setup freedom, you’ll never wanna go back to vanilla internet.
🚠 Enable SSH
This would suppose you already finished installation of your Pi OS.
First things first, before we start, make sure you enable the SSH service on your pi.
1. Make sure SSH is installed and running on the Pi
Now only this would need a HDMI to check, so boot Pi into GUI, let’s do a double check:
1
2
3
sudo systemctl enable ssh
sudo systemctl start ssh
sudo systemctl status ssh
You wanna see:
active (running)
in green.
If it says not found, just do:
1
2
sudo apt update
sudo apt install openssh-server
2. Get your Pi’s IP address
1
ip a
You’ll get like <PI_IP>
.
But to double-check:
1
hostname -I
3. SSH from your Main PC
Now you could get HDMI outta of your so darn sexy monitor. From your PC terminal:
1
ssh <USERNAME>@<PI_IP>
First time? Say yes to the fingerprint.
Then you’re in - your Pi, remotely seduced.
4. Configure static IP on Pi
If you don’t wanna touch the router:
Edit your Pi’s DHCP config:
1
sudo nano /etc/dhcpcd.conf
Scroll down and add:
1
2
3
4
interface wlan0
static ip_address=<PI_IP>/24
static routers=<Router_IP>
static domain_name_servers=1.1.1.1 8.8.8.8
Save it (Ctrl+O, Enter, Ctrl+X), then:
1
sudo reboot
🍍 install Pi-hole
🛠️ Step-by-step: Install Pi-hole
Run this one-liner install command (curl version):
1
curl -sSL https://install.pi-hole.net | bash
This will launch a guided terminal installer that will ask you:
💡 What you’ll be asked (and what to pick)
Static IP
✔️ click<Continue>
Choose an interface
🛜 Hitspace
to select<whlan0>
whatever your wireless interface is - since we’re going fully wireless with the Pi.
❌ Make sureeth0
is unselected (just one should be checked)DNS provider
🕘 Start with Quad9 (filtered, DNSSEC) since we don’t have a VPN set up yet.☁️ Once the VPN’s ready, switch to Cloudflare (DoH or DoT) for better privacy.
Blocklists
✔️ Use the default ones - you can add more later.Web admin interface?
✔️ YES, you want thisWeb server (lighttpd)?
✔️ YES - unless you already plan to use NginxLog queries?
✔️ YES - it’s fun seeing what gets blockedSelect a privacy mode for FTL
0️⃣ Stick with0 - Show everything
- Logs ALL domain requests + which client made them
✅ When it finishes
It’ll give you a cute little message like:
Web Interface: [http://
/admin](http:// /admin) Password: [your randomly generated web password]
Save that password or run:
1
pihole -a -p
to change it.
🔥 Now access it
Open a browser and go to:
1
http://<PI_IP>/admin
Login, poke around!
😽 Recommend Blocklist
In Lists
➡️ Address:
➡️ Add blocklist
You can add these URL one by one. ⬇️
https://adaway.org/hosts.txt
https://big.oisd.nl/
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
https://raw.githubusercontent.com/PolishFiltersTeam/KADhosts/master/KADhosts.txt
https://urlhaus.abuse.ch/downloads/hostfile/
https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext
https://raw.githubusercontent.com/Isaaker/Spotify-AdsList/main/Lists/pi-hole.txt
then
1
sudo pihole -g
or Tools
➡️ Update Gravity
➡️ Update
📱 Set your Phone/router’s dns
✅ Set DNS manually in phone Wi-Fi settings
Go to your phone’s Wi-Fi > Network details > IP settings → switch to Static or Manual and set:
- DNS1:
<PI_IP>
(your Pi-hole) - DNS2:
<PI_IP>
🧠 Pro Tip: Don’t forget to forget the network first, then reconnect with new DNS.
💡 run a VPN on your Pi while still using Pi-hole as DNS
💬 TL;DR first
You can run a VPN (like Clash / V2RayN) on your Pi while still using Pi-hole as DNS for all your devices, so:
- You block ads + trackers 🎯
- You don’t need to run VPN on every device 🧘♀️
✅ Part 1: Confirmed - Pi-hole works perfectly without VPN
Try to connect some site already blocked by pi-hole via your phone/pc. If that works, that’s great.
Which means your DNS chain looks like this:
1
[Device] --> [Pi-hole @ <PI_IP>] --> [Quad9 or other upstream]
✅ Part 2: Why VPN breaks Pi-hole blocking
When you run VPN on your device, one of two things happens:
Situation | Result |
---|---|
VPN forces DNS over its own server | ❌ Pi-hole is bypassed entirely |
VPN sends all traffic (incl DNS) through its tunnel | 😢 Local ISP is bypassed, but ad blocking is gone |
So you’re either free but unprotected, or blocked but adless. That sucks. We want BOTH.
🔥 Part 3: THE MASTERPLAN 💻🌍
What you really want is this:
💡 Use the Pi as a centralized VPN router that:
- Runs Clash
- Routes all international traffic through the VPN
- Keeps DNS local so Pi-hole still blocks stuff
- Protects all devices in your network - without installing VPN apps on them
🍓 Dream Setup Diagram
1
2
3
4
5
6
7
8
9
[All Devices]
↓ (DNS: <PI_IP>)
[Router]
↓
[Raspberry Pi]
├─ Pi-hole → handles DNS (blocks ads)
└─ Clash → handles traffic routing
↳ sites in rule → VPN
↳ Local stuff → direct
🛠 How to Set It Up
✅ Install Clash on the Pi
Go to https://github.com/MetaCubeX/mihomo/releases
download a latest release
e.g. :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Download the binary, and don't forget to check out the latest release yourself!
wget https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/mihomo-linux-arm64-alpha-34de62d.gz
# Unpack it
gunzip mihomo-linux-arm64-alpha-34de62d.gz
# Rename it for convenience
mv mihomo-linux-arm64-alpha-34de62d clash
# Make it executable
chmod +x clash
# Move it into place
sudo mv clash /usr/local/bin
Now try:
1
clash -v
You should see something like:
1
Clash.Meta (mihomo) vX.XX...
📁 Add your node/subscription file
Drop in your config here:
1
sudo nano /root/.config/mihomo/config.yaml
Paste in your config or YAML from your GUI-exported setup.
Or copy the config into your Pi from your PC:
On PC:
1
2
scp <yaml_path> raspberrypi@<PI_IP>:~/config-temp.yaml
SHH into Pi, then move with sudo:
1
sudo mv ~/config-temp.yaml /root/.config/mihomo/config.yaml
change dns config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
mixed-port: 7890
redir-port: 7892
tproxy-port: 7893
allow-lan: true
bind-address: '*'
mode: rule
log-level: info
external-controller: '0.0.0.0:9090'
secret: "<your_password>"
dns:
enable: true
ipv6: false
listen: ':1053'
default-nameserver:
- <PI_IP>
enhanced-mode: fake-ip
nameserver:
- <PI_IP>
proxy-server-nameserver:
- <PI_IP>
fallback:
- <PI_IP>
fallback-filter:
geoip: true
geoip-code: "<your_country_code>"
geosite:
- "<your_region_blocklist_or_category>"
- Applying
<PI_IP>
inproxy-server-nameserver
andfallback
only work IF Pi-hole resolves external domains reliably.- Use these ONLY if your Pi-hole’s upstream DNS isn’t trapped behind a firewall or failing requests.
Feel free to change
<your_password>
.
Run clash and select your node
Now ran
1
clash
✅ Pick your node
List groups:
1
curl -s -H "Authorization: Bearer <your_password>" http://127.0.0.1:9090/proxies | jq '.proxies | keys'
Check what’s inside
<Node Provider>
:1
curl -s -H "Authorization: Bearer <your_password>" http://127.0.0.1:9090/proxies/<node_provider> | jq '.all'
Switch to whatever node you want, like:
1 2 3
curl -X PUT -H "Authorization: Bearer <your_password>" -H "Content-Type: application/json" \ -d '{"name": "<node_name>"}' \ http://127.0.0.1:9090/proxies/<node_provider>
Feel free to change
<your_password>
,<node_provider>
and<node_name>
to your own values.
✅ Verify it worked
1
curl --proxy http://127.0.0.1:7890 https://api.myip.com
If it says your proxy IP - then yes, you’re surfing the free web.
✅ Make Clash/Mihomo run in the background (so terminal can close) - Use systemd
for full background service
Let’s make Mihomo auto-start on boot like a real daemon
Create a systemd service:
1
sudo nano /etc/systemd/system/mihomo.service
Paste:
1 2 3 4 5 6 7 8 9 10 11
[Unit] Description=Mihomo Service After=network.target [Service] ExecStart=/usr/local/bin/clash -f /root/.config/mihomo/config.yaml Restart=on-failure User=root [Install] WantedBy=multi-user.target
Enable & start it:
1 2 3
sudo systemctl daemon-reexec sudo systemctl enable mihomo sudo systemctl start mihomo
Check status:
1
sudo systemctl status mihomo
🔥 IP forwarding & iptables Setup
Step 1: Enable IP forwarding on your Pi
1
2
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
sudo sysctl -w net.ipv4.ip_forward=1
Make it permanent by editing:
1
sudo nano /etc/sysctl.conf
Uncomment or add:
1
net.ipv4.ip_forward = 1
Step 2: Setup iptables rules
You’re gonna redirect traffic from other devices to Clash.
Example iptables setup:
1
2
3
sudo iptables -t nat -N CLASH
sudo iptables -t nat -A CLASH -p tcp -j REDIRECT --to-ports 7892
sudo iptables -t nat -A PREROUTING -s 192.168.1.0/24 -p tcp -j CLASH
Replace
192.168.1.0/24
with your LAN subnet
💡 This redirects all TCP traffic from LAN devices through Clash’s redir port.
😼 Final Step: Set your Raspberry Pi as default gateway for your phone
🧪 Manual Gateway Setup on your Phone
On your phone’s Wi-Fi settings:
- Choose your current network
- Edit IP Settings → set to Static
Then set:
Field Value IP Address Same as your phone already has (e.g. 192.168.1.200
)Gateway <PI_IP>
← your PiDNS <PI_IP>
← your Pi-hole- Save and reconnect
🔁 Now test
1
curl https://api.myip.com
From your phone (or go to ipinfo.io), and…
🥁 You should see your proxy IP, NOT your local ISP.
☑️ Switch to Cloudflare
Now the VPN’s ready, switch your Pi-hole DNS setting to Cloudflare (DNSSEC) for better privacy.
In Settings
➡️ DNS
➡️ Check these two boxes
🌷 What just happened?
Before:
1
Phone → Router → internet
After:
1
Phone → Pi (iptables trap!) → Mihomo → Proxy node → Free internet
🔍 What DNS Really Does
DNS = Domain Name System It’s like the phonebook of the internet-turning names like spotify.com
into IP addresses like 35.186.224.25
so your device knows where to connect.
💡 So When You Use Pi-hole as DNS
- Pi-hole becomes the phonebook 📖
Anyone using your Pi-hole to resolve DNS (like your Phone) is asking your Pi-hole:
“Yo, what’s the IP of
doubleclick.net
?”
And your Pi-hole can:
- 🧱 Block domains you blacklist
- 🕵️ Log every domain someone asks
- 🔄 Forward to upstream DNS if needed
🔓 What’s a DNS Leak Then?
You can check BrowserLeaks or DnsLeakTest to see if you’re experiencing a DNS leak.
A DNS leak happens when:
- Your device is supposed to use your DNS server (like your Pi-hole)
But instead, it sends DNS queries elsewhere, usually:
- Your ISP
- Google (8.8.8.8 or
74.x.x.x
) - Some weird encrypted DNS behind your back (DoH/DoT)
This leaks your browsing activity, even if you’re on a VPN or using a filter like Pi-hole.
🧱 OSI Model Breakdown
📡 Layer 3 - Network Layer
- This is where IP routing lives.
- When you change the gateway to your Pi, you’re redirecting packets to go through your VPN tunnel on the Pi.
- That means: You’re redirecting traffic away from your ISP before it ever reaches them.
💬 Result: Your traffic doesn’t even touch the phucking local ISP. Pi hijacks it, puts it into the VPN, and tunnels it safely like a boss.
✨ Layer 7 - Application Layer
- DNS lives up here (yes, even though it feels low-level).
- Your Pi-hole acts here: intercepts DNS queries and either resolves or blocks them.
- Ads blocked? Domains rewritten? That’s Layer 7 magic.
Combined with Layer 7 control (DNS, ad-block, tracking protection), it’s like you built your own damn freedom stack 🗽
🛠️ So What You’ve Done Is
Layer | Action |
---|---|
3 | Redirected TCP/IP traffic to your VPN tunnel on Pi |
7 | Intercepted DNS queries and blocked/sanitized them via Pi-hole |
Troubleshoot
If you’ve ever used sudo pkill clash
sudo systemctl stop mihomo
, then you probably got booted from SSH like a phreakin’ hot potato. 🥔 (Don’t worry, we’ve all been that potato. )
like:
1
2
ssh raspberrypi@<PI_IP>
ssh: connect to host <PI_IP> port 22: Connection refused
🛠️ How to fix Connection refused
Option 1: Unplug and replug Pi, if you already set Mihomo to auto-start as shown above.
Option 2: HDMI Cable
✅ Step 1: Physically access the Pi
Plug it into a monitor + keyboard, or connect via serial console if you have UART cables.
Log in locally.
✅ Step 2: Re-enable networking
Run this to see if networking still exists:
1
ip a
If wlan0
or eth0
doesn’t have an IP like 192.168.1.184
, you can restart DHCP:
1
2
3
sudo dhclient wlan0
# Or for ethernet:
sudo dhclient eth0
If you have a static IP configured in /etc/dhcpcd.conf
, check that it’s not misconfigured or being ignored.
✅ Step 3: Restart SSH
1
sudo systemctl restart ssh
Then try to connect again from your PC:
1
ssh raspberrypi@<PI_IP>
✅ Step 4: Restart Clash safely
1
sudo systemctl start mihomo
And test with:
1
curl --proxy http://127.0.0.1:7890 https://api.myip.com
✅ Step 5: reapply your iptables rules after killing Clash
When you run:
1
sudo pkill clash
your iptables rules (especially custom NAT chains) may not persistent:
So reapply this:
1
2
3
4
5
6
7
8
# 💣 Flush existing NAT rules to avoid duplicates
sudo iptables -t nat -F
sudo iptables -t nat -X
# 🔁 Recreate CLASH chain
sudo iptables -t nat -N CLASH
sudo iptables -t nat -A CLASH -p tcp -j REDIRECT --to-ports 7892
sudo iptables -t nat -A PREROUTING -s 192.168.100.0/24 -p tcp -j CLASH
Then traffic from all LAN devices will be transparently routed through your Clash TProxy setup again.
✅ Step 5: Make It Stick (Persistence Setup)
- Install persistent rules
1
2
sudo apt update
sudo apt install iptables-persistent
Say yes when it asks to save your current rules.
🔥 Step 2: Save your working rules to disk
1
sudo iptables-save | sudo tee /etc/iptables/rules.v4
Wanna check them again?
1
sudo cat /etc/iptables/rules.v4
Now they’ll load on every reboot, even if you’re sleepy and forget 😪
👻 If DNS breaks, ads return, or VPN feels flaky:
- Restart
mihomo
& Pi-hole- Check
/etc/resolv.conf
- Clear device DNS cache
- Or just… turn it off and back on
- Maybe you’d like to check this Troubleshooting out? 😿