The Problem
If you host a website or web application on popular hosting providers (e.g. Hetzner), users in certain regions may be unable to access it.
Some ISPs, operating under government directives, use deep packet inspection (DPI) devices that systematically throttle connections to foreign hosting providers. Rather than outright blocking, they limit TCP connections to approximately 16KB per connection, making websites effectively unusable. The HTML page might partially load, but CSS, JavaScript, images, and fonts all fail — leaving users with a broken, unstyled page.
This isn't a problem you can solve by asking your users to "just use a VPN." Your website should work transparently, without requiring any special action from visitors.
This guide walks you through a battle-tested solution for exactly this problem.
The Solution: Reverse Proxy + AmneziaWG Tunnel
The architecture that actually works:
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ User in │ HTTPS │ In-Region VPS │ AWG │ Your Origin │
│ restricted │────────:arrow_forward:│ (Nginx reverse │─tunnel─:arrow_forward:│ Server │
│ region │ │ proxy) │ (UDP) │ (e.g. Hetzner) │
└──────────────┘ └──────────────────┘ └──────────────────┘
No DPI issues │ │
(domestic traffic) │ Encrypted tunnel │
│ disguised as QUIC │
│ DPI can't inspect │Why this works:
- User → In-Region VPS: Domestic traffic within the same country — no throttling
- In-Region VPS → Your origin: Traffic flows through an AmneziaWG tunnel on UDP port 443, which looks like QUIC (HTTP/3) to DPI systems. The DPI can't identify the destination or inspect the content
- Completely transparent — your users don't need to install anything or change any settings
| What is AmneziaWG? |
|---|
| AmneziaWG is a fork of WireGuard that adds DPI-resistant obfuscation. It modifies packet headers, randomizes handshake sizes, and can disguise traffic as common UDP protocols like QUIC. It was built specifically to defeat sophisticated DPI systems. Key parameters:
|
Who This Tutorial Is For
This tutorial is for developers and operators who host web applications on mainstream providers (e.g. Hetzner) and need to make them accessible to users in regions with ISP-level DPI throttling — without migrating their entire infrastructure.
Why not just host the website inside the restricted region?
Since this solution requires renting a small VPS inside the restricted region anyway, a natural question is: why not skip the complexity and host the application there directly?
| Description | |
|---|---|
| Your application likely needs more resources than a cheap regional VPS offers. | The in-region VPS in this guide only needs 1 vCPU and 1GB RAM — it runs nothing but Nginx and a tunnel endpoint. Your actual application, with its database, background workers, build tools, and runtime, likely needs significantly more. Mainstream providers give you 4-8x the resources for the same price, along with managed databases, object storage, automated backups, monitoring, and mature deployment tooling (Docker, CI/CD, APIs) that smaller regional providers typically lack. |
| Hosting your data inside a restricted region creates legal exposure. | If your application and database live on a server inside the restricted country, local authorities can physically access, seize, or compel disclosure of that data. With the reverse proxy architecture described here, the in-region VPS is completely stateless — it holds no application data, no database, no user information. If it were seized or inspected, there's nothing to find beyond an Nginx config and a tunnel endpoint. Your actual data remains on infrastructure in a jurisdiction you're more comfortable with. |
| A note on tunnel security: | The AmneziaWG tunnel is configured with AllowedIPs = 10.10.0.2/32, meaning the in-region VPS can only send traffic to a single IP on a private subnet. It cannot scan your origin server's network or access other services. The tunnel is point-to-point and encrypted — it does not grant the in-region VPS any privileged access beyond what Nginx proxies through it. That said, the in-region VPS does terminate user-facing TLS, so it can observe unencrypted request/response content in transit. If this is a concern, consider end-to-end encryption at the application layer. |
| One in-region VPS can serve as a proxy for multiple projects. | The reverse proxy architecture is not limited to a single website. You can configure Nginx on the same in-region VPS with multiple server blocks, each proxying a different domain to a different origin server — all through the same AmneziaWG tunnel (or separate tunnels to different origins). This means a single $4-10/month VPS can make dozens of your projects accessible from the restricted region, making the per-project cost negligible. |
What Doesn't Work
Before diving into the solution, here's what we tried and why it failed — so you don't waste time going down these paths.
| Description | ||
|---|---|---|
| CDN Services (Cloudflare, Gcore, etc.) | ❌ | Major CDN providers' IP ranges are often throttled by the same DPI systems. Even CDN providers with Points of Presence inside the restricted region may still route traffic through monitored infrastructure. Cloudflare's use of TLS ECH (Encrypted Client Hello) is also specifically targeted by some DPI implementations. |
| Simple Reverse Proxy on an In-Region VPS (Partial) | ⚠️ | Renting a VPS inside the restricted region and setting up Nginx as a reverse proxy solves the client → proxy path (domestic traffic isn't throttled), but the proxy → your origin server connection still travels through the DPI infrastructure. Since your origin server is on a "foreign" hosting provider, the proxy-to-origin traffic gets the same 16KB throttle. This was a frustrating discovery — the proxy successfully receives full responses from the origin, but the DPI sitting between the in-region VPS and the outside world still throttles the connection. |
| Standard WireGuard VPN Tunnel | ❌ | WireGuard has fixed packet headers and predictable packet sizes that create an easily recognizable signature. Modern DPI systems identify and block WireGuard connections within seconds. |
Prerequisites
You'll need:
- Your origin server running your web application (this guide assumes Linux with a reverse proxy like Traefik, Caddy, or Nginx)
- A VPS inside the restricted region from a provider that accepts international payment (see section below)
- A domain name with DNS you can modify
- Basic SSH and Linux command line knowledge
| Finding a VPS Provider Inside the Restricted Region |
|---|
This is often the hardest part. Most local hosting providers in restricted regions only accept domestic payment methods. Look for providers that accept:
|
Step 1 - Set Up the In-Region VPS
SSH into your new VPS and install the essentials:
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx certbot python3-certbot-nginxStep 2 - DNS Configuration
Point your domain (or subdomain) to the in-region VPS IP address:
yourapp.example.com → A record → <IN_REGION_VPS_IP>If you have other subdomains (API, monitoring, etc.) that don't need access from the restricted region, leave them pointing to your origin server.
Step 3 - Obtain SSL Certificate on the In-Region VPS
sudo certbot --nginx -d yourapp.example.comThis gives you a Let's Encrypt certificate for the client-facing HTTPS connection.
Step 4 - Configure Nginx Reverse Proxy
Create the Nginx configuration. We'll initially point it to the origin server's public IP — we'll switch it to the tunnel IP later.
First, define your domain and origin server IP:
DOMAIN=yourapp.example.com
ORIGIN_IP=123.123.123.123Then create the config file:
cat > /etc/nginx/sites-available/your-site << EOF
server {
listen 80;
server_name ${DOMAIN};
# Pass ACME challenges through to your origin server
# so its own Let's Encrypt cert can still auto-renew
location /.well-known/acme-challenge/ {
proxy_pass http://${ORIGIN_IP};
proxy_set_header Host ${DOMAIN};
}
location / {
return 301 https://\$host\$request_uri;
}
}
server {
listen 443 ssl;
server_name ${DOMAIN};
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
# Cache fonts (30 days, immutable)
location ~* \.(woff|woff2|ttf|otf|eot) {
proxy_pass https://${ORIGIN_IP}:443;
proxy_ssl_server_name on;
proxy_ssl_name ${DOMAIN};
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;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Cache images, CSS, JS (7 days)
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|css|js) {
proxy_pass https://${ORIGIN_IP}:443;
proxy_ssl_server_name on;
proxy_ssl_name ${DOMAIN};
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;
expires 7d;
add_header Cache-Control "public";
}
# Main reverse proxy
location / {
proxy_pass https://${ORIGIN_IP}:443;
proxy_ssl_server_name on;
proxy_ssl_name ${DOMAIN};
proxy_ssl_session_reuse on;
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;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer settings (optimized for 1GB RAM VPS)
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 8k;
proxy_busy_buffers_size 16k;
}
client_max_body_size 50m;
}
EOFEnable it:
sudo ln -sf /etc/nginx/sites-available/your-site /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginxAt this point, your site may partially work from the restricted region — the client-to-proxy path is fine, but the proxy-to-origin path is still throttled. The next steps fix that.
Step 5 - Install AmneziaWG on Both Servers
Run this on both your origin server and the in-region VPS:
# Prerequisites
sudo apt install -y software-properties-common linux-headers-$(uname -r)
# Enable source repositories (needed for kernel module build)
sudo sed -i 's/^# deb-src/deb-src/' /etc/apt/sources.list
# Install AmneziaWG
sudo add-apt-repository -y ppa:amnezia/ppa
sudo apt update -y
sudo apt install -y amneziawg amneziawg-tools
# Load kernel module and verify
sudo modprobe amneziawg
lsmod | grep amneziaYou should see amneziawg in the module list. If not, check that your kernel headers match your running kernel.
Step 6 - Generate Keys
On your origin server:
mkdir -p /etc/amnezia/amneziawg
sudo bash -c 'awg genkey | tee /etc/amnezia/amneziawg/server_private.key | awg pubkey > /etc/amnezia/amneziawg/server_public.key'
sudo bash -c 'awg genpsk > /etc/amnezia/amneziawg/preshared.key'
sudo chmod 600 /etc/amnezia/amneziawg/server_private.key /etc/amnezia/amneziawg/preshared.key
echo "=== Copy these to the in-region VPS ==="
echo "Origin Public Key: $(sudo cat /etc/amnezia/amneziawg/server_public.key)"
echo "Preshared Key: $(sudo cat /etc/amnezia/amneziawg/preshared.key)"On the in-region VPS:
mkdir -p /etc/amnezia/amneziawg
sudo bash -c 'awg genkey | tee /etc/amnezia/amneziawg/client_private.key | awg pubkey > /etc/amnezia/amneziawg/client_public.key'
sudo chmod 600 /etc/amnezia/amneziawg/client_private.key
echo "=== Copy this to the origin server ==="
echo "VPS Public Key: $(sudo cat /etc/amnezia/amneziawg/client_public.key)"You'll need to exchange these keys between the two servers.
| Copy | From | To |
|---|---|---|
/etc/amnezia/amneziawg/preshared.key |
origin | in-region VPS |
/etc/amnezia/amneziawg/server_public.key |
origin | in-region VPS |
/etc/amnezia/amneziawg/client_public.key |
in-region VPS | origin |
Copy files to other server:
# On the origin server, run:
sudo scp /etc/amnezia/amneziawg/preshared.key <user>@<IN_REGION_VPS_IP>:~/
sudo scp /etc/amnezia/amneziawg/server_public.key <user>@<IN_REGION_VPS_IP>:~/
# On the in-region VPS, run:
sudo scp /etc/amnezia/amneziawg/client_public.key <user>@<ORIGIN_SERVER_IP>:~/Move copied files into correct directory
# On the origin server, run:
sudo mv ~/client_public.key /etc/amnezia/amneziawg/
# On the in-region VPS, run:
sudo mv ~/preshared.key /etc/amnezia/amneziawg/
sudo mv ~/server_public.key /etc/amnezia/amneziawg/Step 7 - Configure AmneziaWG on the Origin Server
Create the config file:
sudo bash -c 'cat > /etc/amnezia/amneziawg/awg0.conf << EOF
[Interface]
Address = 10.10.0.1/24
ListenPort = 443
PrivateKey = $(cat /etc/amnezia/amneziawg/server_private.key)
MTU = 1280
# Obfuscation parameters (must match on both sides)
Jc = 4
Jmin = 50
Jmax = 1000
S1 = 55
S2 = 155
H1 = 1953034736
H2 = 752945292
H3 = 3945748733
H4 = 1666444888
# QUIC protocol signature — makes the tunnel look like QUIC (HTTP/3)
# Without this, sophisticated DPI detects and kills the tunnel in seconds
I1 = <b 0xc70000000108ce1bf31eec7d93360000449e227e4596ed7f75c4d35ce31880b4133107c822c6355b51f0d7c1bba96d5c210a48aca01885fed0871cfc37d59137d73b506dc013bb4a13c060ca5b04b7ae215af71e37d6e8ff1db235f9fe0c25cb8b492471054a7c8d0d6077d430d07f6e87a8699287f6e69f54263c7334a8e144a29851429bf2e350e519445172d36953e96085110ce1fb641e5efad42c0feb4711ece959b72cc4d6f3c1e83251adb572b921534f6ac4b10927167f41fe50040a75acef62f45bded67c0b45b9d655ce374589cad6f568b8475b2e8921ff98628f86ff2eb5bcce6f3ddb7dc89e37c5b5e78ddc8d93a58896e530b5f9f1448ab3b7a1d1f24a63bf981634f6183a21af310ffa52e9ddf5521561760288669de01a5f2f1a4f922e68d0592026bbe4329b654d4f5d6ace4f6a23b8560b720a5350691c0037b10acfac9726add44e7d3e880ee6f3b0d6429ff33655c297fee786bb5ac032e48d2062cd45e305e6d8d8b82bfbf0fdbc5ec09943d1ad02b0b5868ac4b24bb10255196be883562c35a713002014016b8cc5224768b3d330016cf8ed9300fe6bf39b4b19b3667cddc6e7c7ebe4437a58862606a2a66bd4184b09ab9d2cd3d3faed4d2ab71dd821422a9540c4c5fa2a9b2e6693d411a22854a8e541ed930796521f03a54254074bc4c5bca152a1723260e7d70a24d49720acc544b41359cfc252385bda7de7d05878ac0ea0343c77715e145160e6562161dfe2024846dfda3ce99068817a2418e66e4f37dea40a21251c8a034f83145071d93baadf050ca0f95dc9ce2338fb082d64fbc8faba905cec66e65c0e1f9b003c32c943381282d4ab09bef9b6813ff3ff5118623d2617867e25f0601df583c3ac51bc6303f79e68d8f8de4b8363ec9c7728b3ec5fcd5274edfca2a42f2727aa223c557afb33f5bea4f64aeb252c0150ed734d4d8eccb257824e8e090f65029a3a042a51e5cc8767408ae07d55da8507e4d009ae72c47ddb138df3cab6cc023df2532f88fb5a4c4bd917fafde0f3134be09231c389c70bc55cb95a779615e8e0a76a2b4d943aabfde0e394c985c0cb0376930f92c5b6998ef49ff4a13652b787503f55c4e3d8eebd6e1bc6db3a6d405d8405bd7a8db7cefc64d16e0d105a468f3d33d29e5744a24c4ac43ce0eb1bf6b559aed520b91108cda2de6e2c4f14bc4f4dc58712580e07d217c8cca1aaf7ac04bab3e7b1008b966f1ed4fba3fd93a0a9d3a27127e7aa587fbcc60d548300146bdc126982a58ff5342fc41a43f83a3d2722a26645bc961894e339b953e78ab395ff2fb854247ad06d446cc2944a1aefb90573115dc198f5c1efbc22bc6d7a74e41e666a643d5f85f57fde81b87ceff95353d22ae8bab11684180dd142642894d8dc34e402f802c2fd4a73508ca99124e428d67437c871dd96e506ffc39c0fc401f666b437adca41fd563cbcfd0fa22fbbf8112979c4e677fb533d981745cceed0fe96da6cc0593c430bbb71bcbf924f70b4547b0bb4d41c94a09a9ef1147935a5c75bb2f721fbd24ea6a9f5c9331187490ffa6d4e34e6bb30c2c54a0344724f01088fb2751a486f425362741664efb287bce66c4a544c96fa8b124d3c6b9eaca170c0b530799a6e878a57f402eb0016cf2689d55c76b2a91285e2273763f3afc5bc9398273f5338a06d>
[Peer]
PublicKey = $(cat /etc/amnezia/amneziawg/client_public.key)
PresharedKey = $(cat /etc/amnezia/amneziawg/preshared.key)
AllowedIPs = 10.10.0.2/32
EOF'
sudo chmod 600 /etc/amnezia/amneziawg/awg0.confOpen the firewall:
sudo ufw allow 443/udp
sudo ufw allow in on awg0Step 8 - Configure AmneziaWG on the In-Region VPS
Replace <ORIGIN_SERVER_IP> with the IP of your server.
sudo bash -c 'cat > /etc/amnezia/amneziawg/awg0.conf << EOF
[Interface]
Address = 10.10.0.2/24
PrivateKey = $(cat /etc/amnezia/amneziawg/client_private.key)
MTU = 1280
# Obfuscation parameters — must match origin server exactly
Jc = 4
Jmin = 50
Jmax = 1000
S1 = 55
S2 = 155
H1 = 1953034736
H2 = 752945292
H3 = 3945748733
H4 = 1666444888
# Same QUIC signature as origin
I1 = <b 0xc70000000108ce1bf31eec7d93360000449e227e4596ed7f75c4d35ce31880b4133107c822c6355b51f0d7c1bba96d5c210a48aca01885fed0871cfc37d59137d73b506dc013bb4a13c060ca5b04b7ae215af71e37d6e8ff1db235f9fe0c25cb8b492471054a7c8d0d6077d430d07f6e87a8699287f6e69f54263c7334a8e144a29851429bf2e350e519445172d36953e96085110ce1fb641e5efad42c0feb4711ece959b72cc4d6f3c1e83251adb572b921534f6ac4b10927167f41fe50040a75acef62f45bded67c0b45b9d655ce374589cad6f568b8475b2e8921ff98628f86ff2eb5bcce6f3ddb7dc89e37c5b5e78ddc8d93a58896e530b5f9f1448ab3b7a1d1f24a63bf981634f6183a21af310ffa52e9ddf5521561760288669de01a5f2f1a4f922e68d0592026bbe4329b654d4f5d6ace4f6a23b8560b720a5350691c0037b10acfac9726add44e7d3e880ee6f3b0d6429ff33655c297fee786bb5ac032e48d2062cd45e305e6d8d8b82bfbf0fdbc5ec09943d1ad02b0b5868ac4b24bb10255196be883562c35a713002014016b8cc5224768b3d330016cf8ed9300fe6bf39b4b19b3667cddc6e7c7ebe4437a58862606a2a66bd4184b09ab9d2cd3d3faed4d2ab71dd821422a9540c4c5fa2a9b2e6693d411a22854a8e541ed930796521f03a54254074bc4c5bca152a1723260e7d70a24d49720acc544b41359cfc252385bda7de7d05878ac0ea0343c77715e145160e6562161dfe2024846dfda3ce99068817a2418e66e4f37dea40a21251c8a034f83145071d93baadf050ca0f95dc9ce2338fb082d64fbc8faba905cec66e65c0e1f9b003c32c943381282d4ab09bef9b6813ff3ff5118623d2617867e25f0601df583c3ac51bc6303f79e68d8f8de4b8363ec9c7728b3ec5fcd5274edfca2a42f2727aa223c557afb33f5bea4f64aeb252c0150ed734d4d8eccb257824e8e090f65029a3a042a51e5cc8767408ae07d55da8507e4d009ae72c47ddb138df3cab6cc023df2532f88fb5a4c4bd917fafde0f3134be09231c389c70bc55cb95a779615e8e0a76a2b4d943aabfde0e394c985c0cb0376930f92c5b6998ef49ff4a13652b787503f55c4e3d8eebd6e1bc6db3a6d405d8405bd7a8db7cefc64d16e0d105a468f3d33d29e5744a24c4ac43ce0eb1bf6b559aed520b91108cda2de6e2c4f14bc4f4dc58712580e07d217c8cca1aaf7ac04bab3e7b1008b966f1ed4fba3fd93a0a9d3a27127e7aa587fbcc60d548300146bdc126982a58ff5342fc41a43f83a3d2722a26645bc961894e339b953e78ab395ff2fb854247ad06d446cc2944a1aefb90573115dc198f5c1efbc22bc6d7a74e41e666a643d5f85f57fde81b87ceff95353d22ae8bab11684180dd142642894d8dc34e402f802c2fd4a73508ca99124e428d67437c871dd96e506ffc39c0fc401f666b437adca41fd563cbcfd0fa22fbbf8112979c4e677fb533d981745cceed0fe96da6cc0593c430bbb71bcbf924f70b4547b0bb4d41c94a09a9ef1147935a5c75bb2f721fbd24ea6a9f5c9331187490ffa6d4e34e6bb30c2c54a0344724f01088fb2751a486f425362741664efb287bce66c4a544c96fa8b124d3c6b9eaca170c0b530799a6e878a57f402eb0016cf2689d55c76b2a91285e2273763f3afc5bc9398273f5338a06d>
[Peer]
PublicKey = $(cat /etc/amnezia/amneziawg/server_public.key)
PresharedKey = $(cat /etc/amnezia/amneziawg/preshared.key)
Endpoint = <ORIGIN_SERVER_IP>:443
AllowedIPs = 10.10.0.1/32
PersistentKeepalive = 25
EOF'
sudo chmod 600 /etc/amnezia/amneziawg/awg0.confStep 9 - Start the Tunnel
Start on the origin server first (it's the listener):
sudo systemctl enable --now awg-quick@awg0Then start on the in-region VPS (it initiates the connection):
sudo systemctl enable --now awg-quick@awg0Verify:
# On either server — should show a recent handshake
sudo awg show
# Test connectivity (from in-region VPS)
ping -c 3 10.10.0.1
# Test connectivity (from origin server)
ping -c 3 10.10.0.2Both pings should succeed with ~40-60ms latency.
Step 9.1 - Survive Kernel Upgrades (Origin Server)
This step is critical. AmneziaWG uses a DKMS kernel module. When your kernel updates (e.g., via unattended upgrades), the module may not be rebuilt for the new kernel, and the tunnel will fail to start after reboot — silently breaking your site for restricted-region users.
Create a systemd service that auto-builds the module before the tunnel starts:
sudo bash -c 'cat > /usr/local/bin/awg-module-ensure.sh << "SCRIPT"
#!/bin/bash
KVER=$(uname -r)
if modprobe amneziawg 2>/dev/null; then
logger -t awg-ensure "amneziawg module loaded successfully"
exit 0
fi
logger -t awg-ensure "Module missing for kernel $KVER, attempting DKMS build"
# Install headers if missing
apt-get install -y -qq linux-headers-$KVER 2>/dev/null
# Build and install module
dkms install amneziawg/1.0.0 -k $KVER 2>&1 | logger -t awg-ensure
# Try loading again
if modprobe amneziawg; then
logger -t awg-ensure "Module built and loaded successfully"
else
logger -t awg-ensure "ERROR: Failed to build/load module"
exit 1
fi
SCRIPT'
sudo chmod +x /usr/local/bin/awg-module-ensure.shCreate the systemd service:
sudo bash -c 'cat > /etc/systemd/system/awg-module-ensure.service << "EOF"
[Unit]
Description=Ensure AmneziaWG kernel module is built and loaded
Before=awg-quick@awg0.service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/awg-module-ensure.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF'Make the tunnel service depend on it:
sudo mkdir -p /etc/systemd/system/awg-quick@awg0.service.d
sudo bash -c 'cat > /etc/systemd/system/awg-quick@awg0.service.d/override.conf << "EOF"
[Unit]
After=awg-module-ensure.service
Requires=awg-module-ensure.service
EOF'
sudo systemctl daemon-reload
sudo systemctl enable --now awg-module-ensure.serviceAlso ensure the module name is in the auto-load list:
sudo bash -c 'echo "amneziawg" >> /etc/modules-load.d/amneziawg.conf'Now on every boot — even after a kernel upgrade — the ensure service runs first, builds the module if needed, and then the tunnel starts.
Step 9.2 - Tunnel Watchdog (In-Region VPS)
The origin server may reboot for maintenance or kernel upgrades. When it comes back, the in-region VPS needs to re-establish the tunnel automatically. The PersistentKeepalive setting helps, but won't always recover from a full reboot on the other end.
Create a watchdog script that restarts the tunnel if the handshake goes stale:
sudo tee /usr/local/bin/awg-watchdog.sh > /dev/null << 'SCRIPT'
#!/bin/bash
# Restart tunnel if handshake is older than TIMEOUT seconds
TIMEOUT=180
HANDSHAKE=$(awg show awg0 latest-handshakes 2>/dev/null | awk '{print $2}')
if [ -z "$HANDSHAKE" ] || [ "$HANDSHAKE" -eq 0 ]; then
logger -t awg-watchdog "No handshake found, restarting tunnel"
awg-quick down awg0 2>/dev/null; awg-quick up awg0
exit 0
fi
NOW=$(date +%s)
AGE=$((NOW - HANDSHAKE))
if [ "$AGE" -gt "$TIMEOUT" ]; then
logger -t awg-watchdog "Handshake stale (${AGE}s old), restarting tunnel"
awg-quick down awg0 2>/dev/null; awg-quick up awg0
fi
SCRIPT
sudo chmod +x /usr/local/bin/awg-watchdog.shRun it every minute via cron:
(crontab -l 2>/dev/null | grep -v awg-watchdog; echo "* * * * * /usr/local/bin/awg-watchdog.sh") | crontab -With this in place, if the origin server reboots, the in-region VPS will detect the stale handshake within 3 minutes and re-establish the tunnel automatically. Your users experience at most a few minutes of downtime instead of indefinite breakage.
Tip: You should also run
awg-module-ensure.shand the systemd override on the in-region VPS if it runs the DKMS kernel module. The same kernel upgrade issue applies there too.
Step 10 - Route Nginx Through the Tunnel
Update the Nginx config on the in-region VPS to use the tunnel IP (10.10.0.1) instead of the origin server's public IP:
sudo sed -i 's|<ORIGIN_SERVER_IP>|10.10.0.1|g' /etc/nginx/sites-available/your-site
sudo nginx -t
sudo systemctl reload nginxTest the full chain from the in-region VPS:
curl -sk https://10.10.0.1 -H "Host: yourapp.example.com" | head -5You should see your website's HTML output.
Step 11 - Verify from the Restricted Region
Use check-host.net to test your website from locations inside the restricted region, or ask a user there to confirm everything loads — including large JavaScript bundles, CSS, images, and fonts.
Important Configuration Details
| Why Port 443/UDP? |
We use UDP port 443 because this is the standard QUIC (HTTP/3) port. DPI systems expect to see UDP traffic on this port. Combined with AmneziaWG's QUIC protocol signature ( |
| Why MTU 1280? |
AmneziaWG adds overhead to each packet (obfuscation headers, junk data prefix). Without reducing MTU from the default 1420, some packets exceed the maximum transmission size inside the tunnel and get silently dropped. This manifests as partial page loads where the TCP handshake succeeds but data transfer stalls midway. MTU 1280 (the IPv6 minimum) works reliably even with maximum obfuscation overhead. |
| Why the I1 Parameter is Critical |
This was our most important discovery. Basic AmneziaWG obfuscation (S1/S2/H1-H4 parameters only) randomizes headers and packet sizes, but sophisticated DPI can still detect the tunnel by analyzing traffic patterns. The tunnel would connect, work for 10-30 seconds, then get killed. Adding the |
| Which Parameters Must Match? |
Must be identical on both servers:
Can differ (but simplest to keep the same):
|
| Firewall Rules |
Origin server: In-region VPS: |
Troubleshooting
| Tunnel won't establish (no handshake) |
Common causes:
|
| Tunnel breaks after kernel upgrade / server reboot |
If To prevent this permanently, set up the |
| Tunnel connects but dies after 10-30 seconds |
You're missing the |
| Ping works through tunnel but TCP/HTTPS doesn't |
MTU problem. Ensure both configs have Also verify the origin server's firewall allows TCP traffic from the tunnel interface: |
| Handshake goes stale after periods of inactivity |
The |
| 504 Gateway Timeout in browser |
Nginx can't reach the origin through the tunnel. Debug sequence:
Check with |
| "Destination Host Unreachable" when pinging |
The tunnel handshake hasn't completed. The in-region VPS is the initiator (it has the Then check immediately: |
Further considerations
| Security |
|
| Cost |
The only additional cost is the in-region VPS: typically $4-10/month for minimum specs. No CDN subscriptions, no special software licenses, no per-request fees. |
Conclusion
The complete solution has three layers:
- DNS points to the in-region VPS
- Nginx on the in-region VPS handles TLS termination and reverse proxying
- AmneziaWG tunnel with QUIC obfuscation carries the proxy-to-origin traffic through the DPI undetected
The key insight is that neither a CDN nor a simple reverse proxy is sufficient when DPI throttles both direct and proxied connections to foreign IPs. You need an encrypted, obfuscated tunnel between the in-region proxy and your origin — and that tunnel needs protocol-level disguise (the I1 parameter) to survive against modern DPI systems.
Resources
- AmneziaVPN Project — Creators of AmneziaWG
- AmneziaWG Protocol Documentation
- AmneziaWG Linux Kernel Module
- AmneziaWG Tools
- EDIS Global AmneziaWG Guide
Written based on real-world experience. DPI techniques evolve — if this stops working, check the AmneziaVPN community for the latest bypass methods.