The Problem: WireGuard Is Easy to Block
WireGuard is a great VPN protocol — fast, lean, well-audited crypto. But it has one glaring weakness: predictability.
Every WireGuard packet starts with a 4-byte header containing a message type: 1 for handshake initiation, 2 for response, 3 for cookie, 4 for data. The handshake init is always exactly 148 bytes. These are strong, static signatures that DPI systems fingerprint without breaking a sweat.
And they do. WireGuard is actively blocked in 10+ countries right now. In Russia, standard WireGuard has roughly a 12% success rate. Iran sees 98% packet loss. China's Great Firewall picks it up within seconds. Egypt, Turkmenistan, Myanmar, Pakistan, UAE, Turkey, Belarus, Uzbekistan, Kazakhstan — all confirmed blocking or throttling at the DPI level.
I'm based in Russia, and I watched this unfold firsthand. Through most of 2024, my WireGuard tunnels worked fine. Then they started dropping — not all at once, but ISP by ISP, week by week. Russia's TSPU (Technical Means of Countering Threats — essentially DPI boxes installed at every major provider) learned to identify WireGuard traffic and silently kill it. Handshakes just stopped completing. No error, no timeout message — the packets simply vanished.
That's what pushed me to build an open-source installer for AmneziaWG 2.0, a WireGuard fork with protocol-level obfuscation. Here's how the protocol works under the hood, and how to get it running on your own server.
How DPI Catches WireGuard
Standard WireGuard uses fixed 32-bit message types for its four packet types. A DPI system needs a trivially simple heuristic:
UDP packet + known header value (1-4) + predictable packet size = WireGuard
Handshake init is always 148 bytes. Response is always 92 bytes. These invariant sizes are a dead giveaway. Once the DPI box flags a WireGuard session, it can block the IP, throttle the connection, or silently drop every packet.
What AmneziaWG 2.0 Does About It
AmneziaWG is a WireGuard fork by the Amnezia VPN team. It changes the transport layer to resist DPI while leaving the cryptographic core (Curve25519, ChaCha20-Poly1305, Noise IK) untouched.
The first version (AWG 1.x) replaced standard headers with custom fixed values. Helped for a while, but a patient DPI system could learn the new static values just as well.
AWG 2.0 (late 2025) took a different path: randomize everything, every time.
How the Obfuscation Actually Works
Dynamic Headers (H1-H4)
Instead of fixed header values, AWG 2.0 uses ranges. Every time a packet goes out, a random value is picked from within the configured range:
H1 = 100000-800000 # Handshake init
H2 = 1000000-8000000 # Handshake response
H3 = 10000000-80000000 # Cookie
H4 = 100000000-800000000 # Data transport
Ranges must not overlap — the protocol still needs to tell packet types apart. But the result is that every packet looks different on the wire. There's no single header value for DPI to latch onto.
Random Padding (S1-S4)
AWG pads packets with random bytes to break the fixed-size signatures:
| Parameter | Packet Type | What happens |
|---|---|---|
| S1 | Handshake init | Init becomes 148 + S1 bytes (bye-bye fixed 148) |
| S2 | Handshake response | Response becomes 92 + S2 bytes |
| S3 | Cookie (new in 2.0) | Pads cookie packets |
| S4 | Data transport (new in 2.0) | Pads every data packet |
S3 and S4 are the big additions in 2.0. S4 matters most — it touches every single data packet, which makes traffic analysis across the whole session much harder.
One gotcha: S1 + 56 must not equal S2. Why? Because 148 - 92 = 56, so if the padding values differ by exactly 56, the padded init and padded response end up the same size. That's a pattern you don't want.
CPS — Custom Protocol Signature (I1-I5)
Before the real WireGuard handshake, the client sends decoy packets that mimic another protocol's signature.
Parameters I1 through I5 define the content of up to 5 decoy packets using a tag format:
| Tag | What it does |
|---|---|
<b 0xHEX> |
Insert specific bytes (hex-encoded) |
<r N> |
N cryptographically random bytes |
<rc N> |
N random alphanumeric chars [A-Za-z0-9] |
<rd N> |
N random decimal digits [0-9] |
<t> |
Current UNIX timestamp (4 bytes) |
<c> |
Packet counter (4 bytes) |
Simple version: <r 128> — blast 128 random bytes. DPI sees a random UDP packet from who-knows-what.
Sneakier version: <b 0xc000000001><r 64><t> — starts with bytes that look like a QUIC Initial packet header, then random data, then a timestamp. A DPI box might classify this as the start of a normal QUIC session.
The server ignores CPS packets entirely — it just waits for the real handshake init that comes after.
Junk Packets (Jc, Jmin, Jmax)
Before each handshake, the client fires off Jc junk packets with random sizes between Jmin and Jmax bytes. Pure noise that makes the connection setup profile less recognizable.
What This All Adds Up To
Each AmneziaWG 2.0 server ends up with its own unique parameter set. There's no universal DPI signature that catches all AWG 2.0 traffic — every server is, in effect, speaking its own dialect.
Here's the 1.x-to-2.0 changelog:
| Parameter | AWG 1.x | AWG 2.0 |
|---|---|---|
| H1-H4 | Fixed values | Ranges (random per packet) |
| S1-S2 | Padding for init/response | Unchanged |
| S3 | — | New: cookie padding |
| S4 | — | New: data packet padding |
| I1-I5 (CPS) | Late addition | Fully supported |
| Jc/Jmin/Jmax | Junk packets | Unchanged |
| Crypto | WireGuard (untouched) | WireGuard (untouched) |
What About Speed?
People always ask this, so let's get it out of the way.
You might have seen a "65% overhead" number floating around online. The AmneziaWG developers tracked that down to the userspace Go implementation (amneziawg-go), not the protocol itself. The kernel module (what my installer uses via DKMS) runs at near-WireGuard speeds.
| Metric | WireGuard | AmneziaWG 2.0 | OpenVPN |
|---|---|---|---|
| Crypto overhead | ~4% | ~4% (same crypto) | ~15-20% |
| Obfuscation overhead | — | +3-7% (padding/junk) | +10% (obfs4) |
| Total overhead | ~4% | <12% | ~25-28% |
| Battery drain (4h streaming) | 18% | 19% (+1%) | ~25% |
Real-world benchmark on an uncensored network: WireGuard 95 Mbps, AmneziaWG 92 Mbps. That's a 3% difference — you won't notice it.
And in a censored network? The comparison is "92 Mbps vs 0 Mbps." If WireGuard is blocked, raw speed numbers don't mean much.
How It Stacks Up Against Alternatives
| WireGuard | AmneziaWG 2.0 | OpenVPN+obfs4 | Shadowsocks | VLESS+Reality | |
|---|---|---|---|---|---|
| DPI resistance | None | High | Medium | Low (detectable) | Very High |
| Speed | Fastest | Near-WG (<12% OH) | Slow (~25% OH) | Medium | Medium-Fast |
| Full VPN tunnel | Yes | Yes | Yes | No (proxy) | No (proxy) |
| Runs in kernel | Yes | Yes (DKMS) | No | No | No |
| Setup complexity | Simple | Simple (installer) | Complex | Simple (Outline) | Complex |
| Transport | UDP | UDP (obfuscated) | TCP/UDP + TLS | TCP | TCP |
| Audited crypto | Yes | Inherits WG audit | Yes | Partial | No |
No DPI in your country? Plain WireGuard is simpler, just use that. Need the absolute maximum DPI resistance and OK with a proxy? VLESS+Reality is worth a look, but it's a proxy, not a VPN tunnel. AmneziaWG 2.0 fills the gap between them: WireGuard-grade performance with real obfuscation in a full tunnel.
One-Command Setup
Setting up AmneziaWG 2.0 by hand means building a kernel module, generating 10+ obfuscation parameters with the right constraints, writing server and client configs, configuring firewall rules. Lots of moving parts, lots of places to get something wrong.
I automated all of it in amneziawg-installer. It builds the kernel module via DKMS (not the slower Go userspace implementation), so you get the speed numbers from the table above.
What You Need
- A clean Ubuntu 24.04 LTS server (25.10 is experimental)
- Root SSH access
- ~1 GB RAM
Install
wget https://raw.githubusercontent.com/bivlked/amneziawg-installer/main/install_amneziawg_en.sh
chmod +x install_amneziawg_en.sh
sudo bash ./install_amneziawg_en.sh
The script walks through 8 steps:
- System hardening — strips bloat (snapd, modemmanager), tunes kernel params (BBR, network buffers), sets up swap
- DKMS build — installs the AmneziaWG kernel module from Amnezia PPA
- Firewall — UFW with deny-all default, SSH rate-limiting, VPN port only
- Config generation — all AWG 2.0 parameters auto-generated with proper constraints, server + client configs + QR codes
- Service start — Fail2Ban, awg-quick@awg0 enabled and running
Two reboots happen along the way (kernel updates + module loading). The script saves its state, so you just re-run it after each reboot and it picks up where it left off.
Non-Interactive Mode
For scripted or automated deployments:
sudo bash ./install_amneziawg_en.sh --port=51820 --disallow-ipv6 --route-amnezia --yes
Managing Clients
After installation, use the management script:
# Add a client (generates .conf + QR code .png)
sudo bash /root/awg/manage_amneziawg.sh add my_phone
# List all clients
sudo bash /root/awg/manage_amneziawg.sh list
# Remove a client
sudo bash /root/awg/manage_amneziawg.sh remove my_phone
# Check server status
sudo bash /root/awg/manage_amneziawg.sh check
# Restart after changes
sudo systemctl restart awg-quick@awg0
Connect using the Amnezia VPN client (version >= 4.8.12.7) — that's currently the only client supporting AWG 2.0 parameters.
Checking That It Works
On the server:
sudo awg show awg0
Look for latest handshake under your peer. If it's there, the AWG 2.0 handshake went through.
From the client side:
curl ifconfig.me
If you see your server's IP instead of your home IP — you're connected through the VPN.
Final Thoughts
I've been running this on my own servers for several months now. From Russia, through ISPs with active TSPU filtering, AWG 2.0 connections hold where stock WireGuard has been dead since mid-2025. The crypto hasn't changed, so every WireGuard audit still applies. The overhead? Under 12% on paper, closer to 3% in my testing. And since the installer uses the kernel module, you skip the userspace performance hit entirely.
If you're in a country where WireGuard stopped working, give it a shot. And if you run into issues or have suggestions, I'd genuinely like to hear about it.
Links:
- amneziawg-installer on GitHub — star it if it's useful to you
- AmneziaWG 2.0 Documentation
- Amnezia VPN Client
- Junker — AmneziaWG Signature Generator (for manual setup)
Questions, bugs, feature requests? Open an issue on GitHub or leave a comment here.
Top comments (2)
I'm in Kazakhstan and regular WireGuard has been unreliable here for a while now. Set this up on a Hetzner VPS over the weekend, took maybe 15 min total with the two reboots. Was skeptical about the CPS/QUIC mimicry thing but I actually tested with and without it and the connection drops way less with it on.
One thing - is there a way to set up split routing per client? I have a laptop where I only want blocked sites going through the tunnel, not all traffic.
Yeah there's a routing mode for that. When you run the installer it asks - pick "Amnezia list" and only blocked domains go through the tunnel. Or
--route-amneziaif you're doing it non-interactive.If you already set it up with full tunnel you'd need to reinstall with that flag, it'll regenerate the client configs.