Bug: one transient PortForward() failure permanently disables port forwarding — the first-time path never retries
Is this urgent?
No
Host OS
macOS 15 (OrbStack)
CPU arch
arm64
VPN service provider
Private Internet Access
What are you using to run the container
docker compose
What is the version of Gluetun
Running version latest built on 2026-05-25T16:14:38.848Z (commit 17f2434)
What's the problem
A single transient failure of the first PortForward() call disables port forwarding for the entire life of the tunnel. There is no retry, nothing recovers it, and the container stays healthy throughout — so the only visible symptom is that inbound traffic silently stops.
The retry logic all lives in KeepPortForward, which is only reached if the first call succeeded:
internal/portforward/service/start.go:
internalToExternalPorts, err := s.settings.PortForwarder.PortForward(ctx, obj)
if err != nil {
return nil, fmt.Errorf("port forwarding for the first time: %w", err)
}
// ... only past this point is the KeepPortForward goroutine startedand the caller in internal/vpn/tunnelup.go logs that error and discards it:
err = l.startPortForwarding(data)
if err != nil {
l.logger.Error(err.Error())
}So the failure is terminal for that tunnel session. onTunnelUp won't run again until the VPN reconnects, and the healthcheck only tests the tunnel — which is genuinely fine — so nothing triggers a reconnect either.
For PIA the exposure window is narrow but real: findAPIIP gives each of its two candidate IPs a single 5 s attempt with no retry, so a ~10 s blip at exactly the wrong moment is enough.
Why I'm confident it's the retry and not the network
I run two gluetun containers on one host, same PIA account, same image digest, different servers. Both took the same PIA API timeout nine minutes apart on 2026-09-13, and ended in opposite states purely because of which code path they were in:
| Container | Path | Error | Outcome |
|---|---|---|---|
gluetun-slskd |
steady state (KeepPortForward) |
ERROR [port forwarding] binding port: … context deadline exceeded |
retried, recovered in 2 s, kept its port |
gluetun (arr) |
first time (Service.Start) |
ERROR [vpn] starting port forwarding service: port forwarding for the first time: … |
dead for 3 h 43 m, until I manually restarted it |
The second one had just reconnected after ~35 min of OpenVPN TLS handshake failures, which is the only reason it was in the first-time path at all.
The PIA API was fine by the time I looked. Probing from inside the wedged container's netns, the candidate findAPIIP had timed out on answered in under a second:
10.3.128.1:19999 -> HTTP response in <1s (the IP that had just "timed out")
10.3.18.1:19999 -> HTTP response in <1s (the real gateway)
10.3.0.1:19999 -> hung 305 s (blackholed; the other candidate)Downstream, /gluetun/forwarded_port is truncated to 0 bytes and the control server reports {"port":0,"ports":[]}, while the torrent client carries on listening on the now-unforwarded port — which on a private tracker means being quietly flagged "not connectable". Nothing logs anything after the single ERROR line: I have exactly two non-HTTP log lines across those 3 h 43 m, both of them that error.
Relationship to #3468
#3468 reports that findAPIIP never tries the real gateway and so fails outright on non-/24 assignments. This is a different bug and the two fixes are complementary. In my case findAPIIP picked a perfectly good address — 10.3.128.1 answers in under a second — it just timed out once. #3468's fix would narrow the window but a transient failure at that moment would still wedge port forwarding permanently, because nothing retries.
Suggested fix
Retry the first-time call rather than abandoning it. Something like a bounded backoff inside Service.Start around the PortForward call, or having onTunnelUp treat the error as retryable instead of only logging it. Even a couple of retries a few seconds apart would have turned this 3 h 43 m outage into a non-event — the same way KeepPortForward already absorbs the identical error in steady state.
Happy to test a patch against a real PIA connection if that helps.
(Analysis assisted by an AI agent; the logs, timings and probe results above are all measured on my own host.)
Share your logs (at least 10 lines)
# --- arr gluetun: reconnects, then loses port forwarding permanently ---
2026-09-13T01:11:34-07:00 INFO [openvpn] [vancouver439] Peer Connection Initiated with [AF_INET]149.22.95.58:8080
2026-09-13T01:11:35-07:00 INFO [openvpn] /sbin/ip addr add dev tun0 10.3.18.3/24 broadcast +
2026-09-13T01:11:35-07:00 INFO [openvpn] Initialization Sequence Completed
2026-09-13T01:11:35-07:00 INFO [MTU discovery] setting VPN interface tun0 MTU to maximum valid MTU 1295
2026-09-13T01:11:35-07:00 INFO [ip getter] Public IP address is 149.22.95.58 (Canada, British Columbia, Vancouver)
2026-09-13T01:11:35-07:00 INFO [port forwarding] starting
2026-09-13T01:11:35-07:00 INFO [port forwarding] Found saved forwarded port data for port 46178
2026-09-13T01:11:45-07:00 ERROR [vpn] starting port forwarding service: port forwarding for the first time: finding API IP address: API IP address not found: trying IP 10.3.128.1: Get "https://10.3.128.1:19999/ping": context deadline exceeded
trying IP 10.3.0.1: Get "https://10.3.0.1:19999/ping": context deadline exceeded
# --- nothing further for 3h43m. Container reported (healthy) the entire time. ---
# --- Next lines in the log are HTTP control-server hits only:
2026-09-13T04:54:07-07:00 INFO [http server] 200 GET /v1/portforward wrote 22B in 36.167µs # -> {"port":0,"ports":[]}
# --- the OTHER container, same host/account, 9 minutes earlier: same error, retried, fine ---
2026-09-13T01:02:23-07:00 INFO [port forwarding] clearing port file /gluetun/forwarded_port
2026-09-13T01:02:23-07:00 ERROR [port forwarding] binding port: Get "https://10.28.128.1:19999/bindPort?payload=<payload>&signature=<signature>": context deadline exceeded
2026-09-13T01:02:23-07:00 INFO [port forwarding] starting
2026-09-13T01:02:23-07:00 INFO [port forwarding] Found saved forwarded port data for port 39656
2026-09-13T01:02:25-07:00 INFO [port forwarding] Port forwarded data expires in 19 days
2026-09-13T01:02:25-07:00 INFO [port forwarding] port forwarded is 39656
2026-09-13T01:02:25-07:00 INFO [port forwarding] writing port file /gluetun/forwarded_portRecovery required a full container restart; nothing lighter re-enters the first-time path.
Share your configuration
services:
gluetun:
image: qmcgaw/gluetun@sha256:<pinned digest>
cap_add: [NET_ADMIN]
environment:
VPN_SERVICE_PROVIDER: private internet access
VPN_TYPE: openvpn
OPENVPN_PROTOCOL: udp
SERVER_NAMES: vancouver439
PORT_FORWARD_ONLY: "on"
VPN_PORT_FORWARDING: "on"
VPN_PORT_FORWARDING_PROVIDER: private internet access
VPN_PORT_FORWARDING_STATUS_FILE: /gluetun/forwarded_port
OPENVPN_USER: <redacted>
OPENVPN_PASSWORD: <redacted>
volumes:
- ./data/gluetun:/gluetunSource: passteque/gluetun