Files
3x-ui/docs/content/docs/en/config/amneziawg.mdx
T
MHSanaei ed31ee432c feat(inbounds): deploy AmneziaWG, TUIC and MTProto inbounds to nodes
The node gate assumed a node-assigned sidecar row would never converge,
because the master's reconcile loops only read node_id IS NULL rows. But
a pushed row is local on the node's own panel, whose AmneziaWG, TUIC and
mtg loops run it like any other; node-adopted rows of these protocols
already worked. Only creating or cloning them from the master was blocked.

Open the three protocols on both lists and fix what assumed the master's
host for a node row:
- A node older than the release that introduced the protocol (MTProto
  v3.5.0, AmneziaWG v3.7.0, TUIC v3.8.0) would hand it to Xray as-is, so
  add and protocol-changing update refuse it, and a node that has not
  reported its version yet. Dev builds ("dev+<sha>") track main and pass.
- MTProto's routeXrayPort is a loopback port on the host running mtg.
  The master no longer allocates one, nor forwards a cloned source's, for
  a node row; the node allocates its own and node sync adopts it back.
- AmneziaWG forwardedPorts were checked against the master's inbounds,
  web port and id-derived relay ports. A node row is now checked against
  its own node's inbounds; the node re-checks what only it knows.
  UpdateInbound restores the stored nodeId before that check.

Verified on a docker master+node pair: AWG, routed MTProto and TUIC
created and cloned from the master start on the node (interface, mtg,
tuic-server); an AWG client added and an MTProto client edited on the
master apply on the node; the node-chosen egress port survives edits.

Closes #6306
2026-10-03 00:26:46 +02:00

182 lines
8.3 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, `0`-`1552` / `0`-`1608`: the packets are `148 + S1` and `92 + S2` bytes and must fit the 1700-byte receive buffer amneziawg-go uses on iOS. The panel also rejects `S1 + 56 = S2`, which would give both packets the same size on the wire (amneziawg-go itself accepts it). An AmneziaWG outbound takes the remote server's values as they are, up to `65535`. |
| **S3** | Cookie-reply padding, `0`-`1636`: the reply is `64 + S3` bytes and must fit the 1700-byte receive buffer amneziawg-go uses on iOS. An outbound, as with S1/S2, takes the remote server's value up to `65535`. |
| **S4** | Transport (data) packet padding, `0`-`32`. |
| **H1-H4** | Header values that replace WireGuard's message-type field. Each is a single integer or a `low-high` range, and the four must not overlap — amneziawg-go and the kernel module refuse the whole device otherwise. `1`-`4` are WireGuard's own types and the engine default for a blank field: valid, but without a HeaderProtectionKey the type field then reads like plain WireGuard. |
| **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
```
## Multi-node (sub-nodes)
An AmneziaWG inbound can be created on, or cloned to, a sub-node. The node's
own panel runs the interface, so the node must run panel **v3.7.0** or newer;
the master refuses an older node, or one that has not reported its version yet.
A client's `forwardedPorts` are checked against the ports in use on that node.
## Not yet covered
<Callout type="info">
- **Telegram bot** — AmneziaWG inbounds haven't been exercised through the
bot yet. Treat it as unverified until someone reports back.
</Callout>