[GH-ISSUE #3117] [Self-hosted][AWG 3.1][iOS] Installer reports success although awg0 fails to start; startup error is hidden #1444

Open
opened 2026-10-13 20:42:37 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @trasbe on GitHub (Sep 2, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/3117

Describe the bug

AmneziaVPN on iOS deploys a self-hosted AmneziaWG 3.1 container and reports the installation as successful. When connecting, the app retries for a long time and eventually displays "Connected", but no traffic passes.

The Docker container remains in the "Up" state, but inside it:

  • the awg0 interface does not exist;
  • awg show returns no interfaces;
  • no amneziawg-go process is running;
  • no UDP service is listening on the configured port inside the container.

The container entrypoint attempts awg-quick up, ignores its failure, and then runs tail -f /dev/null. The container is also launched with --log-driver none, so the startup failure is not visible through docker logs and the container appears healthy.

An older, manually deployed AmneziaWG 2.0 instance on the same VPS continues to work over UDP, so the VPS and its network connection are reachable.

To Reproduce

  1. On a VPS that already has an older manually installed AmneziaWG instance/kernel module, add the server to AmneziaVPN on iOS.
  2. Remove the previous app-managed AWG container, then install AmneziaWG 3.1 through the app.
  3. The app reports a successful installation and creates an amnezia-awg2 container.
  4. Try to connect from iOS.
  5. After long retries, the app reports "Connected", but websites and apps have no network access.
  6. Inspect the container:
$ docker exec amnezia-awg2 ip -br link
lo       UNKNOWN
eth0     UP
eth1     UP

$ docker exec amnezia-awg2 awg show
# no output

$ docker exec amnezia-awg2 awg show awg0
Unable to access interface: No such device
  1. Run the same command that /opt/amnezia/start.sh executes automatically:
$ docker exec amnezia-awg2 awg-quick up /opt/amnezia/awg/awg0.conf
Warning: `/opt/amnezia/awg/awg0.conf' is world accessible
[#] ip link add awg0 type amneziawg
[#] awg setconf awg0 /dev/fd/63
Unable to modify interface: Invalid argument
[#] ip link delete dev awg0

Expected behavior

The installer should verify that awg0 exists, that the daemon/interface is listening, and that the generated configuration was applied successfully. If startup fails, installation should fail with the real error instead of leaving a permanently "Up" container and showing a false "Connected" state on iOS.

If an incompatible host AmneziaWG kernel module is present, the installer should detect it and either upgrade it, reject the installation with a useful message, or force the bundled compatible userspace implementation.

Log files

docker logs cannot be read because the container is deliberately created with --log-driver none:

Error response from daemon: configured logging driver does not support reading

Container process list:

PID 1   dumb-init /opt/amnezia/start.sh
PID 7   /bin/bash /opt/amnezia/start.sh
PID 35  tail -f /dev/null

Relevant generated configuration values (keys omitted):

Address = 10.8.1.0/24
ListenPort = 48716
Jc = 4
Jmin = 10
Jmax = 50
S1 = 95
S2 = 137
S3 = 50
S4 = 12
H1 = 1
H2 = 2
H3 = 3
H4 = 4
ContentPaddingAddition = 10-100
RandomTrailers = on
DisableCookies = on

Smartphone

  • Device: iPhone 17 Pro
  • OS: iOS 26.6.1
  • AmneziaVPN: 5.0.1 (version displayed by the iOS app)

Server

  • OS: Ubuntu, Linux kernel 6.8.0-138-generic, x86_64
  • Container: amnezia-awg2
  • amneziawg-tools v3.1.20260812
  • amneziawg-go 0.0.20250522
  • IPv4 forwarding is enabled
  • Container has working outbound Internet connectivity

Additional context

The host already runs an older manually configured AmneziaWG instance. Since awg-quick selects ip link add awg0 type amneziawg and fails at awg setconf, while the bundled amneziawg-go daemon never starts, an incompatible pre-existing host kernel module is suspected. Regardless of the underlying compatibility problem, the installer currently fails to validate protocol readiness and suppresses the only useful startup error.

No server IP addresses, hostnames, or VPN keys are included in this report.

Originally created by @trasbe on GitHub (Sep 2, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/3117 **Describe the bug** AmneziaVPN on iOS deploys a self-hosted AmneziaWG 3.1 container and reports the installation as successful. When connecting, the app retries for a long time and eventually displays "Connected", but no traffic passes. The Docker container remains in the "Up" state, but inside it: - the `awg0` interface does not exist; - `awg show` returns no interfaces; - no `amneziawg-go` process is running; - no UDP service is listening on the configured port inside the container. The container entrypoint attempts `awg-quick up`, ignores its failure, and then runs `tail -f /dev/null`. The container is also launched with `--log-driver none`, so the startup failure is not visible through `docker logs` and the container appears healthy. An older, manually deployed AmneziaWG 2.0 instance on the same VPS continues to work over UDP, so the VPS and its network connection are reachable. **To Reproduce** 1. On a VPS that already has an older manually installed AmneziaWG instance/kernel module, add the server to AmneziaVPN on iOS. 2. Remove the previous app-managed AWG container, then install AmneziaWG 3.1 through the app. 3. The app reports a successful installation and creates an `amnezia-awg2` container. 4. Try to connect from iOS. 5. After long retries, the app reports "Connected", but websites and apps have no network access. 6. Inspect the container: ```console $ docker exec amnezia-awg2 ip -br link lo UNKNOWN eth0 UP eth1 UP $ docker exec amnezia-awg2 awg show # no output $ docker exec amnezia-awg2 awg show awg0 Unable to access interface: No such device ``` 7. Run the same command that `/opt/amnezia/start.sh` executes automatically: ```console $ docker exec amnezia-awg2 awg-quick up /opt/amnezia/awg/awg0.conf Warning: `/opt/amnezia/awg/awg0.conf' is world accessible [#] ip link add awg0 type amneziawg [#] awg setconf awg0 /dev/fd/63 Unable to modify interface: Invalid argument [#] ip link delete dev awg0 ``` **Expected behavior** The installer should verify that `awg0` exists, that the daemon/interface is listening, and that the generated configuration was applied successfully. If startup fails, installation should fail with the real error instead of leaving a permanently "Up" container and showing a false "Connected" state on iOS. If an incompatible host AmneziaWG kernel module is present, the installer should detect it and either upgrade it, reject the installation with a useful message, or force the bundled compatible userspace implementation. **Log files** `docker logs` cannot be read because the container is deliberately created with `--log-driver none`: ```text Error response from daemon: configured logging driver does not support reading ``` Container process list: ```text PID 1 dumb-init /opt/amnezia/start.sh PID 7 /bin/bash /opt/amnezia/start.sh PID 35 tail -f /dev/null ``` Relevant generated configuration values (keys omitted): ```ini Address = 10.8.1.0/24 ListenPort = 48716 Jc = 4 Jmin = 10 Jmax = 50 S1 = 95 S2 = 137 S3 = 50 S4 = 12 H1 = 1 H2 = 2 H3 = 3 H4 = 4 ContentPaddingAddition = 10-100 RandomTrailers = on DisableCookies = on ``` **Smartphone** - Device: iPhone 17 Pro - OS: iOS 26.6.1 - AmneziaVPN: 5.0.1 (version displayed by the iOS app) **Server** - OS: Ubuntu, Linux kernel `6.8.0-138-generic`, x86_64 - Container: `amnezia-awg2` - `amneziawg-tools v3.1.20260812` - `amneziawg-go 0.0.20250522` - IPv4 forwarding is enabled - Container has working outbound Internet connectivity **Additional context** The host already runs an older manually configured AmneziaWG instance. Since `awg-quick` selects `ip link add awg0 type amneziawg` and fails at `awg setconf`, while the bundled `amneziawg-go` daemon never starts, an incompatible pre-existing host kernel module is suspected. Regardless of the underlying compatibility problem, the installer currently fails to validate protocol readiness and suppresses the only useful startup error. No server IP addresses, hostnames, or VPN keys are included in this report.
Author
Owner

@trasbe commented on GitHub (Sep 2, 2026):

Additional confirmation after a clean reinstall:

Before reproducing this issue, I used the AmneziaVPN client to remove all AmneziaVPN-managed server installations/containers shown for this VPS, then added the server again and installed AWG 3.1 from scratch. The fresh installation produced exactly the same result, so this does not appear to be stale app-managed container data or an old generated configuration.

The same generated AWG 3.1 configuration starts successfully when the bundled userspace implementation is forced manually:

$ docker exec amnezia-awg2 amneziawg-go awg0
$ docker exec amnezia-awg2 bash -lc 'awg setconf awg0 <(awg-quick strip /opt/amnezia/awg/awg0.conf)'
$ docker exec amnezia-awg2 ip -4 address add 10.8.1.0/24 dev awg0
$ docker exec amnezia-awg2 ip link set mtu 1280 up dev awg0

After that, awg0 is UP, awg show awg0 displays the configured peers, and traffic from the iOS client works immediately.

This strongly confirms that the failure is specific to the kernel path selected by awg-quick: ip link add ... type amneziawg succeeds via the pre-existing host module, but awg setconf fails with Invalid argument, so the userspace fallback is never attempted. The workaround is temporary and is lost when the container restarts.

<!-- gh-comment-id:5516561426 --> @trasbe commented on GitHub (Sep 2, 2026): Additional confirmation after a clean reinstall: Before reproducing this issue, I used the AmneziaVPN client to remove all AmneziaVPN-managed server installations/containers shown for this VPS, then added the server again and installed AWG 3.1 from scratch. The fresh installation produced exactly the same result, so this does not appear to be stale app-managed container data or an old generated configuration. The same generated AWG 3.1 configuration starts successfully when the bundled userspace implementation is forced manually: ```console $ docker exec amnezia-awg2 amneziawg-go awg0 $ docker exec amnezia-awg2 bash -lc 'awg setconf awg0 <(awg-quick strip /opt/amnezia/awg/awg0.conf)' $ docker exec amnezia-awg2 ip -4 address add 10.8.1.0/24 dev awg0 $ docker exec amnezia-awg2 ip link set mtu 1280 up dev awg0 ``` After that, `awg0` is UP, `awg show awg0` displays the configured peers, and traffic from the iOS client works immediately. This strongly confirms that the failure is specific to the kernel path selected by `awg-quick`: `ip link add ... type amneziawg` succeeds via the pre-existing host module, but `awg setconf` fails with `Invalid argument`, so the userspace fallback is never attempted. The workaround is temporary and is lost when the container restarts.
Author
Owner

@hezeka commented on GitHub (Sep 7, 2026):

Confirmed the same failure mode on Ubuntu 22.04 (Windows 11 + Android clients), and I was able to pin down the root cause: an outdated amneziawg kernel module on the Docker host.

Symptom

Client 5.0.1.5 hangs on "Connecting" indefinitely, then times out. No handshake at all — not a single packet leaves the client, and tcpdump on the server sees nothing. Container amnezia-awg2 shows Up for days, the port is published by docker-proxy, but nothing is actually listening. Plain WireGuard on the same server works fine.

Root cause

run_container.sh starts the container --privileged, with --cap-add=SYS_MODULE and -v /lib/modules:/lib/modules. As a result awg-quick inside the container uses the host's kernel module instead of the bundled userspace amneziawg-go:

# docker exec -it amnezia-awg2 awg-quick up /opt/amnezia/awg/awg0.conf
[#] ip link add awg0 type amneziawg           <- succeeds, using the old host module
[#] awg setconf awg0 /dev/fd/63
Unable to modify interface: Invalid argument   <- module rejects AWG 3.1 keys
[#] ip link delete dev awg0                    <- interface is removed again

The host module was 1.0.20251009 (the AWG 1.5/2.0 branch). AWG 3.1 keys — HeaderProtectionKey, ContentPaddingAddition, RandomTrailers, DisableCookies — require v3.1.20260812 or newer.

Importantly, wg-quick's userspace fallback never triggers: it only fires when ip link add fails. Here ip link add succeeds and the failure happens on the next step (awg setconf), which has no fallback path.

Why it is completely invisible

  • run_container.sh uses --log-driver none, so docker logs shows nothing
  • start.sh does not check the exit code of awg-quick up and proceeds straight to tail -f /dev/null

The container therefore stays Up forever, the installer reports success, and the client retries into the void. Reinstalling the container does not help — the broken module lives on the host, so every redeploy reproduces the same failure.

This is very likely the same underlying cause behind #3081, #3056, #3055 and #2851.

Fix (user side)

modinfo amneziawg | grep ^version        # if this is not 3.1.2026*, this bug is yours
sudo apt install --only-upgrade amneziawg-dkms
sudo modprobe -r amneziawg && sudo modprobe amneziawg
sudo docker restart amnezia-awg2
sudo docker exec amnezia-awg2 awg show

Two traps worth documenting:

  1. sudo apt install amneziawg does not upgrade amneziawg-dkms. The metapackage dependency is already satisfied, so apt only upgrades amneziawg-tools and silently leaves the old module in place.
  2. The DKMS package version string stays 1.0.0 across all builds, so dkms status looks unchanged before and after. The only reliable check is modinfo amneziawg | grep ^version.

Workaround without touching the host module — forces the container onto userspace amneziawg-go:

printf 'blacklist amneziawg\ninstall amneziawg /bin/false\n' | sudo tee /etc/modprobe.d/no-amneziawg.conf
sudo modprobe -r amneziawg && sudo docker restart amnezia-awg2

Suggested fixes (client side)

  1. After deploying the container, verify the interface actually exists (awg show, or ip link show awg0) and fail the installation with the real error instead of reporting success.
  2. In start.sh, check the exit code of awg-quick up and persist the error somewhere readable — e.g. /opt/amnezia/awg/startup.log — so it survives --log-driver none.
  3. Detect an incompatible host amneziawg module before deploying AWG 3.1 and either refuse with a clear message or force WG_QUICK_USERSPACE_IMPLEMENTATION=amneziawg-go.

Environment

  • Server: Ubuntu 22.04, kernel 5.15.0-164-generic, container amnezia-awg2
  • Host module before: amneziawg 1.0.20251009 (dkms build 1.0.0-0~202511041403)
  • Host module after: amneziawg 3.1.20260812 (dkms build 1.0.0-0~202609061402)
  • Clients: 5.0.1.5 on Windows 11 25H2 and Android — both failed identically, both work now

<!-- gh-comment-id:5572548620 --> @hezeka commented on GitHub (Sep 7, 2026): Confirmed the same failure mode on Ubuntu 22.04 (Windows 11 + Android clients), and I was able to pin down the root cause: **an outdated `amneziawg` kernel module on the Docker host**. ### Symptom Client 5.0.1.5 hangs on "Connecting" indefinitely, then times out. No handshake at all — not a single packet leaves the client, and `tcpdump` on the server sees nothing. Container `amnezia-awg2` shows `Up` for days, the port is published by `docker-proxy`, but nothing is actually listening. Plain WireGuard on the same server works fine. ### Root cause `run_container.sh` starts the container `--privileged`, with `--cap-add=SYS_MODULE` and `-v /lib/modules:/lib/modules`. As a result `awg-quick` inside the container uses the **host's** kernel module instead of the bundled userspace `amneziawg-go`: ``` # docker exec -it amnezia-awg2 awg-quick up /opt/amnezia/awg/awg0.conf [#] ip link add awg0 type amneziawg <- succeeds, using the old host module [#] awg setconf awg0 /dev/fd/63 Unable to modify interface: Invalid argument <- module rejects AWG 3.1 keys [#] ip link delete dev awg0 <- interface is removed again ``` The host module was `1.0.20251009` (the AWG 1.5/2.0 branch). AWG 3.1 keys — `HeaderProtectionKey`, `ContentPaddingAddition`, `RandomTrailers`, `DisableCookies` — require `v3.1.20260812` or newer. Importantly, `wg-quick`'s userspace fallback never triggers: it only fires when `ip link add` fails. Here `ip link add` succeeds and the failure happens on the next step (`awg setconf`), which has no fallback path. ### Why it is completely invisible - `run_container.sh` uses `--log-driver none`, so `docker logs` shows nothing - `start.sh` does not check the exit code of `awg-quick up` and proceeds straight to `tail -f /dev/null` The container therefore stays `Up` forever, the installer reports success, and the client retries into the void. Reinstalling the container does not help — the broken module lives on the host, so every redeploy reproduces the same failure. This is very likely the same underlying cause behind #3081, #3056, #3055 and #2851. ### Fix (user side) ``` modinfo amneziawg | grep ^version # if this is not 3.1.2026*, this bug is yours sudo apt install --only-upgrade amneziawg-dkms sudo modprobe -r amneziawg && sudo modprobe amneziawg sudo docker restart amnezia-awg2 sudo docker exec amnezia-awg2 awg show ``` Two traps worth documenting: 1. `sudo apt install amneziawg` does **not** upgrade `amneziawg-dkms`. The metapackage dependency is already satisfied, so apt only upgrades `amneziawg-tools` and silently leaves the old module in place. 2. The DKMS package version string stays `1.0.0` across all builds, so `dkms status` looks unchanged before and after. The only reliable check is `modinfo amneziawg | grep ^version`. Workaround without touching the host module — forces the container onto userspace `amneziawg-go`: ``` printf 'blacklist amneziawg\ninstall amneziawg /bin/false\n' | sudo tee /etc/modprobe.d/no-amneziawg.conf sudo modprobe -r amneziawg && sudo docker restart amnezia-awg2 ``` ### Suggested fixes (client side) 1. After deploying the container, verify the interface actually exists (`awg show`, or `ip link show awg0`) and fail the installation with the real error instead of reporting success. 2. In `start.sh`, check the exit code of `awg-quick up` and persist the error somewhere readable — e.g. `/opt/amnezia/awg/startup.log` — so it survives `--log-driver none`. 3. Detect an incompatible host `amneziawg` module before deploying AWG 3.1 and either refuse with a clear message or force `WG_QUICK_USERSPACE_IMPLEMENTATION=amneziawg-go`. ### Environment - Server: Ubuntu 22.04, kernel 5.15.0-164-generic, container `amnezia-awg2` - Host module before: `amneziawg 1.0.20251009` (dkms build `1.0.0-0~202511041403`) - Host module after: `amneziawg 3.1.20260812` (dkms build `1.0.0-0~202609061402`) - Clients: 5.0.1.5 on Windows 11 25H2 and Android — both failed identically, both work now ---
Author
Owner

@HeisLuka commented on GitHub (Sep 8, 2026):

I think there is one more failure boundary here beyond the stale host kernel module itself.

In current dev, setupContainer() ultimately returns the result of startupContainerWorker(). The latter starts /opt/amnezia/start.sh using:

sudo docker exec -d $CONTAINER_NAME sh -c
"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh"

The important part is docker exec -d.

This means the status observed by InstallController is the result of starting the detached exec process, not the eventual exit status of /opt/amnezia/start.sh.

Therefore changing start.sh from the current:

awg-quick up /opt/amnezia/awg/awg0.conf
...
tail -f /dev/null

to something like:

set -e
awg-quick up /opt/amnezia/awg/awg0.conf

would be useful, but by itself would still not close the installer false-success path.

A deterministic regression case is:

stale host amneziawg module
↓
ip link add awg0 type amneziawg succeeds
↓
awg setconf fails
↓
start.sh exits non-zero
↓
detached docker exec was already accepted successfully
↓
installer has no protocol readiness evidence

I think installation success should be gated on an explicit post-start readiness check rather than process/container launch.

Conceptually:

upload start.sh
↓
start protocol
↓
bounded readiness wait
↓
verify awg0 exists
↓
verify awg show awg0 succeeds / expected config is readable
↓
only then return installation success

If readiness fails, preserve/capture the startup error and return a protocol-startup failure instead of committing the installation as successful.

The useful invariant would be:

INSTALL_SUCCESS
=>
startup reached protocol readiness
AND awg0 exists
AND applied AWG configuration can be read back

And I would explicitly add this countertest:

start.sh exits non-zero after a successfully accepted docker exec -d

expected:
installation fails

forbidden:
installer reports success merely because detached exec started

This also separates two independent fixes:

A. compatibility policy
old host kernel module vs bundled amneziawg-go

B. installer correctness
process/container launch != protocol readiness

Fixing A would solve this particular host-module case. Fixing B prevents the same false-success class for future AWG startup failures as well.

<!-- gh-comment-id:5586158137 --> @HeisLuka commented on GitHub (Sep 8, 2026): I think there is one more failure boundary here beyond the stale host kernel module itself. In current dev, setupContainer() ultimately returns the result of startupContainerWorker(). The latter starts /opt/amnezia/start.sh using: sudo docker exec -d $CONTAINER_NAME sh -c \ "chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh" The important part is docker exec -d. This means the status observed by InstallController is the result of starting the detached exec process, not the eventual exit status of /opt/amnezia/start.sh. Therefore changing start.sh from the current: awg-quick up /opt/amnezia/awg/awg0.conf ... tail -f /dev/null to something like: set -e awg-quick up /opt/amnezia/awg/awg0.conf would be useful, but by itself would still not close the installer false-success path. A deterministic regression case is: stale host amneziawg module ↓ ip link add awg0 type amneziawg succeeds ↓ awg setconf fails ↓ start.sh exits non-zero ↓ detached docker exec was already accepted successfully ↓ installer has no protocol readiness evidence I think installation success should be gated on an explicit post-start readiness check rather than process/container launch. Conceptually: upload start.sh ↓ start protocol ↓ bounded readiness wait ↓ verify awg0 exists ↓ verify `awg show awg0` succeeds / expected config is readable ↓ only then return installation success If readiness fails, preserve/capture the startup error and return a protocol-startup failure instead of committing the installation as successful. The useful invariant would be: INSTALL_SUCCESS => startup reached protocol readiness AND awg0 exists AND applied AWG configuration can be read back And I would explicitly add this countertest: start.sh exits non-zero after a successfully accepted `docker exec -d` expected: installation fails forbidden: installer reports success merely because detached exec started This also separates two independent fixes: A. compatibility policy old host kernel module vs bundled amneziawg-go B. installer correctness process/container launch != protocol readiness Fixing A would solve this particular host-module case. Fixing B prevents the same false-success class for future AWG startup failures as well.
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#1444
No description provided.