On this page
Install a VPN on a desktop and the GUI makes sense: tray icon, server list, one click to connect. Put that same logic on a VPS with no display server and it stops making sense entirely.
Most Linux VPN guides skip this distinction. They walk through apt install, show a green “connected” screen, and move on. That works fine for a laptop. It does nothing for a machine that reboots unattended, runs cron jobs at 3 a.m., or sits behind a firewall with no monitor ever plugged into it. Proton VPN's Linux CLI exists specifically for that second category, and almost nobody explains how to actually use it that way.
Why a GUI client is the wrong tool for most VPS setups
A graphical VPN client assumes a session: someone logged in, watching a status bar, ready to click reconnect if the tunnel drops. A server has none of that. It needs the tunnel to come up before any other service starts, stay up without supervision, and reconnect on its own if the network blips at 4 a.m. while nobody's watching.
That's a process management problem, not a UI problem. Which is exactly what the CLI is built to solve, and what a desktop app was never designed for.
What the CLI gives you that the desktop client doesn't
The difference isn't just “no window.” The CLI exposes connection state as exit codes and log lines instead of a visual indicator, which means it can be checked, parsed, and acted on by other scripts. A provisioning script can call protonvpn-cli connect –fastest, check the return code, and only proceed with the next step (pulling a config from a private repo, say, or hitting an internal API) once the tunnel is actually confirmed up. Try scripting around a system tray icon and there's simply nothing to hook into.
Whether that kind of trust holds up is worth checking against independent testing rather than the vendor's own claims. https://gizmodo.com/best-vpn/proton-vpn covers Proton VPN's audit history and its Secure Core architecture in more detail, alongside where it still lags for reasons that have nothing to do with server use, like streaming support and the absence of Smart DNS.
Wiring the CLI into systemd so it survives reboots
Running protonvpn-cli connect manually after every reboot defeats the purpose. A minimal systemd unit fixes that:
[Unit]
Description=Proton VPN Connection
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/protonvpn-cli connect --fastest
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
Enable it with systemctl enable protonvpn and the tunnel comes up automatically on boot, before anything that depends on it. systemctl status protonvpn then becomes the single command that answers “is the tunnel actually up,” which matters more on a server than it ever did on a laptop.
One wrinkle: credential handling. Storing an OpenVPN config with embedded credentials works for a quick test but shouldn't sit in a world-readable file on a production box. Restricting permissions to the service account (chmod 600, owned by whichever user runs the unit) is the minimum, and pulling credentials from an environment file outside version control is better still.
Where this actually gets used
Two patterns come up repeatedly among people running this setup in production.
The first is routing outbound traffic from scraping or monitoring jobs through a stable, non-residential IP range, which matters when the target service rate-limits by ASN rather than by request volume. The second, more relevant to anyone running a control panel like CyberPanel on a VPS, is gating admin access: instead of exposing port 8090 (or phpMyAdmin, or a Redis admin UI) to the open internet and relying on firewall rules alone, the panel only accepts connections that arrive through the tunnel. That doesn't replace SSH key auth or fail2ban. It sits alongside them, closing off exposure that a firewall rule alone tends to leave gaps in.
Neither pattern needs a GUI at any point. Both need the connection to be scriptable and reliable across reboots, which is the entire argument for the CLI in the first place.
Where Proton Plus stops being optional
The free tier caps out fast in this context. Free-tier servers are shared and rate-limited, which is tolerable for occasional browsing and a real bottleneck for a server pushing continuous scraping or backup traffic. Proton Plus removes the server cap and adds Secure Core routing, relevant for anyone running jobs where traffic correlation is a genuine concern rather than a theoretical one. For a one-off personal VPN, the free tier is fine. For infrastructure running unattended, the free tier's shared-server model becomes the actual limiting factor before anything else does.
The rough edges nobody mentions
The CLI doesn't auto-update the way the desktop app does, so version drift after a distro upgrade is a real failure mode worth checking for rather than assuming away. Killswitch behavior also differs from the GUI implementation and can interact awkwardly with existing iptables rules on a box that already runs its own firewall configuration, so testing the interaction before relying on it in production is worth the twenty minutes it takes. And DNS resolution inside a container namespace doesn't always inherit the host's VPN routing automatically, which trips up anyone running this alongside Docker without first checking how the two interact.
None of these are dealbreakers. They're just the parts that don't show up in a five-minute installation walkthrough, and they're exactly what separates “it connected once” from “it's been running for six months without anyone noticing.”
Checking it's actually working, not just running
A systemd unit reporting "active" only confirms the service started. It says nothing about whether traffic is actually routing through the tunnel. That gap causes more silent failures than the connection dropping outright.
curl --interface tun0 https://api.ipify.org
Run that and compare the result against the server's real public IP. If they match, the tunnel is up but traffic isn't using it, usually a routing table issue rather than a VPN problem. journalctl -u protonvpn –since "1 hour ago" surfaces reconnection attempts that never show up in systemctl status, which only reports the current state, not the history behind it.
A short watchdog script closes the remaining gap: a cron job every five minutes checking the interface, and attempting a reconnect if it's missing.
*/5 * * * * ip link show tun0 || protonvpn-cli connect --fastest
It won't catch every failure mode. It catches the common one, which is the tunnel silently dropping and nothing else on the box noticing until something downstream fails first.