Skip to content

Server mode ignores POSTUP/POSTDOWN env vars; hardcoded PostUp/PostDown uses eth+ with -A #417

Description

@Tomtiger66

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

Server mode ignores POSTUP/POSTDOWN env vars; hardcoded PostUp/PostDown uses eth+ with -A

Expected Behavior

Setting POSTUP/POSTDOWN as container env vars should be honored when the server-mode wg0.conf is generated, OR — if these env vars are not supported for server mode — the auto-generated PostUp/PostDown should detect the actual default-route interface (e.g. via ip route get 1.1.1.1) instead of hardcoding eth+, since ens*/enp* (systemd "predictable network interface names") are the default on virtually every current Debian/Ubuntu host — not just an edge case with multiple Docker networks as in #169.

Current Behavior

  1. POSTUP/POSTDOWN env vars are silently ignored in server mode. Setting them on the container (Kubernetes Deployment env, or docker run -e POSTUP=... -e POSTDOWN=...) has no effect on the generated /config/wg_confs/wg0.conf. The boot log confirms SERVERURL, SERVERPORT, PEERS, PEERDNS, INTERNAL_SUBNET, ALLOWEDIPS are recognized and logged (**** ... is set to ... ****), but POSTUP/POSTDOWN never appear in the log and never make it into the generated config.
  2. Regardless of what's set, the generated wg0.conf always contains:
    PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
    PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE
    
  3. eth+ never matches on hosts using ens3, enp0s3, etc. (default naming scheme on current Ubuntu/Debian via systemd) → MASQUERADE never applies → peers get a successful WireGuard handshake and can reach the server, but get no internet access through the tunnel (NAT never happens, return traffic can't find its way back).
  4. Separately, the hardcoded -A FORWARD appends the ACCEPT rules to the end of the FORWARD chain. On hosts running Kubernetes with kube-router (or any other tool that inserts its own terminating rules earlier in FORWARD, e.g. KUBE-ROUTER-FORWARD), the WireGuard FORWARD rules are never reached — -I FORWARD 1 (insert at the top) would avoid this.

Once both are fixed manually — replacing eth+ with the correct interface and -A→-I 1 for the FORWARD rules — the tunnel works correctly (full NAT, bidirectional traffic, confirmed via tcpdump/conntrack and a working speedtest through the tunnel).

Evidence that eth+ never matches

Actual default-route interface on the test host:

$ ip route get 1.1.1.1
1.1.1.1 via 195.90.216.1 dev ens3 src 195.90.216.57 uid 1000
    cache

Resulting iptables counters after peers had already exchanged traffic through the tunnel (handshake successful, peer had sent data) — the eth+ MASQUERADE rule sits at 0 packets / 0 bytes the entire time, proving it never matches:

$ sudo iptables -t nat -L POSTROUTING -n -v
Chain POSTROUTING (policy ACCEPT ...)
 pkts bytes target     prot opt in     out     source               destination
 ...
    0     0 MASQUERADE  all  --  *      eth+    0.0.0.0/0            0.0.0.0/0

Steps to Reproduce

  1. Run lscr.io/linuxserver/wireguard:latest in server mode (PEERS=1 or more) on any host where the default-route interface is not literally named eth0/eth* — e.g. a stock Ubuntu 24.04/26.04 KVM VPS, where the interface is ens3.
  2. Optionally set POSTUP/POSTDOWN env vars to something custom — observe they have no effect.
  3. After the container starts, inspect the generated config:
    docker exec <container> cat /config/wg_confs/wg0.conf
    
    → PostUp/PostDown contain -o eth+ -j MASQUERADE, regardless of the actual interface name or any POSTUP/POSTDOWN env var set.
  4. Connect a peer, confirm handshake succeeds (wg show shows latest handshake + received bytes), but the peer has no internet access (asymmetric traffic: bytes received >> bytes sent, since return traffic is never NAT'd back to the peer).

Environment

  • Image: lscr.io/linuxserver/wireguard, tag latest, LSIO version 1.0.20260223-r0-ls120
  • Host: Ubuntu 26.04 LTS, KVM VPS, default-route interface ens3
  • Also reproduced on a second, independent host with the same image, default-route interface eth0 (confirming this isn't ens3-specific — the underlying issue is that the interface is never actually queried, only assumed)
  • Deployed via Kubernetes (k3s) with hostNetwork: true, dnsPolicy: ClusterFirstWithHostNet — the FORWARD-chain-ordering issue (-A vs -I 1) is specific to this deployment style but the eth+ NAT issue reproduces identically on plain docker run/docker-compose.

Workaround currently in use

A Helm post-install/post-upgrade hook Job that waits for wg0.conf to be generated, then patches it in place:

sed -i \
  -e "s#iptables -A FORWARD -i %i -j ACCEPT#iptables -I FORWARD 1 -i %i -j ACCEPT#g" \
  -e "s#iptables -A FORWARD -o %i -j ACCEPT#iptables -I FORWARD 1 -o %i -j ACCEPT#g" \
  -e 's#-o eth+ -j MASQUERADE#-o $(ip route get 1.1.1.1 | awk '"'"'{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}'"'"') -j MASQUERADE#g' \
  /config/wg_confs/wg0.conf

...followed by a pod restart so wg-quick re-reads the corrected file. This works because PostUp/PostDown are evaluated via shell at every tunnel activation, so embedding the unresolved $(ip route get ...) expression (rather than a resolved value) keeps it correct across interface changes / redeploys — but this obviously shouldn't be necessary as a post-deploy patch.

Happy to provide the full Helm chart / more logs if useful.


Docker creation

Deployed via Helm/Kubernetes (k3s), not docker run/docker-compose directly, but the equivalent env-var configuration is:

env:
  - name: PUID
    value: "1000"
  - name: PGID
    value: "1000"
  - name: TZ
    value: Europe/Berlin
  - name: SERVERURL
    value: "vpn.example.org"
  - name: SERVERPORT
    value: "51820"
  - name: PEERS
    value: "4"
  - name: PEERDNS
    value: "1.1.1.1,1.0.0.1"
  - name: INTERNAL_SUBNET
    value: "10.13.13.0"
  - name: ALLOWEDIPS
    value: "0.0.0.0/0"
  - name: POSTUP
    value: "iptables -I FORWARD 1 -i %i -j ACCEPT; iptables -I FORWARD 1 -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o $(ip route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i==\"dev\"){print $(i+1); exit}}') -j MASQUERADE"
  - name: POSTDOWN
    value: "iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o $(ip route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i==\"dev\"){print $(i+1); exit}}') -j MASQUERADE"
  - name: LOG_CONFS
    value: "true"

As described above, POSTUP/POSTDOWN here have no effect whatsoever — wg0.conf is generated with the hardcoded eth+/-A regardless of these values.

Container logs

Full log from a fresh install (empty /config), reproducing the bug from a clean state. Peer QR codes and private/preshared keys have been redacted/omitted, everything else is unedited.

[migrations] started
[migrations] no migrations found
───────────────────────────────────────

      ██╗     ███████╗██╗ ██████╗
      ██║     ██╔════╝██║██╔═══██╗
      ██║     ███████╗██║██║   ██║
      ██║     ╚════██║██║██║   ██║
      ███████╗███████║██║╚██████╔╝
      ╚══════╝╚══════╝╚═╝ ╚═════╝

   Brought to you by linuxserver.io
───────────────────────────────────────

To support the app dev(s) visit:
WireGuard: https://www.wireguard.com/donations/

To support LSIO projects visit:
https://www.linuxserver.io/donate/

───────────────────────────────────────
GID/UID
───────────────────────────────────────

User UID:    1000
User GID:    1000
───────────────────────────────────────
Linuxserver.io version: 1.0.20260223-r0-ls120
Build-date: 2026-08-06T13:03:25+00:00
───────────────────────────────────────

Uname info: Linux <hostname> 7.0.0-30-generic #30-Ubuntu SMP PREEMPT_DYNAMIC Fri Jul 31 18:22:54 UTC 2026 x86_64 GNU/Linux
**** As the wireguard module is already active you can remove the SYS_MODULE capability from your container run/compose. ****
****     If your host does not automatically load the iptables module, you may still need the SYS_MODULE capability.     ****
**** Server mode is selected ****
**** External server address is set to vpn.example.org ****
**** External server port is set to 51820. Make sure that port is properly forwarded to port 51820 inside this container ****
**** Internal subnet is set to 10.13.13.0 ****
**** AllowedIPs for peers 0.0.0.0/0 ****
**** Peer DNS servers will be set to 1.1.1.1,1.0.0.1 ****
**** No wg0.conf found (maybe an initial install), generating 1 server and 4 peer/client confs ****
PEER 1 QR code (conf file is saved under /config/peer1):
[... QR code and keys redacted for this report ...]
PEER 2 QR code (conf file is saved under /config/peer2):
[... QR code and keys redacted for this report ...]
PEER 3 QR code (conf file is saved under /config/peer3):
[... QR code and keys redacted for this report ...]
PEER 4 QR code (conf file is saved under /config/peer4):
[... QR code and keys redacted for this report ...]
[custom-init] No custom files found, skipping...
**** Disabling CoreDNS ****
**** Found WG conf /config/wg_confs/wg0.conf, adding to list ****
**** Activating tunnel /config/wg_confs/wg0.conf ****
[#] ip link add dev wg0 type wireguard
[#] wg addconf wg0 /dev/fd/63
[#] ip -4 address add 10.13.13.1 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 10.13.13.5/32 dev wg0
[#] ip -4 route add 10.13.13.4/32 dev wg0
[#] ip -4 route add 10.13.13.3/32 dev wg0
[#] ip -4 route add 10.13.13.2/32 dev wg0
[#] iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
**** All tunnels are now active ****
[ls.io-init] done.

Resulting /config/wg_confs/wg0.conf (keys redacted) — note POSTUP/POSTDOWN env vars shown above under "Docker creation" had zero effect on this output:

[Interface]
Address = 10.13.13.1
ListenPort = 51820
PrivateKey = <redacted>
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE

[Peer]
# peer1
PublicKey = <redacted>
PresharedKey = <redacted>
AllowedIPs = 10.13.13.2/32

[Peer]
# peer2
PublicKey = <redacted>
PresharedKey = <redacted>
AllowedIPs = 10.13.13.3/32

[Peer]
# peer3
PublicKey = <redacted>
PresharedKey = <redacted>
AllowedIPs = 10.13.13.4/32

[Peer]
# peer4
PublicKey = <redacted>
PresharedKey = <redacted>
AllowedIPs = 10.13.13.5/32

Expected Behavior

Expected Behavior

Setting POSTUP/POSTDOWN as container env vars should be honored when the server-mode wg0.conf is generated, OR — if these env vars are not supported for server mode — the auto-generated PostUp/PostDown should detect the actual default-route interface (e.g. via ip route get 1.1.1.1) instead of hardcoding eth+, since ens*/enp* (systemd "predictable network interface names") are the default on virtually every current Debian/Ubuntu host — not just an edge case with multiple Docker networks as in #169.

Steps To Reproduce

Steps to Reproduce

  1. Run lscr.io/linuxserver/wireguard:latest in server mode (PEERS=1 or more) on any host where the default-route interface is not literally named eth0/eth* — e.g. a stock Ubuntu 24.04/26.04 KVM VPS, where the interface is ens3.
  2. Optionally set POSTUP/POSTDOWN env vars to something custom — observe they have no effect.
  3. After the container starts, inspect the generated config:
    docker exec <container> cat /config/wg_confs/wg0.conf
    
    → PostUp/PostDown contain -o eth+ -j MASQUERADE, regardless of the actual interface name or any POSTUP/POSTDOWN env var set.
  4. Connect a peer, confirm handshake succeeds (wg show shows latest handshake + received bytes), but the peer has no internet access (asymmetric traffic: bytes received >> bytes sent, since return traffic is never NAT'd back to the peer).

Environment

## Environment

- Image: `lscr.io/linuxserver/wireguard`, tag `latest`, LSIO version `1.0.20260223-r0-ls120`
- Host: Ubuntu 26.04 LTS, KVM VPS, default-route interface `ens3`
- Also reproduced on a second, independent host with the same image, default-route interface `eth0` (confirming this isn't `ens3`-specific — the underlying issue is that the interface is never actually queried, only assumed)
- Deployed via Kubernetes (k3s) with `hostNetwork: true`, `dnsPolicy: ClusterFirstWithHostNet` — the FORWARD-chain-ordering issue (`-A` vs `-I 1`) is specific to this deployment style but the `eth+` NAT issue reproduces identically on plain `docker run`/`docker-compose`.

CPU architecture

x86-64

Docker creation

## Docker creation

Deployed via Helm/Kubernetes (k3s), not `docker run`/`docker-compose` directly, but the equivalent env-var configuration is:


env:
  - name: PUID
    value: "1000"
  - name: PGID
    value: "1000"
  - name: TZ
    value: Europe/Berlin
  - name: SERVERURL
    value: "vpn.example.org"
  - name: SERVERPORT
    value: "51820"
  - name: PEERS
    value: "4"
  - name: PEERDNS
    value: "1.1.1.1,1.0.0.1"
  - name: INTERNAL_SUBNET
    value: "10.13.13.0"
  - name: ALLOWEDIPS
    value: "0.0.0.0/0"
  - name: POSTUP
    value: "iptables -I FORWARD 1 -i %i -j ACCEPT; iptables -I FORWARD 1 -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o $(ip route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i==\"dev\"){print $(i+1); exit}}') -j MASQUERADE"
  - name: POSTDOWN
    value: "iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o $(ip route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i==\"dev\"){print $(i+1); exit}}') -j MASQUERADE"
  - name: LOG_CONFS
    value: "true"

As described above, `POSTUP`/`POSTDOWN` here have no effect whatsoever — `wg0.conf` is generated with the hardcoded `eth+`/`-A` regardless of these values.

Container logs

## Container logs

Full log from a fresh install (empty `/config`), reproducing the bug from a clean state. Peer QR codes and private/preshared keys have been redacted/omitted, everything else is unedited.


[migrations] started
[migrations] no migrations found
───────────────────────────────────────

      ██╗     ███████╗██╗ ██████╗
      ██║     ██╔════╝██║██╔═══██╗
      ██║     ███████╗██║██║   ██║
      ██║     ╚════██║██║██║   ██║
      ███████╗███████║██║╚██████╔╝
      ╚══════╝╚══════╝╚═╝ ╚═════╝

   Brought to you by linuxserver.io
───────────────────────────────────────

To support the app dev(s) visit:
WireGuard: https://www.wireguard.com/donations/

To support LSIO projects visit:
https://www.linuxserver.io/donate/

───────────────────────────────────────
GID/UID
───────────────────────────────────────

User UID:    1000
User GID:    1000
───────────────────────────────────────
Linuxserver.io version: 1.0.20260223-r0-ls120
Build-date: 2026-08-06T13:03:25+00:00
───────────────────────────────────────

Uname info: Linux <hostname> 7.0.0-30-generic #30-Ubuntu SMP PREEMPT_DYNAMIC Fri Jul 31 18:22:54 UTC 2026 x86_64 GNU/Linux
**** As the wireguard module is already active you can remove the SYS_MODULE capability from your container run/compose. ****
****     If your host does not automatically load the iptables module, you may still need the SYS_MODULE capability.     ****
**** Server mode is selected ****
**** External server address is set to vpn.example.org ****
**** External server port is set to 51820. Make sure that port is properly forwarded to port 51820 inside this container ****
**** Internal subnet is set to 10.13.13.0 ****
**** AllowedIPs for peers 0.0.0.0/0 ****
**** Peer DNS servers will be set to 1.1.1.1,1.0.0.1 ****
**** No wg0.conf found (maybe an initial install), generating 1 server and 4 peer/client confs ****
PEER 1 QR code (conf file is saved under /config/peer1):
[... QR code and keys redacted for this report ...]
PEER 2 QR code (conf file is saved under /config/peer2):
[... QR code and keys redacted for this report ...]
PEER 3 QR code (conf file is saved under /config/peer3):
[... QR code and keys redacted for this report ...]
PEER 4 QR code (conf file is saved under /config/peer4):
[... QR code and keys redacted for this report ...]
[custom-init] No custom files found, skipping...
**** Disabling CoreDNS ****
**** Found WG conf /config/wg_confs/wg0.conf, adding to list ****
**** Activating tunnel /config/wg_confs/wg0.conf ****
[#] ip link add dev wg0 type wireguard
[#] wg addconf wg0 /dev/fd/63
[#] ip -4 address add 10.13.13.1 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 10.13.13.5/32 dev wg0
[#] ip -4 route add 10.13.13.4/32 dev wg0
[#] ip -4 route add 10.13.13.3/32 dev wg0
[#] ip -4 route add 10.13.13.2/32 dev wg0
[#] iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
**** All tunnels are now active ****
[ls.io-init] done.


Resulting `/config/wg_confs/wg0.conf` (keys redacted) — note `POSTUP`/`POSTDOWN` env vars shown above under "Docker creation" had zero effect on this output:


[Interface]
Address = 10.13.13.1
ListenPort = 51820
PrivateKey = <redacted>
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE

[Peer]
# peer1
PublicKey = <redacted>
PresharedKey = <redacted>
AllowedIPs = 10.13.13.2/32

[Peer]
# peer2
PublicKey = <redacted>
PresharedKey = <redacted>
AllowedIPs = 10.13.13.3/32

[Peer]
# peer3
PublicKey = <redacted>
PresharedKey = <redacted>
AllowedIPs = 10.13.13.4/32

[Peer]
# peer4
PublicKey = <redacted>
PresharedKey = <redacted>
AllowedIPs = 10.13.13.5/32

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions