- LAN, private and CGNAT networks remain accessible and are not routed over VPN. No special configuration required.
- Supports systemd integration when running via podman
- Supports roaming clients
Warning
gVisor and cgroup v1 are NOT supported!
Images are published at ghcr.io/tprasadtp/protonwire.
Important
If running as a container, Wireguard MUST be installed on the host, not the container.
- If using Debian 11 (Buster) or later, Raspberry Pi OS (Buster) or later, Fedora, ArchLinux, Linux Mint 20.x or later, RHEL 9 or later, Alma Linux 9 or later, CentOS 9 Stream, Ubuntu 20.04 or later have the required kernel module built-in.
- Kernel versions 5.6 or later.
- If NONE of the above conditions can be satisfied, install WireGuard. Your distribution might already package DKMS module or provide signed kernels with WireGuard built-in. Visit https://www.wireguard.com/install/ for more info.
- To check current kernel version run,
uname -r
Important
It is recommended to use unique private key for each instance of the the VPN container.
- Log in to ProtonVPN and go to Downloads → WireGuard configuration.
- Enter a name for the key, and select features to enable like NetShield and VPN Accelerator & click create.
Some users have reported issues (#236,#211) when NetShield is set to
Block malware, ads and trackers
. Please see Troubleshooting for a work-around. - Generated config might look something like below,
[Interface] # Key for <name> # VPN Accelerator = on PrivateKey = KLjfIMiuxPskM4+DaSUDmL2uSIYKJ9Wap+CHvs0Lfkw= Address = 10.2.0.2/32 DNS = 10.2.0.1 [Peer] # NL-FREE#128 PublicKey = jbTC1lYeHxiz1LNSJHQMKDTq6sHgcWxkBwXvt7GWo1E= AllowedIPs = 0.0.0.0/0 Endpoint = 91.229.23.180:51820
- You will
PrivateKey
and optionallyEndpoint
(without port part) from the above config. - See https://protonvpn.com/support/wireguard-configurations/ for more info.
- CLI arguments will always take precedence over environment variables.
- Environment variables takes precedence over any config file.
- If private key is not specified via CLI or environment variable, it is searched
in following locations.
/etc/protonwire/private-key
/run/secrets/protonwire-private-key
/run/secrets/protonwire/private-key
${CREDENTIALS_DIRECTORY}/private-key
(Only if$CREDENTIALS_DIRECTORY
is set)${CREDENTIALS_DIRECTORY}/protonwire-private-key
(Only if$CREDENTIALS_DIRECTORY
is set)
Important
Private key file MUST NOT be world-readable.
Name | Default/Required | Description |
---|---|---|
PROTONVPN_SERVER |
REQUIRED | (String) ProtonVPN server to connect to. |
WIREGUARD_PRIVATE_KEY |
Required if not specified via mount or secrets | (String) Wireguard Private key |
IPCHECK_URL |
https://protonwire-api.vercel.app/v1/client/ip | (String) URL to check client IP. |
IPCHECK_INTERVAL |
60 |
(Integer) Interval between internal health-checks in seconds. Set this to 0 to disable IP checks. |
SKIP_DNS_CONFIG |
false | (Boolean) Set this to 1 or true to skip configuring DNS. |
KILL_SWITCH |
false | (Boolean) Enable KillSwitch (Experimental) |
This should be server DNS name like, node-nl-01.protonvpn.net
or IP address like
91.229.23.180
. Server name like NL#1
(or NL-1
) may work for pro servers, it is
not recommended.
Important
Script cannot validate if specified server is available under your plan.
It is user's responsibility to ensure that server specified is available
under your subscription and supports required features, like P2P, Streaming etc.
Use --p2p
, --streaming
, --secure-core
flags to enable client side validations.
Warning
This feature is experimental and is NOT covered by semver compatibility guarantees.
Kill-Switch is not a hard kill-switch but more of an internet kill-switch. LAN addresses, Link-Local addresses and CGNAT (also Tailscale) addresses remain reachable. Unlike most VPN containers, kill-switch is implemented via routing policies, routing priorities and custom route tables rather than firewall rules.
- Kill-switch WILL NOT be disabled during reconnects.
- Kill-switch WILL NOT be disabled when running
protonwire disconnect
unless--kill-switch
flag is ALSO specified.
ProtonVPN WireGuard Client Usage: protonwire [OPTIONS...] or: protonwire [OPTIONS...] c|connect [SERVER] or: protonwire [OPTIONS...] d|disconnect or: protonwire [OPTIONS...] check or: protonwire [OPTIONS...] disable-killswitch or: protonwire [OPTIONS...] server-info [SERVER] Options: -k, --private-key FILE|KEY Wireguard private key or file containing private key --service Run as service --service-status-file Use status file created by --service for healthchecks. Only valid when both process are running within the same container. --metadata-url URL Server metadata endpoint URL --check-interval INT IP check interval in seconds (default 60) --check-url URL IP check endpoint URL --skip-dns-config Skip configuring DNS. (Useful for Kubernetes and Consul) --kill-switch Enable killswitch (Experimental) --p2p Verify if specified server supports P2P --streaming Verify if specified server supports streaming --tor Verify if specified server supports Tor --secure-core Verify if specified server supports secure core -q, --quiet Show only errors -v, --verbose Show debug logs -h, --help Display this help and exit --version Display version and exit Examples: protonwire connect nl-1 Connect to server nl-1 protonwire d --kill-switch Disconnect from current server and disable kill-switch protonwire verify [SERVER] Check if connected to a server Files: /etc/protonwire/private-key WireGuard private key Environment: WIREGUARD_PRIVATE_KEY WireGuard private key or file PROTONVPN_SERVER ProtonVPN server IPCHECK_INTERVAL Custom IP check interval in seconds (default 60) IPCHECK_URL IP check endpoint URL (must be https://) SKIP_DNS_CONFIG Set to '1' to skip configuring DNS KILL_SWITCH Set to '1' to enable killswitch (Experimental) DEBUG Set to '1' to enable debug logs
- Script supports
healthcheck
sub-command. By default, when running as a service, script will keep checking everyIPCHECK_INTERVAL
(default=60) seconds using theIPCHECK_URL
api endpoint. To disable healthchecks entirely setIPCHECK_INTERVAL
to0
- Use
protonwire healthcheck --silent --service-status-file
as theHEALTHCHECK
command. Same can be used as liveness probe and readiness probe for Kubernetes.
If entire stack is in a single compose file, then network_mode: service:protonwire
on the services which should be routed via VPN. If the VPN stack is NOT in same
compose file use network_mode: container:<protonwire-container-name>
.
As an example, run caddy web-server, proxying https://ip.me, via VPN using the compose
config given below. Once the stack is up, visiting the http://localhost:8000, or
curl -s http://localhost:8000
should show VPN's country and IP address.
version: '2.3'
services:
protonwire:
container_name: protonwire
# Use semver tags or sha256 hashes of manifests.
# using latest tag can lead to issues when used with
# automatic image updaters like watchtower/podman.
image: ghcr.io/tprasadtp/protonwire:latest
init: true
restart: unless-stopped
environment:
# Quote this value as server name can contain '#'.
PROTONVPN_SERVER: "node-nl-96.protonvpn.net" # NL-FREE#100070
# Set this to 1 to show debug logs for issue forms.
DEBUG: "0"
# Set this to 0 to disable kill-switch.
KILL_SWITCH: "1"
# NET_ADMIN capability is mandatory!
cap_add:
- NET_ADMIN
# sysctl net.ipv4.conf.all.rp_filter is mandatory!
# net.ipv6.conf.all.disable_ipv6 disables IPv6 as protonVPN does not support IPv6.
# 'net.*' sysctls are not required on application containers,
# as they share network stack with protonwire container.
sysctls:
net.ipv4.conf.all.rp_filter: 2
net.ipv6.conf.all.disable_ipv6: 1
volumes:
- type: tmpfs
target: /tmp
- type: bind
source: private.key
target: /etc/protonwire/private-key
read_only: true
ports:
- 8000:80
# This is sample application which will be routed over VPN
# Replace this with your preferred application(s).
caddy_proxy:
image: caddy:latest
network_mode: service:protonwire
command: |
caddy reverse-proxy \
--change-host-header \
--from :80 \
--to https://ip.me:443
Important
- It is essential to expose/publish port(s) on protonwire container, instead of application container.
- SHOULD NOT run the container as privileged. Adding capability
CAP_NET_ADMIN
AND definedsysctls
should be sufficient. - Value for
PROTONVPN_SERVER
must be enclosed within quotes as server name can contain '#'
This section covers running containers via podman. But for deployments use podman's systemd integration.
-
Create a podman secret for private key
podman secret create protonwire-private-key <PRIVATE_KEY|PATH_TO_PRIVATE_KEY>
-
Run protonwire container.
podman run \ -it \ --rm \ --init \ --replace \ --tz=local \ --tmpfs=/tmp \ --name=protonwire \ --secret="protonwire-private-key,mode=600" \ --env=PROTONVPN_SERVER="node-nl-03.protonvpn.net" \ --env=DEBUG=0 \ --env=KILL_SWITCH=1 \ --cap-add=NET_ADMIN \ --sysctl=net.ipv4.conf.all.rp_filter=2 \ --sysctl=net.ipv6.conf.all.disable_ipv6=1 \ --publish=8000:8000 \ --health-start-period=20s \ --health-cmd="protonwire check --service-status-file --silent" \ --health-interval=120s \ --health-on-failure=stop \ ghcr.io/tprasadtp/protonwire:latest
-
Create app(s) sharing network namespace with
protonwire
container. As an example, we are using caddy to proxy website which shows IP info. Replace these with your application container(s) like pyload, firefox etc.podman run \ -it \ --rm \ --tz=local \ --name=protonwire-demo-app \ --network=container:protonwire \ docker.io/library/caddy:latest \ caddy reverse-proxy --change-host-header --from :8000 --to https://ip.me:443
-
Verify that application containers are using VPN by visiting http://:8000.
Important
- The above example publishes container port 8000 to host port 8000. You MUST change these to match your application container(s).
- To publish additional ports from other containers using this VPN
(usually done via argument
--publish <host-port>:<container-port>
), it MUST be done on protonwire container. --sysctl
flags are important! without these, container cannot create/manage WireGuard interface.mode=600
in secret mount is important, as script refuses to use private key with insecure permissions.- If using pods, sysctls MUST be defined on the pod no the protonwire container.
- Pull docker image (if required)
docker pull ghcr.io/tprasadtp/protonwire:latest
- Run the VPN container. Assuming that a container which needs to be routed via VPN, listening on container port
80
and you wish to map it to host port8000
,docker run \ -it \ --rm \ --init \ --publish 8000:80 \ --name protonwire \ --cap-add NET_ADMIN \ --env PROTONVPN_SERVER=<server-name-or-dns> \ --sysctl net.ipv4.conf.all.rp_filter=2 \ --mount type=tmpfs,dst=/tmp \ --mount type=bind,src=<absolute-path-to-key-file>,dst=/etc/protonwire/private-key,readonly \ ghcr.io/tprasadtp/protonwire:latest
Important
- To publish additional ports from other containers using this VPN, it MUST be done
on the
protonwire
container! --sysctl
and--cap-add
flags are important! without these, container cannot create or manage WireGuard interfaces or routing.- docker rootless should also work just fine for most users, but is considered experimental.
-
To use VPN in other container(s), use
--net=container:protonwire
flag. For example, we can run caddy to proxyhttps://ip.me/
via VPN. Visiting http://localhost:8000, orcurl http://localhost:8000
should show VPN's country and IP address.docker run \ -it \ --rm \ --net=container:protonwire \ caddy:latest \ caddy reverse-proxy \ --change-host-header \ --from :80 \ --to https://ip.me:443
See Troubleshooting and FAQ
All artifacts provided by this repository meet SLSA L3. See docs for more info.
All artifacts provided by this repository are signed using cosign. See docs for more info.
Building requires task
,
go
crane
and docker
with buildx
plugin.