Dacă ai încercat vreodată să instalezi un VPN pe un server Linux fără interfață grafică și ai urmat un tutorial care îți arăta o fereastră cu buton de connect, știi deja că ceva nu se potrivește. Aplicațiile GUI pentru VPN sunt construite cu un utilizator în față: cineva care se uită la o iconiță în system tray, care dă click pe reconect când tunelul cade, care e prezent. Un VPS nu are nimic din astea. Pornește singur, rulează nesupravegheat, și dacă VPN-ul cade la 3 noaptea, nimeni nu apasă nimic.
Articolul ăsta acoperă tot ce ai nevoie ca tunelul VPN să fie fiabil pe un server headless: de ce CLI-ul e singura alegere logică, cum îl integrezi cu systemd, cum verifici că traficul chiar trece prin tunel (nu doar că serviciul rulează), cum adaugi un watchdog care îl repornește dacă cade, cum configurezi killswitch-ul, și ce faci când rulezi containere Docker alături de VPN.
De ce nu funcționează GUI-ul pe un server headless
O aplicație grafică pentru VPN pornește un proces care se atașează la o sesiune de display. Pe un server fără X11 sau Wayland, sesiunea aia nu există. Unele aplicații GUI încearcă să pornească oricum și eșuează în tăcere, altele cer explicit un display și refuză să ruleze. Nici una dintre variante nu e utilă.
Dar problema mai profundă nu e că „nu pornește” - e că chiar dacă ai reuși cumva să o pornești, GUI-ul e proiectat greșit pentru cazul de utilizare al unui server. Iată de ce:
Un client GUI gestionează starea conexiunii vizual. Când tunelul cade, îți afișează un mesaj și așteptă să dai click. Nu există un mecanism standard prin care un script extern să detecteze starea și să reacționeze. Exit code-ul procesului nu reflectă starea conexiunii VPN, ci doar dacă aplicația rulează sau nu - două lucruri complet diferite.
CLI-ul funcționează opus: fiecare comandă returnează un exit code care spune exact ce s-a întâmplat, logurile merg la journald și pot fi parsate, iar starea conexiunii poate fi interogată programatic. Asta e tot ce ai nevoie ca să construiești ceva fiabil.
Ce ai nevoie înainte să începi
Ai nevoie de un cont Proton VPN. Planul gratuit funcționează pentru teste, dar pentru un server în producție care rulează continuu vei ajunge repede la limitele lui: serverele gratuite sunt partajate și limitate ca lățime de bandă, și nu ai acces la Secure Core sau la serverele dedicate. Pentru infrastructură serioasă, Proton Plus e practic obligatoriu.
Distribuția nu contează foarte mult - tutorialul funcționează pe orice distribuție bazată pe Debian sau Red Hat. Comenzile de instalare diferă puțin, dar restul e identic.
Ai nevoie de acces root sau sudo pe server, și de conexiune SSH activă.
Instalarea Proton VPN CLI
Proton oferă un script oficial de instalare și pachete pentru distribuțiile principale. Nu instala din pip sau din alte surse terțe - versiunile neoficiale rămân în urmă cu actualizările de securitate.
Pe distribuții bazate pe Debian/Ubuntu:
wget https://repo.protonvpn.com/debian/dists/stable/main/binary-all/protonvpn-stable-release_1.0.3-3_all.deb
sudo dpkg -i protonvpn-stable-release_1.0.3-3_all.deb
sudo apt update
sudo apt install proton-vpn-gnome-desktop
Numele pachetului conține „gnome” dar nu instalează interfața grafică pe un sistem headless - instalează doar clientul și dependențele necesare. Poți verifica ce se instalează cu apt-cache show proton-vpn-gnome-desktop înainte.
Pe distribuții bazate pe Red Hat/Fedora:
sudo dnf install https://repo.protonvpn.com/fedora-$(rpm -E %fedora)/devel/noarch/protonvpn-stable-release-1.0.1-1.noarch.rpm
sudo dnf install proton-vpn-gnome-desktop
După instalare, verifici că ai comanda disponibilă:
protonvpn-cli --version
Autentificarea
Autentificarea se face o singură dată și credențialele sunt salvate local. Rulezi:
protonvpn-cli login <username>
Unde <username> e username-ul contului tău Proton VPN, nu adresa de email. Ți se va cere parola. Credențialele sunt stocate în ~/.local/share/protonvpn/ cu permisiuni restrictive.
Un punct important pe care puțini tutoriale îl menționează: dacă rulezi CLI-ul ca root (ceea ce vei face pentru integrarea cu systemd), autentificarea trebuie făcută și ca root:
sudo protonvpn-cli login <username>
Credențialele pentru root și pentru userul tău normal sunt separate. Dacă te autentifici ca user normal și apoi încerci să conectezi ca root, vei primi o eroare despre credențiale lipsă.
Prima conexiune manuală
Înainte să automatizezi orice, testezi că funcționează manual:
sudo protonvpn-cli connect --fastest
Flag-ul --fastest selectează automat serverul cu cea mai bună latență din lista disponibilă. Alte opțiuni utile:
sudo protonvpn-cli connect --cc RO # server în România
sudo protonvpn-cli connect --cc DE # server în Germania
sudo protonvpn-cli connect -p WireGuard # forțează protocolul WireGuard
sudo protonvpn-cli connect -p OpenVPN-UDP # forțează OpenVPN pe UDP
WireGuard e în general mai rapid și mai stabil pe servere - are overhead mai mic și reconectarea după pierderi de rețea e mai rapidă decât OpenVPN.
Verifici starea conexiunii:
sudo protonvpn-cli status
Ar trebui să vezi ceva de genul:
Status: Connected
Server: DE#123
Country: Germany
Protocol: WireGuard
IP: 185.xxx.xxx.xxx
Deconectezi cu:
sudo protonvpn-cli disconnect
Integrarea cu systemd
Rularea manuală nu e ce vrei pe un server. Vrei ca tunelul să pornească automat la boot, înainte de orice alt serviciu care depinde de el, și să fie gestionat de systemd la fel ca orice alt serviciu.
Creezi fișierul de serviciu:
sudo nano /etc/systemd/system/protonvpn.service
Conținutul:
[Unit]
Description=Proton VPN
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/protonvpn-cli connect --fastest -p WireGuard
ExecStop=/usr/bin/protonvpn-cli disconnect
RemainAfterExit=true
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Câteva explicații pentru că fiecare linie contează:
Type=oneshot înseamnă că systemd consideră serviciul pornit după ce comanda din ExecStart termină execuția, nu în timpul ei. E corect pentru CLI-ul Proton VPN, care se conectează și iese - nu rămâne ca proces în background. Dacă ai folosi Type=simple, systemd ar considera serviciul căzut imediat ce comanda se termină.
RemainAfterExit=true îi spune lui systemd să considere serviciul activ chiar după ce procesul din ExecStart a terminat. Fără asta, systemctl status protonvpn ar raporta „inactive” imediat după conectare, chiar dacă tunelul e activ.
After=network-online.target și Wants=network-online.target se asigură că serviciul pornește doar după ce rețeaua e funcțională, nu imediat la boot când interfețele s-ar putea să nu fie încă ridicate. Diferența dintre Wants și Requires: Wants înseamnă că încearcă să pornească network-online.target dacă nu e deja activ, dar nu eșuează dacă nu reușește. Requires ar bloca pornirea dacă network-online.target eșuează.
Activezi și pornești serviciul:
sudo systemctl daemon-reload
sudo systemctl enable protonvpn
sudo systemctl start protonvpn
Verifici starea:
sudo systemctl status protonvpn
Și te uiți la log-uri:
sudo journalctl -u protonvpn -f
Flag-ul -f urmărește log-urile în timp real, util pentru debugging.
Verificarea că traficul chiar trece prin tunel
Asta e partea pe care aproape nimeni nu o explică și care cauzează cele mai multe probleme silențioase. systemctl status protonvpn îți spune că serviciul e activ. Nu îți spune dacă traficul tău efectiv iese prin tunelul VPN.
Cele două întrebări sunt diferite. Tunelul poate fi „activ” din perspectiva systemd, dar traficul poate trece pe lângă el dacă ceva e greșit cu rutarea.
Verifici cu:
curl --interface tun0 https://api.ipify.org
--interface tun0 forțează curl să folosească interfața de rețea a tunelului VPN. Dacă comanda returnează un IP, compari cu IP-ul real al serverului tău:
curl https://api.ipify.org
Dacă cele două IP-uri sunt diferite, traficul trece prin VPN. Dacă sunt identice, ai o problemă de rutare.
tun0 e numele implicit al interfeței pentru OpenVPN. Pentru WireGuard, interfața se numește de obicei proton0 sau wg0. Verifici cu:
ip link show
Și cauți interfața care nu existau înainte de conectare.
O altă verificare utilă e să te uiți la tabelul de rutare:
ip route show
Ar trebui să vezi o rută default sau specific routes care trimit traficul prin interfața tunelului. Dacă ruta default (0.0.0.0/0) indică spre interfața originală a serverului (de obicei eth0 sau ens3), nu spre tunel, traficul nu trece prin VPN.
Killswitch: ce e și cum îl configurezi
Killswitch-ul e o funcție de securitate care blochează tot traficul de rețea dacă VPN-ul se deconectează. Fără killswitch, dacă tunelul cade, conexiunile tale continuă să meargă direct - cu IP-ul real al serverului, fără protecție. Dacă rulezi ceva care necesită ca traficul să treacă obligatoriu prin VPN (scraping, acces la resurse geo-restricționate, sau pur și simplu nu vrei să expui IP-ul real), killswitch-ul e esențial.
CLI-ul Proton VPN are killswitch integrat. Îl activezi cu:
sudo protonvpn-cli config --killswitch on
Și verifici că e activ:
sudo protonvpn-cli config --list
Când killswitch-ul e activ și VPN-ul cade, sistemul nu mai are acces la rețea până la reconectare. Asta înseamnă că și accesul SSH poate fi blocat dacă VPN-ul cade - sau mai precis, sesiunile SSH existente rămân active (TCP e stabilit), dar nu poți deschide sesiuni noi.
Atenție importantă: Înainte să activezi killswitch-ul pe un server de producție la care accesul SSH e singurul mod de a-l administra, asigură-te că ai un mecanism de recuperare. Unii provideri de VPS oferă consolă de acces din panoul de control, independentă de rețea. Verifici că ai acces la ea înainte.
O alternativă mai controlată decât killswitch-ul integrat e să implementezi regulile de firewall manual cu iptables sau nftables, unde ai mai mult control:
# Permite trafic pe interfața de loopback
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
# Permite trafic pe interfața VPN (WireGuard)
sudo iptables -A INPUT -i proton0 -j ACCEPT
sudo iptables -A OUTPUT -o proton0 -j ACCEPT
# Permite traficul de autentificare VPN (portul 51820 pentru WireGuard)
sudo iptables -A OUTPUT -p udp --dport 51820 -j ACCEPT
sudo iptables -A INPUT -p udp --sport 51820 -j ACCEPT
# Permite SSH pe interfața reală (ca să nu te blochezi)
sudo iptables -A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -p tcp --sport 22 -j ACCEPT
# Blochează tot restul
sudo iptables -A INPUT -j DROP
sudo iptables -A OUTPUT -j DROP
Salvezi regulile cu iptables-save > /etc/iptables/rules.v4 și le incarci la boot cu pachetul iptables-persistent.
Watchdog: ce se întâmplă când tunelul cade
Chiar cu systemd și Restart=on-failure, există scenarii în care tunelul cade și systemd nu știe că a căzut. Type=oneshot cu RemainAfterExit=true înseamnă că din perspectiva systemd serviciul e „activ” atâta timp cât nu i-ai spus explicit că nu e. Dacă procesul de tunel se închide neașteptat fără să treacă printr-un exit code detectabil, systemd rămâne cu impresia că totul e bine.
Un watchdog simplu rezolvă asta: un script care rulează periodic, verifică dacă tunelul e funcțional, și dacă nu e, îl repornește.
Creezi scriptul:
sudo nano /usr/local/bin/protonvpn-watchdog.sh
Conținutul:
#!/bin/bash
LOG_TAG="protonvpn-watchdog"
# Verifică dacă interfața tunelului există
if ! ip link show tun0 &>/dev/null && ! ip link show proton0 &>/dev/null; then
logger -t "$LOG_TAG" "Interfata VPN nu gasita. Incerc reconectarea..."
systemctl restart protonvpn
sleep 10
# Verifică din nou după reconectare
if ! ip link show tun0 &>/dev/null && ! ip link show proton0 &>/dev/null; then
logger -t "$LOG_TAG" "Reconectarea a esuat."
else
logger -t "$LOG_TAG" "Reconectare reusita."
fi
else
# Interfața există, verifică dacă traficul chiar trece
REAL_IP=$(curl -s --max-time 5 https://api.ipify.org)
VPN_IP=$(curl -s --max-time 5 --interface tun0 https://api.ipify.org 2>/dev/null || \
curl -s --max-time 5 --interface proton0 https://api.ipify.org 2>/dev/null)
if [ -z "$VPN_IP" ]; then
logger -t "$LOG_TAG" "Nu pot obtine IP prin tunel. Incerc reconectarea..."
systemctl restart protonvpn
elif [ "$REAL_IP" = "$VPN_IP" ]; then
logger -t "$LOG_TAG" "IP real ($REAL_IP) = IP VPN ($VPN_IP). Traficul nu trece prin tunel. Reconect..."
systemctl restart protonvpn
fi
fi
Îi dai permisiuni de execuție:
sudo chmod +x /usr/local/bin/protonvpn-watchdog.sh
Adaugi un cron job care rulează la fiecare 5 minute:
sudo crontab -e
Adaugi linia:
*/5 * * * * /usr/local/bin/protonvpn-watchdog.sh
Log-urile watchdog-ului merg în syslog și le poți vedea cu:
sudo journalctl -t protonvpn-watchdog
O variantă mai elegantă decât cron e să creezi un timer systemd, dar pentru un watchdog simplu ca ăsta cron e suficient și mai ușor de înțeles.
Proton VPN și Docker: ce trebuie să știi
Containerele Docker au propria stivă de rețea și implicit nu moștenesc rutarea VPN a hostului. Asta înseamnă că dacă ai containere care fac request-uri HTTP, acele request-uri ies direct pe interfața reală a serverului, nu prin VPN - chiar dacă VPN-ul e activ pe host.
Există două abordări principale, fiecare cu trade-off-urile ei:
Varianta 1: Rulezi containerele în network mode host
# docker-compose.yml
services:
aplicatia-mea:
image: imaginea-mea
network_mode: host
Cu network_mode: host, containerul folosește direct stiva de rețea a hostului, inclusiv rutarea VPN. E simplu, dar pierde izolarea de rețea pe care Docker o oferă în mod normal - containerul vede și poate accesa toate interfețele hostului.
Varianta 2: Forțezi traficul containerelor prin interfața VPN
Adaugi o regulă iptables care marchează și reroutează traficul din subnet-ul Docker:
# Subnet-ul implicit Docker e 172.17.0.0/16
sudo iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -o tun0 -j MASQUERADE
Sau pentru WireGuard:
sudo iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -o proton0 -j MASQUERADE
Asta face ca tot traficul care iese din containere să fie mascaradat prin interfața VPN. Dezavantajul e că dacă VPN-ul cade și nu ai killswitch, traficul containerelor revine pe interfața reală.
Varianta 3: Un container dedicat VPN pentru alte containere
Există imagini Docker care rulează Proton VPN (sau orice alt VPN cu OpenVPN/WireGuard) și pot acționa ca gateway pentru alte containere. Containerele care au nevoie de VPN se conectează la containerul VPN prin network_mode: container:vpn-container.
services:
vpn:
image: qmcgaw/gluetun
cap_add:
- NET_ADMIN
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=<cheia-ta>
- SERVER_COUNTRIES=Germany
aplicatia-mea:
image: imaginea-mea
network_mode: "container:vpn"
depends_on:
- vpn
Această abordare e cea mai curată pentru izolarea containerelor care necesită VPN față de cele care nu au nevoie.
Verifici că un container trimite trafic prin VPN:
docker exec -it <container-name> curl https://api.ipify.org
Și compari rezultatul cu IP-ul real al serverului.
Probleme comune și cum le rezolvi
„protonvpn-cli: command not found” când rulezi ca root
CLI-ul poate fi instalat în /usr/local/bin/ care e în PATH pentru userul normal dar nu pentru root. Verifici cu which protonvpn-cli ca user normal, și folosești calea completă în fișierul de serviciu systemd.
Serviciul pornește dar systemctl status arată „failed”
Cel mai probabil cauza e că CLI-ul nu găsește credențialele. Asigură-te că ai rulat sudo protonvpn-cli login (cu sudo, nu ca user normal). Verifici log-urile cu sudo journalctl -u protonvpn -n 50.
Tunelul cade des și watchdog-ul reconectează frecvent
Poate fi o problemă cu serverul ales sau cu protocolul. Încearcă să forțezi WireGuard în loc de OpenVPN cu -p WireGuard în ExecStart. WireGuard e semnificativ mai stabil pe conexiuni instabile.
SSH se blochează după activarea killswitch-ului
Dacă killswitch-ul blochează SSH, ai nevoie de acces la consola VPS-ului din panoul de control al providerului. Dezactivezi killswitch-ul din consolă cu sudo protonvpn-cli config --killswitch off și te reconectezi prin SSH. Pe viitor, folosești varianta cu iptables manuală care permite explicit SSH.
Containerele Docker nu trec prin VPN
Verifici interfața VPN cu ip link show și te asiguri că folosești numele corect (tun0 vs proton0) în regulile iptables. Dacă ai folosit o imagine gluetun sau similară, verifici că variabilele de mediu sunt corecte.
Cum arată o configurare completă
Ca să rezum, pe un server corect configurat ai:
/etc/systemd/system/protonvpn.service- pornește tunelul la boot, înainte de alte servicii/usr/local/bin/protonvpn-watchdog.sh- verifică la fiecare 5 minute că traficul chiar trece prin tunel- Killswitch activ (sau reguli iptables echivalente) - asigură că nu există scurgeri de trafic dacă VPN-ul cade
- Regulile iptables pentru Docker - dacă rulezi containere care au nevoie de VPN
Testul final: repornești serverul, aștepți 30 de secunde și verifici curl ``https://api.ipify.org. Dacă returnează un IP diferit față de IP-ul real al serverului, totul funcționează corect.
Un VPN pe un server headless nu e complicat dacă înțelegi că e o problemă de process management, nu de interfață. Odată ce ai pus piesele la locul lor - CLI autentificat ca root, serviciu systemd cu dependențele corecte, watchdog care verifică traficul real nu doar starea serviciului - rulează singur fără să mai ai nevoie să te gândești la el.