Files
3x-ui/docs/content/docs/en/config/amneziawg.mdx
T
kuzzrus bd1c27b03d fix(amneziawg): H1-H4 generator + queue-depth throughput fixes (#6330)
* fix(amneziawg): stop H1-H4 generator misclassifying transport packets

Both the Go generator and its frontend mirror picked a random *range*
per H1-H4 field with only a minimum width enforced (no maximum).
amneziawg-go's packet classifier only ever compares a fixed-size
ciphertext prefix against these bounds, so a wide range buys no DPI
resistance -- the boundaries themselves are never observable on the
wire. It does cost real throughput: with randomTrailers on (the
default here), the handshake-size checks relax from == to >, so a
wide H-range misclassifies a proportional fraction of ordinary
transport packets as handshakes and silently drops them
(amnezia-vpn/amneziawg-go#183). A single value per field is strictly
safer than any range, with no obfuscation trade-off.

Live-tested: narrowing H1-H4 alone took AmneziaWG upload from
2-3 Mbit/s to 200+ Mbit/s on one box, and ~20 Mbit/s to 120-156 Mbit/s
on another, single-variable, no other change.

* fix(amneziawgnet): raise tunQueueDepth to absorb slow-start bursts

1024 was sized for a single-connection buffering problem (the
gVisor-to-amneziawg-go TUN handoff channel needing slack for the
download direction). tcpip.Stack.Stats() during a real many-connection
download (20-28 concurrent TCP flows, e.g. a segmented speed test)
showed SlowStartRetransmits jump by ~770 in a single second the moment
CurrentEstablished crossed ~20 -- consistent with many connections'
simultaneous slow-start growth briefly exceeding 1024 outstanding
packets and gVisor treating the resulting silent drops as real network
loss.

* fix(amneziawg): trim comment blocks to the repo's 2-line cap

Review feedback: four comment blocks in the previous commits exceeded
CLAUDE.md's 2-line-per-block hard rule (up to 13 lines). Trimmed each to
the one non-obvious fact plus the amneziawg-go#183 reference; the fuller
rationale already lives in the commit message. Also refreshed the stale
H1-H4 range example in docs/content/docs/en/config/amneziawg.mdx to match
the new single-value generator output.
2026-09-03 21:50:23 +02:00

178 lines
7.7 KiB
Plaintext

---
title: AmneziaWG
description: Set up an AmneziaWG inbound in 3x-ui — obfuscation parameters, native IPv6, per-client port-forwarding, and routing client traffic through Xray.
icon: Lock
---
**AmneziaWG** is a WireGuard fork that adds traffic obfuscation (junk packets,
randomized padding, and rewritten protocol magic values) so the tunnel doesn't
look like WireGuard to deep-packet inspection. It's a popular choice where
plain WireGuard is blocked but a WireGuard-shaped tunnel with a different
fingerprint gets through.
<Callout type="info">
AmneziaWG runs **embedded in the panel process** — `amneziawg-go` over a
userspace (gVisor) network stack, not a kernel module. There is no DKMS
build, no Secure Boot conflict, and no host network/kernel access
requirement, so it works the same way inside a container as on bare
metal. Each peer's decapsulated traffic relays into its own loopback Xray
SOCKS5 inbound, so a peer's routing, sniffing, and per-client stats all
come from Xray's own machinery — the same as any other protocol's
inbound, not a separate code path.
</Callout>
## Key settings
### Server / interface
| Field | What it is |
| ------------------------ | ------------------------------------------------------------------------ |
| **Subnet** | The tunnel's IPv4 subnet (e.g. `10.8.1.0/24`); each client gets an address from it. |
| **MTU** | Interface MTU. Leave at the default unless you have a reason to change it. |
| **DNS (primary/secondary)** | Seeded into downloadable client configs; the server's own interface doesn't need one. |
| **External interface** | The host NIC a peer's IPv6 address gets aliased onto when IPv6 is enabled (see below). Leave blank to auto-detect. |
### Obfuscation (AmneziaWG 3.1)
The same values must match on both ends of the tunnel, so the server stores
them once and every client config inherits them. The panel generates a
randomized set for you (with a **regenerate** button) — a static, reused
value defeats the point, since DPI can fingerprint it over time.
| Field | What it is |
| ------------ | ---------------------------------------------------------------------------- |
| **Jc** | Number of junk packets sent before the handshake. |
| **Jmin/Jmax** | Size range (bytes) for those junk packets. `Jmin` must not exceed `Jmax`. |
| **S1/S2** | Padding added to the handshake init/response packets. `S1 + 56` must not equal `S2` — amneziawg-go rejects a value that would make both packets the same size. |
| **S3** | Cookie-reply padding, `0`-`64`. |
| **S4** | Transport (data) packet padding, `0`-`32`. |
| **H1-H4** | Magic header values that replace WireGuard's standard message-type bytes. Each is a single integer or a `low-high` range; `1`-`4` are reserved (real WireGuard message types) and must not be used. |
| **I1-I5** | Optional signature packets — random bytes prepended before the handshake, e.g. `<r 148>`. Generated sets fill `I1` only, matching Amnezia's own generator. |
| **HeaderProtectionKey** | A base64 32-byte key for the 3.0 header-protection mechanism. Must match on every client config; blank disables it. |
| **ContentPaddingAddition** | A single integer or `low-high` byte range of extra padding on content packets. Kept `<= 64` by the generator so a 1420-MTU tunnel doesn't fragment. |
| **RekeyAfterTime / RekeyTimeout / RejectAfterTime / KeepaliveTimeout / MaxHandshakeAttempts** | Handshake-timing randomization: each is a `low-high` range (seconds; attempts for the last one) the peer samples from, so session timing stops being a WireGuard fingerprint. Every `RekeyAfterTime` value must stay below every `RejectAfterTime` value. Blank keeps the WireGuard default. |
| **RandomTrailers** | Appends a random number of bytes to the end of every packet. |
| **DisableCookies** | Never send cookie replies — removes a DPI-visible WireGuard message type, at the cost of WireGuard's handshake-flood mitigation. |
<Callout type="info">
If you enter obfuscation values by hand instead of using the generated
defaults, keep `H1`-`H4` **non-overlapping** and above `4`, and double-check
`S1 + 56 != S2` — a bad value here keeps the embedded interface from
coming up at all.
</Callout>
<Callout type="warn">
The 3.1 parameters need a **3.1-capable client**. Clients must run a
3.1-capable Amnezia app; blanking the 3.1 fields renders a config older
clients still understand. There is no host-side version requirement —
the panel ships its own pinned `amneziawg-go`, not whatever happens to be
installed on the system.
</Callout>
## Set it up in the panel
<Steps>
<Step>
### Add an inbound
Add a new inbound, choose protocol **AmneziaWG**, and set the port and tunnel
subnet.
</Step>
<Step>
### Leave obfuscation on defaults (or regenerate)
The panel fills in a randomized, kernel-valid obfuscation set automatically.
Use **Regenerate** if you want a fresh one; there's no need to hand-edit these
unless you have a specific reason to.
</Step>
<Step>
### Add a client
Each client gets its own keypair and tunnel address. Download the client's
`.conf` or copy its share link (`vpn://…`, importable by the official
AmneziaWG/AmneziaVPN apps) from the client list.
</Step>
<Step>
### Optional: enable IPv6
Turning on IPv6 allocates an IPv6 address alongside each client's IPv4 one
from the configured IPv6 subnet. The panel aliases that address onto the
external interface's host NIC so outbound connections carry the peer's own
distinct public IPv6 identity — no NAT66 needed.
</Step>
<Step>
### Optional: forward ports to a client
Set a client's forwarded ports (e.g. `80, 443, 8000-8100`) to open a real
listener on the host that relays that traffic straight to the client's
tunnel address — useful for a client that needs to expose a service through
the server.
</Step>
</Steps>
Every AmneziaWG inbound's traffic already goes through Xray — each peer
relays into its own loopback SOCKS5 inbound, tagged with the AmneziaWG
inbound's own tag, so it shows up as a normal source on the
[Routing](/docs/operations/outbounds-routing) page like any other protocol's
inbound. There is no separate toggle for this: unlike a kernel tunnel,
there's no other way for a peer's traffic to reach the internet once it's
decapsulated.
## What the configuration looks like
A client's downloadable `.conf` (also what the `vpn://` share link encodes,
base64url'd) looks like this:
```ini title="client .conf"
[Interface]
PrivateKey = <client private key>
Address = 10.8.1.2/32
DNS = 8.8.8.8, 8.8.4.4
Jc = 4
Jmin = 65
Jmax = 220
S1 = 87
S2 = 44
S3 = 21
S4 = 9
H1 = 463065432
H2 = 912345678
H3 = 1345678901
H4 = 1987654321
I1 = <r 148>
HeaderProtectionKey = 8Iu83eHDA3fMKKSGaEsVW9Ycd2lYYzc0MYlk1jJTvE4=
ContentPaddingAddition = 17-49
RekeyAfterTime = 111-139
RekeyTimeout = 4-7
RejectAfterTime = 187-251
KeepaliveTimeout = 9-14
MaxHandshakeAttempts = 19-36
RandomTrailers = on
DisableCookies = on
# my-client
[Peer]
PublicKey = <server public key>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = your-server:443
PersistentKeepalive = 25
```
## Not yet covered
<Callout type="info">
- **Multi-node (sub-nodes)** and **Telegram bot** — AmneziaWG inbounds haven't
been exercised through those paths yet. They likely work (the reconciler
runs the same way regardless of how the panel itself is deployed), but
that's not the same as a confirmed, tested claim — treat it as unverified
rather than assume it either way until someone reports back.
</Callout>