VM WireGuard + ProtonVPN (Gateway “VPN-only”)
Questo documento descrive come creare una VM gateway (Ubuntu/Debian) che:
- si connette a ProtonVPN via WireGuard
- fa da gateway per altre VM/LXC (es. aria2, jdownloader, ecc.)
- applica un kill-switch: se WireGuard/VPN cade, nessun traffico esce verso Internet (niente leak)
Riferimenti ufficiali
- ProtonVPN — WireGuard configurations: https://protonvpn.com/support/wireguard-configurations
- WireGuard — Quickstart: https://www.wireguard.com/quickstart/
- wg-quick (man): https://man7.org/linux/man-pages/man8/wg-quick.8.html
- nftables NAT/masquerade (doc Red Hat): https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/security_guide/sec-configuring_nat_using_nftables
0) Variabili (adatta ai tuoi IP)
Esempio basato su una LAN 192.168.110.0/24:
- VPN_GW_VM_IP:
192.168.110.50 - LAN_GW (router/pfSense):
192.168.110.1 - LAN_IF (interfaccia LAN sulla VM):
ens18(può essereens19,enp1s0, ecc.) - WG_IF (interfaccia WireGuard):
wg0 - DOWNLOADER_HOSTS: client che useranno questo gateway (es.
192.168.110.32,192.168.110.33, …)
Approccio più semplice: sui client downloader imposti gateway = 192.168.110.50.
1) Proxmox: crea la VM
- Crea una VM Ubuntu Server 22.04/24.04 o Debian 12.
- 1 NIC su
vmbr0(o il bridge della VLAN corretta). - Imposta IP statico sulla VM (es.
192.168.110.50/24) e gateway192.168.110.1.
2) VM: installa WireGuard e tool
Ubuntu/Debian
3) ProtonVPN: genera e scarica la config WireGuard
- Vai su account ProtonVPN → Downloads → WireGuard configuration
- Genera una config (scegli server/paese, ecc.)
- Scarica il file
.conf(contienePrivateKey,Address,DNS,Endpoint,PublicKey,AllowedIPs, …)
Guida Proton: https://protonvpn.com/support/wireguard-configurations
⚠️ Se stai rigenerando una config per sostituire una esistente (es. dopo il problema descritto in §10.3), il file scaricato da Proton è pensato per un client "normale" e non contiene
Table = 51820né i blocchiPostUp/PostDowndello split-routing. Devi riportarli a mano dal file precedente, altrimenti il forwarding per i client downloader smette di funzionare anche con l'handshake riuscito. Vedi §10.3 per il file completo di esempio.Consigliato: aggiungi anche
PersistentKeepalive = 25nel blocco[Peer]— mantiene aperto il mapping NAT del tuo router/ISP anche con traffico intermittente, riducendo il rischio che il tunnel smetta di ricevere risposte dopo periodi di inattività.
4) Importa la config e avvia wg-quick
- Copia la config dentro la VM, poi:
mkdir -p /etc/wireguard
chmod 700 /etc/wireguard
cp /root/proton.conf /etc/wireguard/wg0.conf
chmod 600 /etc/wireguard/wg0.conf
- Avvio manuale di test:
- Abilita all’avvio:
Riferimento wg-quick: https://man7.org/linux/man-pages/man8/wg-quick.8.html
5) Abilita IP forwarding (la VM deve fare routing)
cat >/etc/sysctl.d/99-vpn-gw.conf <<'EOF'
net.ipv4.ip_forward=1
EOF
sysctl -p /etc/sysctl.d/99-vpn-gw.conf
6) nftables: NAT + Kill-Switch (VPN-only)
Obiettivo:
- accetto forward LAN -> wg0
- blocco LAN -> Internet via LAN_IF (bypass)
- NAT (masquerade) in uscita su wg0
6.1) Crea /etc/nftables.conf
Sostituisci
ens18con la tua interfaccia LAN reale se diversa.
cat >/etc/nftables.conf <<'NFT'
flush ruleset
define LAN_IF = "ens18"
define WG_IF = "wg0"
define LAN_NET = 192.168.110.0/24
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif "lo" accept
# Consenti SSH dalla LAN (opzionale ma consigliato)
iifname $LAN_IF ip saddr $LAN_NET tcp dport 22 accept
# ICMP dalla LAN (ping)
iifname $LAN_IF ip protocol icmp accept
# (Opzionale) accetta traffico in ingresso dalla VPN
iifname $WG_IF accept
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
# LAN -> VPN OK
iifname $LAN_IF oifname $WG_IF accept
# Kill-switch: vieta LAN -> WAN (bypass) se non passa da wg0
iifname $LAN_IF oifname $LAN_IF drop
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# NAT verso VPN
oifname $WG_IF ip saddr $LAN_NET masquerade
}
}
NFT
6.2) Abilita nftables
Doc NAT/masquerade: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/security_guide/sec-configuring_nat_using_nftables
7) Client (VM/LXC downloader): passare dal gateway VPN
Opzione A (semplice, robusta): imposta gateway = VPN_GW_VM_IP
Sul client downloader:
- IP statico (es.
192.168.110.32/24) - Gateway:
192.168.110.50 - DNS: come preferisci (vedi note DNS)
Test sul client:
Opzione B (avanzata): policy routing “solo per certi IP”
Se vuoi lasciare il gateway dei client su pfSense ma forzare “alcuni IP” via VPN:
- serve policy routing (mark + routing table) sulla VM.
(Implementazione da fare su richiesta perché dipende da sub-reti e obiettivi.)
8) Verifiche “a prova di paranoia”
8.1) Verifica che la VM stia usando la VPN
Sulla VM gateway:
⚠️ Attenzione: questo
curleseguito dalla VM stessa passa dalla tabella di routing principale (default via LAN_GW dev LAN_IF), non dalla VPN — lo split-routing di questo playbook instrada viawg0solo il traffico forwardato dai client (quello che entra daLAN_IF), non il traffico generato localmente dalla VM. Un curl riuscito qui conferma solo che la VM ha accesso diretto a Internet, non che il tunnel funzioni. Per testare davvero il tunnel, usawg show(bytes received) o il curl dal client in §7.
8.2) Verifica kill-switch
- Spegni WireGuard sulla VM:
- Sul client downloader prova:
Atteso: timeout / fail (niente traffico → niente leak)
- Riattiva:
9) Note DNS (anti “DNS leak”)
Se i client usano DNS “di LAN” (es. AdGuard/pfSense), le query DNS restano in LAN. Se vuoi che anche il DNS segua la VPN:
- imposta sui client un DNS raggiungibile via VPN (o usa quello indicato nella config Proton)
- oppure fai “DNS proxy” sulla VM gateway (unbound/dnsmasq) e fai puntare i client a quello.
10) Troubleshooting rapido
10.1) WireGuard
10.2) Routing / NAT
Verifica path “da client”:
10.3) Sintomo: il tunnel invia ma non riceve mai nulla (0 B received)
Come si presenta:
wg showmostratransfer: 0 B received, N KiB sent— i bytes sent crescono ma received resta sempre a zero, e non compare mai la rigalatest handshake.- Nella chain
forwarddi nftables, il contatore della regola LAN→wg0 (uscita) cresce normalmente, ma quello della regola wg0→LAN (ritorno) resta fermo a0 bytes 0 packets. wg-quick down wg0 && wg-quick up wg0non cambia nulla: torna subito a 0 B received.- Il client dietro il gateway (LXC/VM downloader) riesce a pingare il gateway ma va in timeout su qualsiasi traffico verso Internet.
Come isolare la causa (in ordine, il più veloce per primo):
- Escludi il clock:
date -uetimedatectl status— seSystem clock synchronized: no, risolvi prima l'NTP (un orologio sfasato fa scartare silenziosamente l'handshake per via del timestamp anti-replay di WireGuard). - Escludi il routing/rete generale:
ping -c 5 <IP_ENDPOINT_SERVER>dalla VM. Se risponde con latenza normale, la rete verso quell'host funziona a livello IP — il problema è isolato all'handshake WireGuard stesso, non a router/ISP. - Conferma con tcpdump: mentre generi traffico dal client,
tcpdump -ni <LAN_IF> udp port 51820sulla VM. Se vedi solo pacchetti in uscita verso l'endpoint (i retry di handshake, ogni ~5s) e mai una risposta, il server non sta rispondendo a questa chiave — non è un problema locale di nftables/routing (tcpdump cattura prima del filtro). - Prova un endpoint/chiave diversi: genera una nuova config WireGuard dal pannello ProtonVPN (anche per un server diverso) e sostituiscila. Se con la nuova chiave l'handshake si completa subito, la causa era la chiave/peer revocato o scaduto lato Proton sul vecchio server — capita ad esempio dopo rotazioni interne o inattività prolungata, e il server droppa l'handshake senza inviare alcun errore esplicito, da qui il "silenzio totale" invece di un rifiuto visibile.
Fix: sostituisci la config come descritto nella nota in §3, ricordando di riportare a mano Table = 51820 e i blocchi PostUp/PostDown (il file scaricato da Proton non li include), e aggiungi PersistentKeepalive = 25 per prevenire ricadute dovute a scadenza del mapping NAT.
11) Hardening minimo consigliato
- SSH solo da LAN (già in nftables)
- niente servizi extra sulla VM
- aggiornamenti:
Appendix: perché wg-quick e non “app Proton”
wg-quick è perfetto quando vuoi una VM “router/gateway” senza UI, gestibile via systemd.
WireGuard quickstart: https://www.wireguard.com/quickstart/