Two independent VPN tunnels: WireGuard + ProtonVPN via Shadowsocks
We’ll add a ProtonVPN container next to an existing WireGuard container on the same VPS, reachable from the PC via a Shadowsocks proxy, I always try to make easily-reversible changes. .
The goal
- Keep the existing WireGuard container untouched.
- Add a second, independent tunnel: PC → Shadowsocks → ProtonVPN container → internet.
- Each container manages its own networking. No shared routes, no host-level iptables changes.
- Pick either tunnel per-client, per-app, or run both at once.
Server side
The existing wireguard service stays as it is (network_mode: host, its own subnet and peers). We’ll add a gluetun container running the ProtonVPN client with Shadowsocks enabled.
Add a protonvpn service to docker-compose.yml, alongside the existing wireguard service:
protonvpn:
image: qmcgaw/gluetun:latest
container_name: protonvpn
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- OPENVPN_USER=${PROTONVPN_USERNAME}
- OPENVPN_PASSWORD=${PROTONVPN_PASSWORD}
- SERVER_COUNTRIES=Netherlands
- SHADOWSOCKS=on
- SHADOWSOCKS_PASSWORD=${SHADOWSOCKS_PASSWORD}
- SHADOWSOCKS_METHOD=chacha20-ietf-poly1305
ports:
- "8388:8388/tcp"
- "8388:8388/udp"
volumes:
- ./protonvpn:/gluetun
restart: unless-stopped
gluetun handles the OpenVPN connection to ProtonVPN internally and runs the Shadowsocks server inside its own container network namespace, separate from the host’s routing table.
Create a .env file next to docker-compose.yml with PROTONVPN_USERNAME, PROTONVPN_PASSWORD, and SHADOWSOCKS_PASSWORD. Use your ProtonVPN OpenVPN credentials here, not your account login (get them from https://account.protonvpn.com/account#openvpn ).
SHADOWSOCKS_PASSWORD=<some-password>
PROTONVPN_USERNAME=<openvpn-username-not-protonvpn-account-name>
PROTONVPN_PASSWORD=<openvpn-password-not-your-account-password>
Bring the stack up:
docker-compose up -d
docker logs protonvpn
Confirm the exit IP is ProtonVPN’s, not the VPS’s own:
docker exec protonvpn wget -qO- ifconfig.me
Client side
Clash (or one of its GUI wrappers, like Clash Verge) reads a single YAML config and exposes both an HTTP and a SOCKS5 proxy.
Save the following as config.yaml:
port: 7890
socks-port: 7891
allow-lan: false
mode: Rule
proxies:
- name: "ProtonVPN-SS"
type: ss
server: your-vps-host.example.com
port: 8388
cipher: chacha20-ietf-poly1305
password: "your_shadowsocks_password"
udp: true
proxy-groups:
- name: "PROXY"
type: select
proxies:
- ProtonVPN-SS
- DIRECT
rules:
- MATCH,PROXY
Run clash from the directory containing config.yaml:
clash
# INFO Start initial compatible provider PROXY
# INFO HTTP proxy listening at: 127.0.0.1:7890
# INFO SOCKS proxy listening at: 127.0.0.1:7891
Clash reads the YAML directly and runs as a regular foreground process.
Wiring into GNOME
With Clash running:
- Open
Settings→Network→Network Proxy. - Select
Manual. - Under
SOCKS Host, enter127.0.0.1and port7891. - Click
Apply.
Or the equivalent from the command line:
gsettings set org.gnome.system.proxy mode 'manual'
gsettings set org.gnome.system.proxy.socks host '127.0.0.1'
gsettings set org.gnome.system.proxy.socks port 7891
Most GTK apps, and anything that respects the system proxy (including a lot of CLI tools via all_proxy), will route through ProtonVPN. Verify with:
curl --socks5 127.0.0.1:7891 ifconfig.me
Firefox exception
Firefox does not honor org.gnome.system.proxy, even when set to Use system proxy settings. This isn’t specific to this setup, as far as I know, it’s been reported against Firefox on Linux for well over a decade. It’s never really been fixed, just worked around
I keep the manual SOCKS5 setting in Firefox’s own network settings. It’s two clicks to flip between “Manual” and “No proxy”, which is fine for how often I actually switch it. I tried FoxyProxy. Worth installing only if you want per-site rules or a toolbar toggle. I also tried the MOZ_GTK_USE_PORTAL=1 environment variable some people suggest, and it was inconsistent in my testing, so I couldn’t rely on it.
.
A one-line PAC file gets you the easy Manual/No proxy toggle without opening the manual-config dialog each time, if you’d rather point Firefox at a file you control (for instance, ~/.config/proxy.pac):
function FindProxyForURL(url, host) { return "SOCKS5 127.0.0.1:7891"; }
Then: Settings → Network Settings → Automatic proxy configuration URL → file://${HOME}/.config/proxy.pac.