Automatically refreshes Private Internet Access (PIA) WireGuard configs for Gluetun only when the tunnel is actually down. It runs alongside Gluetun in Docker, regenerates wg0.conf with a bundled pia-wg-config binary built from source, and restarts the Gluetun container only after consecutive failures.
This project bundles a forked pia-wg-config binary (originally by Kyle Lucas, forked via Ephemeral-Dust), which generates WireGuard configurations for PIA. It is the tool recommended by the Gluetun documentation for PIA WireGuard setups. The fork adds support for server name output, port-forwarding server filtering, and fixes token generation on PIA's newer server format.
PIA WireGuard sessions can expire. If Gluetun restarts or loses the tunnel after expiry, it can get stuck until a fresh config is generated, manually or via some other means. This container monitors connectivity inside Gluetun and regenerates the config only when needed. It will also generate the wg0.conf config if it doesn't exist, so useful for those who are installing Gluetun for the first time.
- Check VPN status via Gluetun's control server API (
/v1/publicip/ip). - When healthy, check every
HEALTHY_CHECK_INTERVAL_SECONDS(default 30 minutes). - On failure, switch to faster checks every
CHECK_INTERVAL_SECONDS(default 60s). - After
FAIL_THRESHOLDconsecutive failures, generate a new config and restart Gluetun. - If config generation fails repeatedly, stop retrying after
MAX_GENERATION_RETRIESuntil connectivity recovers. - With port forwarding enabled, also monitors
/v1/portforwardand automatically syncsSERVER_NAMES.
- Docker socket mount (
/var/run/docker.sock) fordocker execanddocker restart/docker compose. - Gluetun config directory mounted into this container at
/config.
Required:
PIA_USERNAMEPIA_PASSWORDPIA_REGION
Optional:
GLUETUN_CONTAINER(default:gluetun)WG_CONF_PATH(default:/config/wg0.conf)CHECK_INTERVAL_SECONDS(default:60) - interval when tunnel is down or degradedHEALTHY_CHECK_INTERVAL_SECONDS(default:1800) - interval when tunnel is healthyFAIL_THRESHOLD(default:3) - consecutive failures before regenerating configMAX_GENERATION_RETRIES(default:3) - max config generation attempts before waiting for recoveryHEALTH_LOG_INTERVAL(default:10) - log "Tunnel healthy" every N successful checksLOG_LEVEL(default:info) - set todebugfor verbose loggingPIA_PORT_FORWARDING(default:false) - enable port forwarding monitoring and server filteringDOCKER_COMPOSE_HOST_DIR(optional) - absolute host path to compose directory for automaticSERVER_NAMESupdatesDOCKER_COMPOSE_ENV_FILE(default:.env) - env file name to update withSERVER_NAMESON_FAILURE_SCRIPT(optional) - script to run when failure threshold is reachedON_RECOVERY_SCRIPT(optional) - script to run when tunnel recoversON_PORT_CHANGE_SCRIPT(optional) - script to run when the forwarded port changesCOMPOSE_UP_TIMEOUT(default:300) - seconds beforedocker compose upis killed if hung; prevents pia-wg-refresh from freezing permanently if the docker daemon's container start hangs at the kernel levelPIA_WG_CONFIG_BIN(default:/usr/local/bin/pia-wg-config)PIA_WG_CONFIG_URL(optional: if set, download/replacepia-wg-configon startup)PIA_WG_CONFIG_SHA256(optional: verify the download before installing)SELF_TEST(optional: set to1to exit after startup checks)
When using Gluetun's port forwarding feature with VPN_SERVICE_PROVIDER=custom, Gluetun requires SERVER_NAMES to match the connected PIA server. See Gluetun issue #3070 for details.
Set PIA_PORT_FORWARDING=true and DOCKER_COMPOSE_HOST_DIR to enable fully automatic management:
- Only connects to PIA servers that support port forwarding (via
-pflag) - Monitors port forwarding status via Gluetun's control server API
- Automatically syncs
SERVER_NAMESand recreates Gluetun when needed - Updates your
.envfile for persistence (so futuredocker composecommands use the correct value) - Auto-detects the docker compose project name from container labels (no manual configuration needed)
Your .env file should contain:
PIA_USERNAME=your_user
PIA_PASSWORD=your_pass
PIA_REGION=us_chicago
SERVER_NAMES=chicago412
If DOCKER_COMPOSE_HOST_DIR is not set, the container will:
- Log the server name on each config generation
- Include the server name in the
wg0.confheader for reference - Warn when
SERVER_NAMESneeds to be updated manually
Generated config header example:
# Generated by pia-wg-refresh
# Date: 2025-01-11T12:00:00Z
# Region: au_melbourne
# Server: melbourne412 (use this for SERVER_NAMES if port forwarding)
You can configure scripts to run when failures occur or when the tunnel recovers. Scripts are executed asynchronously (non-blocking) so they don't delay the main monitoring loop.
Script paths can include arguments (e.g., /scripts/notify.sh failure).
Set ON_FAILURE_SCRIPT to a script path (with optional arguments). The script runs when the failure threshold is reached.
Environment variables passed to the script:
FAILURE_TYPE- eitherconnectivityorport_forwarding
Set ON_RECOVERY_SCRIPT to a script path. The script runs when the tunnel recovers after a failure.
Environment variables passed to the script:
PIA_SERVER_NAME- the connected PIA server name (e.g.,dublin423)PIA_FORWARDED_PORT- the forwarded port number (if port forwarding is enabled)
Set ON_PORT_CHANGE_SCRIPT to a script path. The script runs whenever the forwarded port changes, including on first discovery after startup.
Environment variables passed to the script:
PIA_FORWARDED_PORT- the new forwarded port numberPIA_PREVIOUS_PORT- the previous port number (empty string on first discovery)PIA_SERVER_NAME- the connected PIA server name
Using separate scripts:
pia-wg-refresh:
environment:
- ON_FAILURE_SCRIPT=/scripts/notify-failure.sh
- ON_RECOVERY_SCRIPT=/scripts/notify-recovery.sh
- ON_PORT_CHANGE_SCRIPT=/scripts/notify-ports.sh
volumes:
- ./scripts:/scripts:roUsing a single script with arguments:
pia-wg-refresh:
environment:
- ON_FAILURE_SCRIPT=/scripts/notify.sh failure
- ON_RECOVERY_SCRIPT=/scripts/notify.sh recover
- ON_PORT_CHANGE_SCRIPT=/scripts/notify.sh ports
volumes:
- ./scripts:/scripts:roExample notify.sh:
#!/bin/sh
ACTION="$1"
if [ "$ACTION" = "failure" ]; then
curl -X POST "https://your-webhook.com/notify" -d "VPN failure: $FAILURE_TYPE"
elif [ "$ACTION" = "recover" ]; then
curl -X POST "https://your-webhook.com/notify" \
-d "VPN recovered on server $PIA_SERVER_NAME with port $PIA_FORWARDED_PORT"
elif [ "$ACTION" = "ports" ]; then
curl -X POST "https://your-webhook.com/notify" \
-d "Port changed: $PIA_PREVIOUS_PORT -> $PIA_FORWARDED_PORT (server: $PIA_SERVER_NAME)"
fiA working Prowl notification example covering all three hook types is available in scripts/pia-wg-refresh-notification.sh.
Important: Hook scripts must use #!/bin/sh as the shebang. The container uses Alpine Linux which doesn't include bash. If your script requires bash-specific features, you'll need to modify the Dockerfile to install bash.
Hook output is logged to /logs/hooks.log.
Logs are append-only in /logs:
/logs/refresh.log- main refresh loop logs (plain text)/logs/pia-wg-config.log- output from config generation/logs/docker.log- output from docker restart/compose commands/logs/hooks.log- output from hook scripts
Console output (docker logs) includes colored log levels for easier reading:
[debug]- cyan[info]- green[warn]- yellow[error]- red
Log files are kept as plain text without color codes.
Basic setup:
services:
pia-wg-refresh:
image: ghcr.io/ccarpinteri/pia-wg-refresh:latest
container_name: pia-wg-refresh
environment:
- PIA_USERNAME=${PIA_USERNAME}
- PIA_PASSWORD=${PIA_PASSWORD}
- PIA_REGION=${PIA_REGION}
- GLUETUN_CONTAINER=gluetun
volumes:
- ./gluetun/config/wireguard:/config
- ./logs:/logs
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
healthcheck:
test: grep -q "^Endpoint" /config/wg0.conf || exit 1
start_period: 10s
interval: 5s
gluetun:
# Pin to a specific version - gluetun:latest tracks HEAD and can ship regressions.
# Check https://github.com/qdm12/gluetun/releases for the latest stable release.
image: qmcgaw/gluetun:v3.41.1
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ./gluetun/config:/gluetun
environment:
- VPN_SERVICE_PROVIDER=custom
- VPN_TYPE=wireguard
restart: unless-stoppedWith automatic port forwarding:
services:
pia-wg-refresh:
image: ghcr.io/ccarpinteri/pia-wg-refresh:latest
container_name: pia-wg-refresh
environment:
- PIA_USERNAME=${PIA_USERNAME}
- PIA_PASSWORD=${PIA_PASSWORD}
- PIA_REGION=${PIA_REGION}
- PIA_PORT_FORWARDING=true
- DOCKER_COMPOSE_HOST_DIR=/path/to/your/compose/directory
- GLUETUN_CONTAINER=gluetun
volumes:
- ./gluetun/config/wireguard:/config
- ./logs:/logs
- /var/run/docker.sock:/var/run/docker.sock
- /path/to/your/compose/directory:/path/to/your/compose/directory
restart: unless-stopped
healthcheck:
test: grep -q "^Endpoint" /config/wg0.conf || exit 1
start_period: 10s
interval: 5s
gluetun:
# Pin to a specific version - gluetun:latest tracks HEAD and can ship regressions.
# Check https://github.com/qdm12/gluetun/releases for the latest stable release.
image: qmcgaw/gluetun:v3.41.1
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ./gluetun/config:/gluetun
environment:
- VPN_SERVICE_PROVIDER=custom
- VPN_TYPE=wireguard
- VPN_PORT_FORWARDING=on
- VPN_PORT_FORWARDING_PROVIDER=private internet access
- VPN_PORT_FORWARDING_USERNAME=${PIA_USERNAME}
- VPN_PORT_FORWARDING_PASSWORD=${PIA_PASSWORD}
- SERVER_NAMES=${SERVER_NAMES}
restart: unless-stoppedReplace /path/to/your/compose/directory with the absolute path where your docker-compose.yml and .env files are located (e.g., /home/user/docker/gluetun).
Gluetun v3.39.1 and later make all control server API routes private by default. pia-wg-refresh uses two endpoints (/v1/publicip/ip and /v1/portforward) to check VPN and port forwarding status. Without configuration these return 401 and pia-wg-refresh will incorrectly treat the VPN as down.
To fix this, create auth/config.toml inside the directory you mount to /gluetun in the container (e.g. ./gluetun/config/auth/config.toml):
[[roles]]
name = "pia-wg-refresh"
routes = ["GET /v1/publicip/ip", "GET /v1/portforward"]
auth = "none"This opens only the two read-only endpoints pia-wg-refresh needs. All other routes — including /v1/vpn/settings which exposes VPN credentials — remain protected.
If you are running an older version of Gluetun (pre-v3.39.1), no action is needed.
qmcgaw/gluetun:latest tracks the development branch and can ship regressions before they hit a stable release. It is strongly recommended to pin to a specific version tag (e.g. qmcgaw/gluetun:v3.41.1) and update deliberately. Check the Gluetun releases page for the latest stable release. If you use an auto-update tool (e.g. Watchtower, Synology Container Manager), exclude gluetun from automatic updates.
This container requires the Docker socket and can restart other containers. It is intended for trusted, single-host setups.
- The bundled generator writes
wg0.confinside/config. This container validates the new config before replacing the existing one. - VPN status is checked via Gluetun's control server API at
localhost:8000, which responds instantly regardless of VPN state. - Port forwarding status is monitored via the
/v1/portforwardendpoint whenPIA_PORT_FORWARDING=true. - The bundled
pia-wg-configbinary is built from ccarpinteri/pia-wg-config (a fork of Ephemeral-Dust/pia-wg-config). The fork fixes token generation failures on PIA's newer servers (Server-XXXXX-Xanaming format) by using PIA's central token API instead of the regional meta server endpoint.
Run make test to build the image and verify both the bundled and download paths without requiring Gluetun.
Run make test-bundled or make test-download to exercise a single path.
PIA_WG_CONFIG_REF(default:main) sets the git ref used to buildpia-wg-configin the Dockerfile.
MIT