new-peer.sh now generates MTU = 1280 by default, still overridable with WG_MTU.
runbooks/vpn-remote-access.md no longer repeats the stale SNAT, host-identity and Android IPv6 claims, and gains a Troubleshooting section.
The daemonset.yaml header comment gets the same SNAT correction.
The WireGuard DaemonSet does not roll: kubectl kustomize gives identical ConfigMap and DaemonSet hashes on origin/main and on this branch. The runbooks ConfigMap is content hashed, so the runbooks site does roll. No sensitive path is touched.
Why 1280
On mobile data at 1420 the handshake was healthy but names and pages failed. Clearing the Android app's MTU field fixed it. Upstream GoBackend.java uses getMtu().orElse(1280), so a blank field means 1280 on that backend. 1280 is the value that worked, and it is also the IPv6 minimum.
This is inferred from one before-and-after, on one phone and one carrier. The path MTU was not measured. Profiles already on devices do not change.
Is the server's 1420 a problem for a 1280 client?
The server's wg0 stays at 1420, because changing it restarts all WireGuard pods. Flagged, not changed.
TCP: should be fine. A 1280 client advertises an MSS of 1240, which caps what the far end sends, so outer packets stay near 1340 bytes.
Large UDP from server to phone: residual risk. MSS does not apply. A datagram with an inner size of 1281-1420 bytes goes out as up to 1480 outer bytes and could be lost on the same path. QUIC should recover by probing; large DNS answers are the likeliest casualty.
Neither was captured on the wire. If large UDP replies turn out to fail, the fix is a lower server MTU, as a deliberate rollout.
Stale claims
Where
Claim
Result
Runbook limitations
everything arrives SNATed
Fixed. LAN and internet see the node address; VIPs see 10.90.0.x
Runbook verification table
Gateway sees a host-identity source
Fixed. world measured on VIPs .23 and .24, not observed on the Gateway's .20
Runbook limitations
IPv6 leaks on Android
Fixed from upstream source, not observed on a device
Runbook, "nothing handshakes"
wg show separates a bad forward from a bad key
Fixed. It does not; the conntrack table does
daemonset.yaml header
SNAT claim
Fixed, comment only
peers.conf
"No peers enrolled yet"
Left. A comment edit changes the ConfigMap hash and rolls the DaemonSet
clusters/base/cluster-vars.yaml
coverage comment omits WG_CLIENT_CIDR
Left. Sensitive path
platform/vpn-ingress/README.md, runbook status line
"no peer has ever completed a handshake"; VPN "inert"
Left. Outside this change
Gate
Pinned toolbox, from the worktree, on the committed tree: rc 3, UNDETERMINED, not green.
Not run: gitea-settings (no token locally).
Also skipped: conformance L2/L3, the declared no-cluster gap.
## Summary
- `new-peer.sh` now generates `MTU = 1280` by default, still overridable with `WG_MTU`.
- `runbooks/vpn-remote-access.md` no longer repeats the stale SNAT, `host`-identity and Android IPv6 claims, and gains a Troubleshooting section.
- The `daemonset.yaml` header comment gets the same SNAT correction.
**The WireGuard DaemonSet does not roll**: `kubectl kustomize` gives identical ConfigMap and DaemonSet hashes on `origin/main` and on this branch. The runbooks ConfigMap is content hashed, so the runbooks site does roll. No sensitive path is touched.
## Why 1280
On mobile data at 1420 the handshake was healthy but names and pages failed. Clearing the Android app's MTU field fixed it. Upstream `GoBackend.java` uses `getMtu().orElse(1280)`, so a blank field means 1280 on that backend. 1280 is the value that worked, and it is also the IPv6 minimum.
**This is inferred from one before-and-after, on one phone and one carrier.** The path MTU was not measured. Profiles already on devices do not change.
## Is the server's 1420 a problem for a 1280 client?
The server's `wg0` stays at 1420, because changing it restarts all WireGuard pods. **Flagged, not changed.**
- **TCP: should be fine.** A 1280 client advertises an MSS of 1240, which caps what the far end sends, so outer packets stay near 1340 bytes.
- **Large UDP from server to phone: residual risk.** MSS does not apply. A datagram with an inner size of 1281-1420 bytes goes out as up to 1480 outer bytes and could be lost on the same path. QUIC should recover by probing; large DNS answers are the likeliest casualty.
Neither was captured on the wire. If large UDP replies turn out to fail, the fix is a lower server MTU, as a deliberate rollout.
## Stale claims
| Where | Claim | Result |
|---|---|---|
| Runbook limitations | everything arrives SNATed | Fixed. LAN and internet see the node address; VIPs see `10.90.0.x` |
| Runbook verification table | Gateway sees a `host`-identity source | Fixed. `world` measured on VIPs `.23` and `.24`, **not observed on the Gateway's `.20`** |
| Runbook limitations | IPv6 leaks on Android | Fixed from upstream source, not observed on a device |
| Runbook, "nothing handshakes" | `wg show` separates a bad forward from a bad key | Fixed. It does not; the conntrack table does |
| `daemonset.yaml` header | SNAT claim | Fixed, comment only |
| `peers.conf` | "No peers enrolled yet" | **Left.** A comment edit changes the ConfigMap hash and rolls the DaemonSet |
| `clusters/base/cluster-vars.yaml` | coverage comment omits `WG_CLIENT_CIDR` | **Left.** Sensitive path |
| `platform/vpn-ingress/README.md`, runbook status line | "no peer has ever completed a handshake"; VPN "inert" | **Left.** Outside this change |
## Gate
Pinned toolbox, from the worktree, on the committed tree: **rc 3, UNDETERMINED, not green.**
- Not run: `gitea-settings` (no token locally).
- Also skipped: `conformance` L2/L3, the declared no-cluster gap.
- Passed: `spec-immutability`, `decisions-log`, `lint`, `l0`, `render-golden`, `l1`. `approval-policy` reported N/A offline.
Full list of what was and was not verified: `decisions/2026-11-08-wg-client-mtu-docs.md`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
new-peer.sh generated MTU 1420, which stalled traffic on mobile data
despite a healthy handshake (one phone, one carrier, 2026-11-09). Default
to 1280, overridable through WG_MTU.
Correct stale claims in the runbook and the DaemonSet header against
live BPF conntrack: LoadBalancer VIPs see 10.90.0.x as world, not a
SNATed host identity. Add a troubleshooting section.
The DaemonSet render is unchanged, so WireGuard pods do not roll.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
new-peer.shnow generatesMTU = 1280by default, still overridable withWG_MTU.runbooks/vpn-remote-access.mdno longer repeats the stale SNAT,host-identity and Android IPv6 claims, and gains a Troubleshooting section.daemonset.yamlheader comment gets the same SNAT correction.The WireGuard DaemonSet does not roll:
kubectl kustomizegives identical ConfigMap and DaemonSet hashes onorigin/mainand on this branch. The runbooks ConfigMap is content hashed, so the runbooks site does roll. No sensitive path is touched.Why 1280
On mobile data at 1420 the handshake was healthy but names and pages failed. Clearing the Android app's MTU field fixed it. Upstream
GoBackend.javausesgetMtu().orElse(1280), so a blank field means 1280 on that backend. 1280 is the value that worked, and it is also the IPv6 minimum.This is inferred from one before-and-after, on one phone and one carrier. The path MTU was not measured. Profiles already on devices do not change.
Is the server's 1420 a problem for a 1280 client?
The server's
wg0stays at 1420, because changing it restarts all WireGuard pods. Flagged, not changed.Neither was captured on the wire. If large UDP replies turn out to fail, the fix is a lower server MTU, as a deliberate rollout.
Stale claims
10.90.0.xhost-identity sourceworldmeasured on VIPs.23and.24, not observed on the Gateway's.20wg showseparates a bad forward from a bad keydaemonset.yamlheaderpeers.confclusters/base/cluster-vars.yamlWG_CLIENT_CIDRplatform/vpn-ingress/README.md, runbook status lineGate
Pinned toolbox, from the worktree, on the committed tree: rc 3, UNDETERMINED, not green.
gitea-settings(no token locally).conformanceL2/L3, the declared no-cluster gap.spec-immutability,decisions-log,lint,l0,render-golden,l1.approval-policyreported N/A offline.Full list of what was and was not verified:
decisions/2026-11-08-wg-client-mtu-docs.md.🤖 Generated with Claude Code