Amnezia VPN server setup: AmneziaWG on your own VPS

Amnezia VPN server setup on your own VPS: install AmneziaWG with the app or by hand, tune obfuscation, connect clients and fix it when WireGuard is blocked.

Wherever DPI filters traffic — Russia, Iran, China — plain WireGuard is recognised by its handshake and dropped. From Russia, plain WireGuard does not connect to ForestsNet Cloud IPs at all. AmneziaWG does: it sends noise before the handshake and alters the packets' service fields, so the signature disappears. Plain WireGuard is still the simpler choice for links between your own servers and for networks without DPI — see WireGuard VPN: server and clients.

Current as of September 2026: AmneziaVPN app 5.0.3.0, AmneziaWG protocol 3.1 (amneziawg-tools 3.1.20260812, kernel module 3.1.20260906, amneziawg-go 3.1.20260828).

What you'll need

  • A ForestsNet Cloud VPS in the Netherlands or Germany running Ubuntu 22.04/24.04 or Debian 12/13 — the systems Amnezia officially supports. 1 vCPU and 1 GB RAM are enough; from 10 connections, take 2 GB.
  • SSH as root or as a user with passwordless sudo — see VNC console and first connection.
  • The AmneziaVPN app (step 2) or AmneziaWG (step 3).
  • Traffic headroom: only outbound traffic counts toward your plan, and for a VPN that roughly equals what your clients download.

Step 1. Understand why WireGuard gets blocked

What DPI sees in WireGuard

WireGuard never tried to hide. Every packet starts with a message type (1–4) followed by three zero bytes, and the handshake packets have fixed sizes: 148 bytes for the initiation, 92 for the response, 64 for the cookie reply. The first UDP packet of a new connection is 148 bytes starting with 01 00 00 00 — exactly what the open-source DPI engine nDPI checks for.

Censors have used this for years. Iran blocked WireGuard outright in 2022, China's Great Firewall blocks WireGuard server ports after a period of use — within a day in some regions — and Russia's TSPU ran a mass wave against WireGuard and OpenVPN in August 2023; by 2025 WireGuard was blocked in most Russian regions. On some networks the handshake never completes, on others the flow freezes after a couple of data packets.

What AmneziaWG changes

AmneziaWG (AWG) is a WireGuard fork by the Amnezia team: the cryptography is the same, only the way packets look on the wire changes. Before every handshake, the initiator — usually the client — sends from the same UDP port:

  • I1–I5 — signature packets: byte templates that imitate the start of another protocol, such as DNS or QUIC;
  • then Jc junk packets of random size between Jmin and Jmax bytes.

The WireGuard packets themselves change too:

  • S1–S4 — a random prefix on the initiation, response, cookie and data packets, so the 148/92/64 sizes are no longer constant.
  • H1–H4 — replacements for the type numbers 1–4. Since 2.0 these are ranges, and every packet gets a random value.
  • HeaderProtectionKey (3.x) encrypts the header fields WireGuard sends in the clear. The nonce comes from the prefix, so S1–S4 must be at least 12.
  • ContentPaddingAddition and RandomTrailers add a random number of bytes, DisableCookies stops cookie replies, and timers are set as ranges.

As a result, the first packet of the flow is junk or an imitation of another protocol, not a WireGuard handshake. On our addresses, that is exactly what separates a working connection from Russia from a dead one.

Tip: AmneziaWG with empty parameters is plain WireGuard: anything unset counts as zero, the type numbers stay 1–4, and such an "AWG" is blocked just the same.

Protocol versions

  • 1.0 (late 2023) — Jc, Jmin, Jmax, S1, S2, single-value H1–H4.
  • 1.5 (July 2025) — I1–I5.
  • 2.0 (September 2025, in the app since January 2026) — S3, S4 and H ranges.
  • 3.0 and 3.1 (July–August 2026) — header protection, random sizes and timers; 3.1 adds RandomTrailers and DisableCookies. Amnezia released 3.1 after a wave of blocking in Russia in June–July 2026 and now calls 2.0 vulnerable.

The client must support the config's version: a 3.1 config won't work in an older app.

Which parameters must match

S1–S4, H1–H4, HeaderProtectionKey and RandomTrailers must be identical on the server and the client. Jc, Jmin, Jmax, I1–I5, ContentPaddingAddition, the timers and DisableCookies may differ; the developers recommend setting junk and signature packets on the client only.

We tested mismatches in a lab: a wrong S1 or header key means no handshake at all, while a wrong S4 lets the client show a handshake but no data gets through.

Important: don't copy parameter values from other people's guides. In the 2.0 profile, the same S and H values on thousands of servers turn into a signature again; in 3.1 protection rests on the header key, unique to each server.

Step 2. Install with the AmneziaVPN app

Two paths from here: the app (this step) or a manual install (step 3).

Connect the server

  1. Install AmneziaVPN 5.0.3.0 or newer from amnezia.org or GitHub.
  2. Choose Self-hosted VPN and enter the server IP, login and password or private key (ED25519 or RSA in PEM).
  3. The Automatic mode installs AmneziaWG; everything else can be added later.

A user other than root needs passwordless sudo. Amnezia's documentation suggests:

echo 'USERNAME ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/USERNAME

What the app does to the server

  1. Checks privileges with sudo -n. If apt is running, it waits — on a fresh VPS that is usually automatic updates.
  2. Installs Docker from the distribution's repository (docker.io). It refuses to work with podman.
  3. Creates the amnezia-dns-net Docker network (172.29.172.0/24, bridge amn0) and /opt/amnezia/<container> directories.
  4. Changes the host: enables ip_forward, stops answering ping, raises nf_conntrack_max to 262144, enlarges buffers and switches to hybla.
  5. Builds the amnezia-awg2 container from amneziavpn/amneziawg-go, a userspace Go implementation that needs no kernel module. It runs with --privileged and --restart always, without logs, publishing one UDP port.
  6. Inside the container, generates keys, writes /opt/amnezia/awg/awg0.conf, brings up the interface and NAT, then builds your device profile.

The defaults are AWG 3.1: a random UDP port in 30000–49999, Jc 4–6, Jmin 10, Jmax 50, S1–S4 of 12 bytes, H1–H4 = 1–4, a per-server HeaderProtectionKey, ContentPaddingAddition 10–100, RandomTrailers and DisableCookies on, and timers as ranges (RekeyAfterTime 100–120 s, KeepaliveTimeout 5–15 s, PersistentKeepalive 25–35 s). I1 is a 44-byte packet resembling a DNS response.

Standard H1–H4 are not a weakness here: the headers are encrypted with the key, and Amnezia's documentation itself recommends keeping 1–4 in this mode. Random H values were the disguise before version 3.0.

Important: after the install the server stops answering ping. This is Amnezia's rule, not a hosting fault; it lasts until reboot and comes back with every protocol install or change. To remove it right away: sudo iptables -D INPUT -p icmp --icmp-type echo-request -j DROP

Docker publishes the UDP port bypassing the INPUT chain, so ufw usually doesn't interfere, but an external firewall must let the port through; Amnezia's documentation advises a port below 9999, since some ISPs block UDP on high ports. Keys and users live only inside the container: delete it and every issued connection is gone.

Which protocol to pick

  • AmneziaWG — the default: UDP, fast, easy on the battery.
  • XRay (VLESS + Reality) — the fallback: TCP 443, helps when UDP is throttled. ForestsNet Cloud servers must not impersonate third-party sites, so put your own domain in SNI and dest instead of the one the app fills in by default — see the Terms of Use. VLESS with a panel: 3x-ui install and VLESS Reality setup.
  • WireGuard, OpenVPN, IKEv2 — only for networks without DPI. OpenVPN over Cloak and over Shadowsocks are marked unsupported in 5.x.

Services include AmneziaDNS, SFTP, SOCKS5 and Telegram proxies (see MTProto proxy for Telegram on your own server). Protocols are compared in Self-hosted VPN: which protocol to choose.

Sharing access and limits

Share works in two modes:

  • VPN access without server management — for people: one protocol, one device. A vpn:// key is for Amnezia's apps; for AmneziaWG and routers export the AmneziaWG native format, a regular .conf.
  • Full access to the server and VPN — the key contains the SSH login and password or private key, i.e. the whole server. It can't be revoked; the only way out is changing the SSH credentials. Use it for your own devices only.

The Users tab shows each connection's latest handshake and traffic, and that's where you revoke them. The app has no speed, traffic or time limits — for those you need a separate panel, such as Remnawave for VLESS.

Important: any change to the protocol's server settings (port, subnet, obfuscation) recreates the container, and every issued connection stops working.

Settings and moving to 3.1

Settings live under the server: Protocols → AmneziaWG. The server section changes the port, subnet and obfuscation; the connection section changes I1, junk and MTU for this device only.

If AWG was installed before August 2026, the server most likely runs 2.0. The documented upgrade path: update the app, remove AmneziaWG together with its users, reinstall on a port below 9999 and share access again.

Step 3. Install AmneziaWG manually

This path is for those who want the kernel module instead of Docker and full control over configs. The parameters match what the app sets, so clients need AWG 3 support: current AmneziaVPN and AmneziaWG apps will do.

Install the kernel module and tools

First update the system and reboot if the kernel was upgraded. Ubuntu:

sudo apt update
sudo apt install -y software-properties-common python3-launchpadlib gnupg2 linux-headers-$(uname -r)
sudo add-apt-repository -y ppa:amnezia/ppa
sudo apt install -y amneziawg

Debian uses the same PPA with the focal branch. The official guide relies on apt-key, which Debian 13 no longer ships, so add the key with signed-by:

sudo apt update && sudo apt install -y curl gnupg linux-headers-$(uname -r)
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x75C9DD72C799870E310542E24166F2C257290828' | sudo gpg --dearmor -o /etc/apt/keyrings/amnezia.gpg
echo 'deb [signed-by=/etc/apt/keyrings/amnezia.gpg] https://ppa.launchpadcontent.net/amnezia/ppa/ubuntu focal main' | sudo tee /etc/apt/sources.list.d/amnezia.list
sudo apt update && sudo apt install -y amneziawg

awg --version should print amneziawg-tools v3.1…, and sudo modprobe amneziawg should succeed; the package version 1.0.20210914-… says nothing about the protocol. DKMS rebuilds the module after kernel updates if the headers are there — install the headers metapackage, such as linux-headers-generic or linux-headers-amd64.

Tip: if the module won't build, awg-quick falls back to the userspace amneziawg-go implementation when it finds it in PATH. It's slower but independent of the kernel.

Generate keys and parameters

From here on, work as root in /etc/amnezia/amneziawg; the package has already created it with mode 700:

sudo -i
cd /etc/amnezia/amneziawg && umask 077
awg genkey | tee server.key | awg pubkey > server.pub
awg genkey | tee client1.key | awg pubkey > client1.pub
awg genpsk > client1.psk
awg genkey > hpk.key
r() { shuf -i "$1-$2" -n 1; }
while :; do
  S1=$(r 12 64); S2=$(r 12 64); S3=$(r 12 64); S4=$(r 12 24)
  [ $((S2 + 28)) -ne "$S3" ] && break
done
cat > common.txt <<EOF
S1 = $S1
S2 = $S2
S3 = $S3
S4 = $S4
H1 = 1
H2 = 2
H3 = 3
H4 = 4
HeaderProtectionKey = $(cat hpk.key)
RandomTrailers = on
EOF

common.txt is the block shared by the server and all clients. S values are at least 12, or the header key is rejected; S4 is added to every data packet, so it stays small. The loop keeps service packets from colliding in size (92 + S2 ≠ 64 + S3), the same check the Amnezia app makes. RandomTrailers must be on at both ends, or the server drops the lengthened handshake.

Build the server config

PORT=$(r 1025 9999); echo "$PORT" > port.txt
WAN=$(ip route get 1.1.1.1 | sed -n 's/.* dev \([^ ]*\).*/\1/p')
cat > awg0.conf <<EOF
[Interface]
Address = 10.66.66.1/24
ListenPort = $PORT
PrivateKey = $(cat server.key)
MTU = 1400
$(cat common.txt)
DisableCookies = on
PostUp = iptables -I FORWARD -i %i -j ACCEPT
PostUp = iptables -I FORWARD -o %i -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -s 10.66.66.0/24 -o $WAN -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -s 10.66.66.0/24 -o $WAN -j MASQUERADE

[Peer]
# client1
PublicKey = $(cat client1.pub)
PresharedKey = $(cat client1.psk)
AllowedIPs = 10.66.66.2/32
EOF
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-amneziawg.conf && sysctl --system
ufw allow "$PORT"/udp    # if ufw is enabled
systemctl enable --now awg-quick@awg0
  • The port is random and below 9999. WAN is the external interface: MASQUERADE on eth0 when the real one is ens3 is a classic cause of "connected, but no internet".
  • MTU = 1400 leaves room for S4. The FORWARD rules go to the top of the chain so ufw and Docker chains don't intercept them.
  • There's no junk or I1 on the server — the client sends them.

Build the client config

I1 is built as a DNS response, following the Amnezia app's approach but with your own domain and a random ID and address. Replace 203.0.113.10 with your VPS IP:

dns_i1() {
  local q="" l
  for l in ${1//./ }; do
    q+=$(printf '%02x' "${#l}")$(printf '%s' "$l" | od -An -tx1 | tr -d ' \n')
  done
  echo "<r 2><b 0x81800001000100000000${q}0000010001c00c000100010000012c0004><r 4>"
}
SERVER_IP=203.0.113.10
cat > client1.conf <<EOF
[Interface]
PrivateKey = $(cat client1.key)
Address = 10.66.66.2/32
DNS = 1.1.1.1, 1.0.0.1
MTU = 1280
Jc = $(r 4 6)
Jmin = $(r 10 30)
Jmax = $(r 60 120)
I1 = $(dns_i1 example.com)
$(cat common.txt)
ContentPaddingAddition = 10-100
RekeyAfterTime = 100-120
KeepaliveTimeout = 5-15

[Peer]
PublicKey = $(cat server.pub)
PresharedKey = $(cat client1.psk)
Endpoint = $SERVER_IP:$(cat port.txt)
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25-35
EOF
qrencode -t ansiutf8 < client1.conf

Keep junk small: mobile carriers pass big junk less willingly, and a Jmax above the MTU causes fragmentation. MTU = 1280 is safe on mobile networks, ::/0 keeps IPv6 from bypassing the tunnel, and qrencode comes from apt. Pick a domain for I1 that is ordinary in your region.

Check and add clients

Done: after importing client1.conf, awg show awg0 on the server shows a fresh latest handshake.

For a new client, generate keys and a PSK, append a [Peer] section with the next address (10.66.66.3/32) to awg0.conf and run systemctl reload awg-quick@awg0 — changes apply through awg syncconf without dropping connections.

A 2.0 profile for routers

Keenetic's stable 5.1 branch and most OpenWrt builds only understand 2.0 parameters. For them, bring up a second interface awg1 with its own subnet and port, without HeaderProtectionKey, RandomTrailers, DisableCookies, ContentPaddingAddition and timer ranges, and with a plain PersistentKeepalive = 25. Protection here comes from random S values and non-overlapping H ranges:

while :; do
  S1=$(r 15 150); S2=$(r 15 150); S3=$(r 15 150); S4=$(r 12 32)
  [ $((S1 + 56)) -ne "$S2" ] && [ $((S2 + 28)) -ne "$S3" ] && [ $((S1 + 84)) -ne "$S3" ] && break
done
read -r a b c d e f g h <<< "$(shuf -i 5-2147483647 -n 8 | sort -n | tr '\n' ' ')"
printf 'S1 = %s\nS2 = %s\nS3 = %s\nS4 = %s\nH1 = %s-%s\nH2 = %s-%s\nH3 = %s-%s\nH4 = %s-%s\n' \
  "$S1" "$S2" "$S3" "$S4" "$a" "$b" "$c" "$d" "$e" "$f" "$g" "$h" > common-v2.txt

Step 4. Connect clients

  • Windows — AmneziaWG 3.1.0 (MSI from GitHub) or AmneziaVPN 5.0.3.0.
  • macOS — AmneziaWG 3.1.4 from the App Store or AmneziaVPN for macOS 13+.
  • Android — AmneziaWG 3.1.4 from Google Play (file or QR) or AmneziaVPN, which also runs on Android TV.
  • iOS — AmneziaWG 3.1.4, also in the Russian App Store; import the .conf there. AmneziaVPN isn't in that storefront, and Amnezia's DefaultVPN can't manage a self-hosted server.
  • Linux — awg-quick from the same PPA, as on the server, or AmneziaVPN.
  • Keenetic — S3, S4 and I1–I5 since KeeneticOS 5.1; AWG 3.1 only in the 5.2 Alpha 11 test build so far. Import the config under Other connections; the WireGuard VPN component is required.
  • OpenWrt — the Slava-Shchipunov/awg-openwrt builds Amnezia links to: 2.0 for 23.05.6, 24.10.3–24.10.7 and 25.12.0–25.12.4, 3.1 for 24.10.8 and 25.12.5.
  • GL.iNet — AWG 2.0 on firmware 4.9+. MikroTik has no native support, and Apple TV has no official client.

Troubleshooting

Obfuscation or network

Watch the server while the client connects. For the app install, use sudo docker exec amnezia-awg2 awg show.

awg show awg0
tcpdump -ni any udp port "$(cat /etc/amnezia/amneziawg/port.txt)"
  • No packets arrive — wrong IP or port, a closed port or blocking on the way. Test from another network.
  • Packets arrive, the server stays silent — no latest handshake; the server can't parse them: keys, S1/S2, HeaderProtectionKey or RandomTrailers don't match. AWG drops such packets silently, the logs stay empty.
  • The server sees the client but no handshake, while the client says "connected" — the peer has an endpoint and some bytes in transfer, but no latest handshake. The server answered the handshake but rejects the data: S3/S4 or H3/H4 differ, or DPI cuts data after the handshake.
  • Handshake present, counters grow, sites don't open — the problem is past the tunnel: NAT, forwarding, DNS, MTU.

Symptom → cause → fix

  • Connects, but no traffic. Check sysctl net.ipv4.ip_forward, the interface in the MASQUERADE rule and its counter: iptables -t nat -L POSTROUTING -v -n. If data flows for a few seconds and then freezes, DPI is cutting the flow after the handshake — change I1.
  • Works on Wi-Fi, not on mobile. Mobile carriers are stricter with UDP. Community measurements from Russia in 2026 show that on some networks junk alone isn't enough: a DNS-like I1 passes, random bytes don't, and a fake QUIC packet gets validated and dropped. China's GFW decrypts QUIC Initial packets and filters them by SNI, so a random-bytes QUIC imitation fails there too. Set MTU = 1280 and try another port. Where mobile internet runs on whitelists, as in parts of Russia, foreign UDP doesn't pass at all — neither AWG nor VLESS will help. Keep XRay as a fallback.
  • Low speed. Small requests work but large pages stall — that's MTU, lower it on the client. The Amnezia container runs in userspace and can max out 1 vCPU (top); the kernel module is faster. If XRay over TCP is much faster, the carrier shapes UDP.
  • Broke after an update. Server on 3.1, client too old — you need AmneziaVPN 5.0.1.5+ or AmneziaWG 3.1 and a fresh config. Line unrecognized means outdated amneziawg-tools. Module amneziawg not found after a kernel update means DKMS didn't build the module: dkms status, kernel headers, dkms autoinstall. Keenetic sees a handshake but no data after a server module update — open issue #215; keep routers on the 2.0 profile.

If nothing connects from inside the censored country while it works from abroad, the IP may be blocked. ForestsNet checks its addresses for TSPU blocks daily — contact support.

Security and maintenance

  • SSH — keys only: How to harden SSH on your VPS. AmneziaVPN stores SSH access to the server, so guard the device it runs on like the key itself.
  • Firewall — in ufw allow SSH first, then the AWG port: The ufw firewall: managing ports.
  • Updates — keep AmneziaVPN current: 5.0.1.5 fixed a vulnerability found by Positive Technologies. Reboot after an apt upgrade that brings a new module.
  • Key rotation — revoke a lost device at once: delete its [Peer] and run systemctl reload awg-quick@awg0. The server key and HeaderProtectionKey can only change together with reissuing every client config.
  • Backups — /etc/amnezia/amneziawg for the manual install; with Docker the keys exist only in the container: sudo docker cp amnezia-awg2:/opt/amnezia/awg ./awg-backup.

Sharing the VPN with others — shut out torrents: How to block torrents on your VPN or proxy. The rest is in the VPS security checklist.

Sources