DEV Community

Cover image for AmneziaWG 2.0: Self-Host an Obfuscated WireGuard VPN That Bypasses DPI
Ivan Bondarev
Ivan Bondarev

Posted on

AmneziaWG 2.0: Self-Host an Obfuscated WireGuard VPN That Bypasses DPI

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The script walks through 8 steps:

  1. System hardening — strips bloat (snapd, modemmanager), tunes kernel params (BBR, network buffers), sets up swap
  2. DKMS build — installs the AmneziaWG kernel module from Amnezia PPA
  3. Firewall — UFW with deny-all default, SSH rate-limiting, VPN port only
  4. Config generation — all AWG 2.0 parameters auto-generated with proper constraints, server + client configs + QR codes
  5. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:


Questions, bugs, feature requests? Open an issue on GitHub or leave a comment here.

Top comments (2)

Collapse
 
alextit profile image
Алексей Титков

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.

Collapse
 
bivlked profile image
Ivan Bondarev

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-amnezia if 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.