An AmneziaWG config looks like a WireGuard config with two dozen cryptic
lines bolted on: Jc, S4, H2, I1. What
each one does is documented almost nowhere: the official docs describe version 1.5, articles
repeat each other, and generators hand you numbers with no explanation. Below is every
field, which of them must match the server exactly and which are yours
alone (we measured that, and our first reading of it was wrong), and why somebody
else’s config sometimes answers with Line unrecognized. The field list was taken on
31 August 2026 from a live node — out of the awg binary,
amneziawg-tools v3.1.20260812 — rather than copied from other
people’s articles.
The short answer
AmneziaWG is a fork of WireGuard. Everything you know about a WireGuard config still
holds: [Interface] with a key and an address, [Peer] with an
Endpoint and AllowedIPs. The one difference is a set of extra
fields that describe the shape of the packets — how many padding
bytes, what numbers mark each packet type, what goes on the wire first.
The thing worth understanding before any table: these fields are not “tuned correctly” — they are matched. Some of them are a shared alphabet between client and server: off by one and there is no handshake at all. The rest concern only the client, and the server knows nothing about them. The line between those two halves is the most useful thing on this page, and we measured it ourselves.
If you use a ready-made service (ours or anyone else’s), you do not configure these fields: the config arrives complete, with the values already agreed. This article is for people running their own server, moving a config between devices, or working out why a tunnel will not come up.
Every field of a 3.1 config
These are the fields plain WireGuard does not have. All of them live in
[Interface] except the last one. The standard lines
(PrivateKey, Address, DNS, MTU,
PublicKey, Endpoint, AllowedIPs,
PersistentKeepalive) are unchanged and are not repeated here.
| Field | What it sets | Format |
|---|---|---|
Jc |
How many separate datagrams of random content go out before the handshake. | integer, 0 to send none |
Jmin / Jmax |
Length bounds for those datagrams: each length is picked at random from the range. | bytes, Jmin < Jmax |
S1 |
Padding bytes placed before the body of the Initiation packet — the first packet of the handshake. | integer up to 65535 |
S2 |
The same for the Response packet. | integer up to 65535 |
S3 |
The same for the Cookie packet. Added in 2.0. | integer up to 65535 |
S4 |
The same for every data packet. Added in 2.0 — and this is the field that quietly eats your MTU, see below. | integer up to 65535 |
H1–H4 |
Which numbers mark the four packet types (Initiation, Response, Cookie, data) instead of the standard 1, 2, 3, 4. | a number or min-max; ranges must not overlap |
I1–I5 |
The content of up to five standalone datagrams sent first of all, before the junk packets. | tag string, see below |
HeaderProtectionKey |
The key the packet header is encrypted with. Added in 3.x; the 1.5 and 2.0 tools had no such line. | hex key |
ContentPaddingAddition |
Additional content padding. The field exists in 3.x; what exactly it changes we have not measured, and we are not going to invent it. | range |
RandomTrailersDisableCookies |
Switches added in 3.x. By their names: a random trailer on packets, and turning the cookie mechanism off. Not verified by measurement. | on / off |
RekeyAfterTimeRekeyTimeoutRejectAfterTimeKeepaliveTimeoutMaxHandshakeAttempts |
WireGuard’s internal timers, made configurable in 3.x: when to renegotiate keys, how long to wait for an answer, when to call a session dead. These used to be constants in the code. | range |
AdvancedSecurity |
The only field on this list that belongs to [Peer] rather than [Interface]. A leftover from 1.5. |
on / off |
🔴 Where this table stops being certain. What Jc,
S1–S4, H1–H4 and
I1–I5 do comes from reading the engine source and from our own
measurements — that part we know. The five timers,
ContentPaddingAddition, RandomTrailers and
DisableCookies we know less well: the names and formats were read out of the
binary, the meaning inferred from the names, none of it confirmed by measurement. Our own
configs do not use them. We would rather list them with that caveat than pretend the list is
both complete and verified.
I1–I5: the tag grammar
The value of I1…I5 is not a number but a string of tags of the
form <tag argument>, one after another. The engine builds the datagram
contents from them left to right. The supported tags are:
| Tag | What it puts in the packet | Bytes |
|---|---|---|
<b 0xHEX> | Fixed bytes, written as a hex number. | half the hex length |
<r N> | N random bytes. | N |
<rc N> | N random printable characters. | N |
<rd N> | N random digits. | N |
<t> | Current Unix time, 4 bytes, big-endian. | 4 |
<d>, <ds>, <dz N> | The data itself, the data in base64, the data length as a number. Inside I1–I5 these are meaningless: there is no data, those packets are built empty. | — |
An example string: <b 0xa1b2c3><r 32><t> — three
fixed bytes, thirty-two random ones and a timestamp, 39 bytes in total.
Three traps visible straight in the parsing code:
- an unclosed angle bracket gives
missing enclosing >, an unknown tag givesunknown tag; - an empty string and a missing field are the same thing: there is no chain, and behaviour falls back to 1.x;
I2–I5exist but are rarely used: almost every config we have seen setsI1only. Ours too.
What has to match the server
This is the central question when setting things up, and the answer is not obvious. The receiving side identifies a packet by two things at once: the size equals “padding plus message size” AND the number in the header falls into the right range. That splits the fields into two halves — and this is not a guess, we checked it on a test rig on 28 July 2026.
| What differed between client and server | Result |
|---|---|
I1 (different tag strings) and Jc/Jmin/Jmax (5/40/70 against 3/64/256) |
✅ handshake passed, traffic flowed through the tunnel |
S1: 16 against 15, everything else identical |
❌ no handshake |
H1: a different range, everything else identical |
❌ no handshake |
In one line:
S1–S4andH1–H4must match. They are the shared alphabet. One byte of difference and the two sides cannot hear each other.Jc,Jmin,JmaxandI1–I5are the client’s business. The server drops those datagrams without looking inside: to it they are just noise that arrived on the port.
The practical value of the second line is larger than it looks. Because the server knows
nothing about the client’s I fields, you can change them on one device without
touching the server and without dropping anyone else’s live tunnels. Changing S
or H on a running server, by contrast, is an operation with downtime: every
connected client falls off until it picks up the new config.
🔴 And here is the mistake this section exists for. The first edition of
our own internal write-up claimed the exact opposite: “the I values must match
on both sides byte for byte.” It sounded reasonable — fixed bytes are compared byte for
byte in one place in the engine, so surely here too. We built a rig and checked: a client
with one tag string connected to a server with a different one, the handshake passed,
traffic flowed. A plausible explanation and a verified fact turned out to be different
things again — and the difference would have been expensive: under the wrong reading,
changing I1 anywhere would have meant changing it on every server at once.
Five reasons there is no handshake
All five look identical from the outside, and the symptom is misleading: the interface is
up, awg show lists the peer, and the latest handshake line never
appears.
- The
H1–H4ranges overlap. A packet type is decided by which range its number lands in; if two ranges overlap, some packets are read as the wrong type. Non-overlapping ranges follow directly from how identification works. Jminis not smaller thanJmax. The range the length is drawn from is empty.- Only half the fields were carried over.
JcandI1were copied, whileSandHstayed from the previous config — that is exactly the half that had to match. - Different versions on the two sides. See below: 1.5 and 3.x have different field sets, and an extra line is not a warning but a refusal to parse the file.
- MTU was not reduced by
S4. This is the only one of the five where the handshake does pass — and heavy pages hang afterwards. That is the next section.
S4 eats your MTU
S4 is padding on every data packet, which means every packet you send grows
by S4 bytes after the MTU has been applied. Our own measurements: at
MTU 1420 with S4 = 15 the wire carries 1495 bytes,
and on links with S4 = 23 and 27 it carries 1503 and
1507. What actually gets through to the outside is exactly 1500 — so
the last two do not fit.
The rule is simple: subtract S4 from your usual MTU. If it
was 1420 and S4 = 15, set 1405. And check the other side: MTU is set
separately on the client and on the server interface, and fixing only the client cures
exactly half the problem. The byte-level maths and a way to measure your own limit are in
“What MTU to set for WireGuard”.
A separate story that cost us two days. If turning S4 on makes the tunnel
rebuild itself for no reason, look at the kernel module version on the server rather than
at the config. In the module build we ran in early August, the server confused its own
keepalive packet with data and started spurious handshakes. We first worked around it with
a shorter PersistentKeepalive, then fixed it by updating the module. The
config had nothing to do with it — a textbook case of the fix not belonging where the
symptom is.
Versions 1.5, 2.0 and 3.x
The most common reason a config “will not load” is that it belongs to another version. The field set changed twice, and not only by addition:
- 1.5 — had
J1,J2,J3andItime; had noS3orS4. - 2.0 —
J1–J3andItimewere removed as redundant;S3,S4and the signature packetsI1–I5were added. - 3.x — header protection, additional content padding, the switches and the five timers from the first table.
Verified on our node on 31 August 2026: the
amneziawg-tools v3.1.20260812 binary contains no J1,
J2, J3 or Itime at all. A config from an old generator
does not “partly apply” — the tool answers Line unrecognized and stops
parsing. It fails the same way in the other direction: a config with S4 will
not be accepted by a 1.5 server.
One command tells you your version: awg --version. If the two sides differ,
the answer is an upgrade, not a hunt for the right fields.
Loading a config on Android
The official AmneziaWG app for Android takes a config three ways: as a .conf
file, as a .zip archive holding several of them, and as a QR code. The QR route
is the easiest when the config lives on a computer: one command builds the code —
qrencode -t ansiutf8 < tunnel.conf — and the phone reads it off the
screen.
Three things specific to phones:
- MTU does not always come across. If you imported a config and heavy pages hang, check the MTU field in the tunnel settings first — it may have been left empty.
- Junk packets cost battery and data. A large
Jcwith a wideJmin–Jmaxrange is more noticeable on mobile than on Wi-Fi: every reconnection means extra datagrams. Since the server does not check them anyway, it is sensible to keep them modest on a phone. - One device, one key. The same config on two phones at once will not work: the two will keep taking the session away from each other.
How to confirm it is working
On the server one command shows everything — awg show. It prints not
only the familiar keys and latest handshake, but also the values in force for
jc, jmin, jmax, s1–s4 and
h1–h4. That is how you check that what applied is what you wrote
in the file, and not what was left over from last time.
Read it like this:
- No handshake at all, while the network to the server is fine —
suspect an
SorHmismatch first, not the port and not the firewall. - Handshake present, received-bytes counter flat — look at routing
and
AllowedIPs. - Handshake present, small things load, heavy things hang — that
is MTU, almost certainly together with
S4.
From a person’s side, a connection is worth checking by the result rather than by the badge in the app: what address and country a website sees, and whether name lookups leak past the tunnel. How to do that in a minute — “How to check that your connection is really protected”.
The short version: S and H are the shared language of the two
sides — carry them across whole, never adjust them “slightly”. Jc and
I are yours; the server never asks. And do not forget to subtract
S4 from the MTU: it is the one field whose mistake does not stop you
connecting, and breaks the connection later and invisibly.