Blueprint: Migrating Gluetun & PIA from OpenVPN to WireGuard

Blueprint: Migrating Gluetun & PIA from OpenVPN to WireGuard
Photo by Privecstasy / Unsplash

A technical reference, trade-off analysis, and step-by-step implementation guide for running Private Internet Access (PIA) with WireGuard and dynamic port forwarding on Unraid via Gluetun.


1. Context & Motivation

Current Benchmark Baseline (OpenVPN)

  • Host Direct Speed: ~1.35 Gbps (~169 MB/s on Gigabit+ Fiber ISP)
  • Current Gluetun OpenVPN Tunnel: ~400 Mbps (~48.5 MB/s sustained)
  • Current Status: 100% stable, fully automated port forwarding (port 23096), zero maintenance.

Why Switch to WireGuard?

  • Throughput Potential: Increases tunnel speed from ~400 Mbps to 700 Mbps - 1+ Gbps (~90-120+ MB/s).
  • Efficiency: Lower CPU utilization, zero userspace-to-kernel context switching overhead, and modern ChaCha20-Poly1305 encryption.
  • When to Do This: If you routinely download massive 4K remuxes (50-100 GB) and want to saturate gigabit download speeds.

2. The Core Challenge: Why PIA is Unique

Unlike VPN providers like Mullvad, AirVPN, or Proton that provide static WireGuard private keys and configuration files in their user dashboards, Private Internet Access does not support static WireGuard keys.

Instead, PIA uses a dynamic, ephemeral REST API handshake:

sequenceDiagram
    participant Host as Unraid / Helper Script
    participant PIAToken as PIA Token API (HTTPS)
    participant PIAGateway as PIA Montreal Gateway (:1337)
    participant Gluetun as Gluetun Container

    Host->>PIAToken: POST /api/client/v2/token (User + Pass)
    PIAToken-->>Host: Ephemeral Auth Token
    Host->>Host: Generate Local WG Keypair (wg genkey)
    Host->>PIAGateway: POST /addKey?pt=<token>&pubkey=<pubkey>
    PIAGateway-->>Host: Assigned IP, Server Pubkey, Gateway IP
    Host->>Gluetun: Write wg0.conf into appdata
    Gluetun->>PIAGateway: Establish WireGuard Handshake (UDP)

Why Gluetun Throws an Error Out-of-the-Box

When you set VPN_SERVICE_PROVIDER="private internet access" and VPN_TYPE="wireguard", Gluetun returns:

VPN provider name is not valid for wireguard: private internet access must be one of airvpn, custom, ivpn, mullvad, protonvpn...

Because Gluetun is strictly designed to isolate and killswitch all network traffic before a tunnel is up, it refuses to perform un-tunneled dynamic HTTP API registrations on boot. Therefore, PIA + WireGuard must be configured as a custom provider using a pre-generated configuration.


3. Architecture Blueprint: The Helper Pattern

To run WireGuard with PIA on Gluetun, you use a helper mechanism to generate and maintain the ephemeral WireGuard configuration and handle port forwarding renewals.

flowchart TD
    subgraph Host["Unraid Server (10.0.0.x)"]
        Helper["PIA WG Helper<br/>(Docker Container or Script)"]
        AppData["/mnt/user/appdata/gluetun/wireguard/wg0.conf"]
        Gluetun["Gluetun Container<br/>(VPN_SERVICE_PROVIDER=custom)<br/>(VPN_TYPE=wireguard)"]
        qBit["qBittorrent Container<br/>(--net=container:gluetun)"]
    end
    PIAAPI["PIA Token & Gateway API"]
    VPNServer["PIA Montreal WireGuard Server"]

    Helper -->|1. Authenticate & Register Key| PIAAPI
    Helper -->|2. Writes Config| AppData
    AppData -->|3. Mounted Into| Gluetun
    Gluetun -->|4. WireGuard Tunnel| VPNServer
    qBit --> Gluetun

4. Step-by-Step Implementation Guide

When you are ready to make the switch, follow these steps:

Step 1: Deploy the PIA WireGuard Config Helper

You can use the community-standard kylegrantlucas/pia-wg-config or thrn/pia-wg container to generate and refresh the config file.

Create an Unraid User Script or run the helper container:

docker run --rm \
  -e USER="pXXXXXXX" \
  -e PASSWORD="your_secure_password" \
  -e REGION="ca_montreal" \
  -v /mnt/user/appdata/gluetun/wireguard:/pia \
  thrn/pia-wg:latest

This generates:

  • /mnt/user/appdata/gluetun/wireguard/wg0.conf
  • Extracts the assigned local IP (e.g., 10.x.x.x), private key, and Montreal gateway endpoint.

Step 2: Update the Gluetun Unraid Template (my-gluetun.xml)

Switch Gluetun's configuration to use the custom WireGuard configuration:

  1. Change Provider & Type:
    • VPN_SERVICE_PROVIDER = custom
    • VPN_TYPE = wireguard
  2. Mount the Config File:
    • Host Path: /mnt/user/appdata/gluetun/wireguard/wg0.conf
    • Container Path: /gluetun/wireguard/wg0.conf (Mode: ro)
  3. Alternative: Environment Variable Passthrough (No File Mount):
    If you extract the variables directly from wg0.conf, you can set:
    • WIREGUARD_PRIVATE_KEY = <private_key_string>
    • WIREGUARD_ADDRESSES = <assigned_internal_ip>/32
    • WIREGUARD_ENDPOINT_IP = <montreal_ip>
    • WIREGUARD_ENDPOINT_PORT = 1337
    • WIREGUARD_PUBLIC_KEY = <pia_server_public_key>

Step 3: WireGuard Dynamic Port Forwarding

On WireGuard, PIA requires refreshing the port forward signature every 15 minutes by communicating with their internal gateway IP:

# 1. Fetch Signature and Port
curl -s -G --data-urlencode "token=$TOKEN" \
  "http://10.0.222.1:1337/getSignature" > /tmp/pia_signature.json

PORT=$(jq -r '.port' /tmp/pia_signature.json)
SIGNATURE=$(jq -r '.signature' /tmp/pia_signature.json)
PAYLOAD=$(jq -r '.payload' /tmp/pia_signature.json)

# 2. Bind Port (Keepalive Loop every 15 minutes)
curl -s -G --data-urlencode "payload=$PAYLOAD" \
  --data-urlencode "signature=$SIGNATURE" \
  "http://10.0.222.1:1337/bindPort"

Note: In Gluetun, if you provide VPN_PORT_FORWARDING=on, VPN_PORT_FORWARDING_PROVIDER=private internet access, and valid credentials, Gluetun will attempt this bind loop automatically if SERVER_NAME matches the gateway region.


Step 4: Rebuild Containers

/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/rebuild_container gluetun
/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/rebuild_container qbittorrent

5. Instant Rollback Plan (Back to OpenVPN)

If the WireGuard configuration ever fails or PIA's token exchange acts up, reverting to the proven OpenVPN setup takes under 30 seconds:

  1. In Unraid WebGUI $\rightarrow$ Docker $\rightarrow$ Edit gluetun:
    • Set VPN_SERVICE_PROVIDER = custom
    • Set VPN_TYPE = openvpn
    • Set OPENVPN_CUSTOM_CONFIG = /gluetun/custom.ovpn
    • Click Apply.
  2. Tunnel immediately restores to PIA Montreal with OpenVPN on port 23096.

Restart qbittorrent:

docker restart qbittorrent

6. Summary Comparison

Dimension OpenVPN (Active Today) WireGuard (This Blueprint)
Throughput ~400 Mbps (~48 MB/s) ~700-1000 Mbps (~90-120 MB/s)
Configuration Single XML template Helper script + Custom WireGuard config
Port Forwarding Fully automated by Gluetun Requires API signature keepalive loop
Reliability 100% resilient across reboots Requires fresh token if key expires
Recommendation Best for 24/7 "set and forget" Best for maximum raw speed
Fahim

Fahim

Engineer, husband, dad, and photography enthusiast documenting technology, creativity, and the occasional rabbit hole.
Ottawa, ON