Mayak Blog

How to set up AmneziaWG: what every line of the config means

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.

FieldWhat it setsFormat
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
H1H4 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
I1I5 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
RandomTrailers
DisableCookies
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
RekeyAfterTime
RekeyTimeout
RejectAfterTime
KeepaliveTimeout
MaxHandshakeAttempts
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, S1S4, H1H4 and I1I5 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 I1I5 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:

TagWhat it puts in the packetBytes
<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 I1I5 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 gives unknown tag;
  • an empty string and a missing field are the same thing: there is no chain, and behaviour falls back to 1.x;
  • I2I5 exist but are rarely used: almost every config we have seen sets I1 only. 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 serverResult
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:

  • S1S4 and H1H4 must match. They are the shared alphabet. One byte of difference and the two sides cannot hear each other.
  • Jc, Jmin, Jmax and I1I5 are 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.

  1. The H1H4 ranges 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.
  2. Jmin is not smaller than Jmax. The range the length is drawn from is empty.
  3. Only half the fields were carried over. Jc and I1 were copied, while S and H stayed from the previous config — that is exactly the half that had to match.
  4. 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.
  5. 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, J3 and Itime; had no S3 or S4.
  • 2.0 — J1J3 and Itime were removed as redundant; S3, S4 and the signature packets I1I5 were 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 Jc with a wide JminJmax range 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, s1s4 and h1h4. 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 S or H mismatch 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.

Try Mayak

Mayak runs on AmneziaWG, and everything above is handled on the service side: the parameters are agreed with the server, the MTU is computed with S4 taken into account, and the config arrives in the app ready to use. Our own servers in the Netherlands, Poland and Russia, and an honest label showing which route you are on. 7 days free once you confirm your email, no card required.

The account is created right in the app. A confirmed email opens the access — 7 free days.