[!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.
uname -r
[!IMPORTANT]
It is recommended to use unique private key for each instance of the the VPN container.
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
PrivateKey
and optionally Endpoint
(without port part) from the above config./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://icanhazip.com/ | (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.
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
healthcheck
sub-command. By default, when running as a service,
script will keep checking every IPCHECK_INTERVAL
(default=60) seconds using the
IPCHECK_URL
api endpoint. To disable healthchecks entirely set IPCHECK_INTERVAL
to 0
protonwire healthcheck --silent --service-status-file
as the HEALTHCHECK
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://
[!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.
docker pull ghcr.io/tprasadtp/protonwire:latest
80
and you wish to map it to host port 8000
,
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 proxy https://ip.me/
via VPN. Visiting http://localhost:8000, or curl 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.