The DPI bypass tunnel from the previous chapters works perfectly all my traffic goes through an encrypted HTTPS WebSocket to my home server, and the university firewall thinks I'm just browsing my portfolio. But I realized there was one thing still leaking: DNS queries.

The Leak

In the original DPI bypass scripts, I routed DNS queries directly to Cloudflare's 1.1.1.1 bypassing the tunnel. This was necessary because DNS through tun2socks wasn't working reliably. The problem is that these DNS queries travel as plain text UDP on port 53. The university network can see every single domain I'm resolving.

The actual traffic to those domains is encrypted through the Xray tunnel, so they can't see or block it. But they can see that I'm looking up store.steampowered.com and discord.com and draw their own conclusions.

For gaming at university, this is mostly a cosmetic issue they block based on traffic patterns, not DNS lookups. But it bothered me. The whole point of the tunnel is invisibility. One leak undermines the concept.

What I Tried (And Failed)

Attempt 1: DNS Through the Tunnel

The "obvious" fix: just don't route 1.1.1.1 directly. Let DNS queries go through tun0 → tun2socks → Xray like everything else.

Didn't work. tun2socks converts UDP DNS queries to go through a SOCKS5 proxy, which then goes through Xray's WebSocket (TCP). The UDP-to-TCP conversion chain is too fragile DNS queries timed out and nothing resolved.

Attempt 2: Xray's Built-in DNS

Xray has a dokodemo-door inbound that can handle DNS. I configured it to listen on 127.0.0.1:53 and route DNS through the tunnel.

Also didn't work. Same underlying issue: DNS over the multi-layer proxy chain was unreliable.

Attempt 3: stubby (DNS-over-TLS)

Installed stubby as a local DNS-over-TLS resolver. The idea: DNS queries go to stubby on localhost, stubby encrypts them and sends to Cloudflare via TLS on port 853.

This one actually worked! But port 853 is a dead giveaway, it's the designated DNS-over-TLS port. Any DPI system (including China's Great Firewall) can instantly identify and block it. Using port 853 is basically announcing "I'm encrypting my DNS because I have something to hide."

Also, Debian's default stubby config included a bunch of academic test servers with outdated TLS pins that kept failing. After cleaning those up, it worked but the port 853 issue remained.

Attempt 4: cloudflared (DNS-over-HTTPS)

Cloudflare's own cloudflared tool can act as a local DNS-over-HTTPS proxy. DNS over port 443 indistinguishable from normal HTTPS.

Except Cloudflare removed this feature in version 2026.2.0. The proxy-dns command just prints an error and exits. Thanks, Cloudflare.

What Actually Worked: dnscrypt-proxy

Fifth attempt. dnscrypt-proxy is a mature DNS proxy that supports DNS-over-HTTPS natively. It listens on localhost, receives plain DNS queries from the system, and forwards them encrypted over HTTPS (port 443) to Cloudflare.

sudo apt install dnscrypt-proxy

Of course it didn't work out of the box either (nothing ever does on this server ahahah). Debian packages it with systemd socket activation, which means the socket unit binds to 127.0.2.1:53 instead of 127.0.0.1:53, and the actual service doesn't start properly when triggered by the socket.

The fix: disable the socket activation entirely and create a clean service file that runs dnscrypt-proxy directly.

sudo systemctl stop dnscrypt-proxy.socket
sudo systemctl stop dnscrypt-proxy
sudo systemctl disable dnscrypt-proxy.socket
sudo systemctl disable dnscrypt-proxy

Configured it to use only Cloudflare (removed all the default academic test servers that kept timing out):

# /etc/dnscrypt-proxy/dnscrypt-proxy.toml
listen_addresses = ['127.0.0.1:53']
server_names = ['cloudflare']

Created a clean systemd service:

[Unit]
Description=DNSCrypt Proxy (DoH)
After=network.target

[Service]
ExecStart=/usr/sbin/dnscrypt-proxy -config /etc/dnscrypt-proxy/dnscrypt-proxy.toml
Restart=always
User=root

[Install]
WantedBy=multi-user.target

Tested it manually first:

$ sudo dnscrypt-proxy -config /etc/dnscrypt-proxy/dnscrypt-proxy.toml
[NOTICE] dnscrypt-proxy 2.1.8
[NOTICE] Now listening to 127.0.0.1:53 [UDP]
[NOTICE] Now listening to 127.0.0.1:53 [TCP]
[NOTICE] [cloudflare] OK (DoH) - rtt: 65ms
[NOTICE] dnscrypt-proxy is ready - live servers: 1

65ms round trip to Cloudflare. Fast enough. And it uses HTTPS on port 443, completely invisible to DPI.

The Updated Scripts

The key insight: dnscrypt-proxy needs to reach Cloudflare's DoH servers directly (not through the tunnel), but the DNS queries themselves are encrypted via HTTPS. So we route the DoH endpoint IPs directly and everything else through the tunnel.

The on script now:

  1. Resolves mosearc.eu and one.one.one.one (Cloudflare DoH) before starting the tunnel
  2. Starts dnscrypt-proxy and Xray
  3. Routes server IPs and DoH IPs directly
  4. Points system DNS to 127.0.0.1 (dnscrypt-proxy)
  5. Creates tun0 and routes everything else through it

The off script stops dnscrypt-proxy, removes routes, and restores the original DNS config from a backup.

The Final Traffic Flow

What the university network now sees:

Connection What it looks like Can they read it?
DNS queries HTTPS to Cloudflare (port 443) No
All other traffic HTTPS WebSocket to mosearc.eu (port 443) No
Domain lookups Hidden inside HTTPS No
Gaming traffic Hidden inside HTTPS No

Everything is HTTPS on port 443. Two encrypted connections one for DNS, one for everything else. Both look like someone browsing normal websites. The DPI system cannot distinguish this from a student doing homework.

"Five attempts, four failures, one solution. The debugging was harder than the actual fix."

Why Each DNS Method Failed or Worked

Method Result Why
Plain DNS through tunnel ❌ Failed UDP-over-TCP conversion unreliable
Xray dokodemo-door ❌ Failed Same UDP-over-TCP issue
stubby (DNS-over-TLS) ⚠️ Worked but visible Port 853 is a DPI red flag
cloudflared (DoH) ❌ Deprecated Feature removed in 2026.2.0
dnscrypt-proxy (DoH) ✅ Works Port 443 HTTPS, invisible to DPI

What's Next

  • V2Ray mobile client setup
  • UPS (crash experiment still running)
  • Containerization with Docker
  • Self-hosted drive with NAS
  • VPS reverse proxy
  • Mail server
  • Local LLM