WireGuard lento: come trovare la causa reale
WireGuard lento? Un MTU errato può consentire ping e SSH, ma bloccare i trasferimenti. Misura l'MTU con bisezione, limita il TCP MSS e controlla lo steal time.
Da cosa dipendono realmente le basse velocità di WireGuard
Le basse velocità di WireGuard dipendono da una di quattro cause, ma non sono tutte ugualmente probabili. La prima è l'MTU (maximum transmission unit): il tunnel crea pacchetti troppo grandi per uno dei collegamenti lungo il percorso, quindi i trasferimenti di grandi quantità di dati si bloccano mentre quelli piccoli funzionano normalmente. La seconda è il percorso di rete, che era già il fattore limitante prima della creazione del tunnel. La terza è la CPU di un piccolo VPS condiviso, dove la cifratura compete con tutti gli altri guest sullo stesso host. La quarta è la connessione del peer.
Verificale in quest'ordine. L'MTU viene prima perché è l'unica causa dell'elenco introdotta direttamente da WireGuard e perché i suoi sintomi non sembrano affatto una semplice lentezza. Un MTU errato si manifesta di solito con un tunnel che si connette immediatamente, risponde a ping, accetta un accesso SSH e poi si blocca al primo tentativo di copiare un file.
Prima di tutto, conviene escludere un sintomo specifico. Se ogni nuovo sito impiega diversi secondi prima di iniziare il caricamento e poi trasferisce i dati alla massima velocità, il problema riguarda la risoluzione dei nomi, non il throughput. DNS tramite WireGuard ha modalità di errore proprie e nessuna modifica dell'MTU può risolverle.
Perché l’MTU di WireGuard è 1420?
Ogni pacchetto inviato nel tunnel viene cifrato e inserito in un nuovo pacchetto. L’involucro aggiunge byte, che vengono sottratti al payload.
L’header dati di WireGuard è di 32 byte: un campo tipo di 4 byte, un indice del destinatario di 4 byte, un contatore di 8 byte e il tag di autenticazione Poly1305 di 16 byte. Attorno a questo header si trova un header UDP di 8 byte. Attorno all’header UDP si trova l’header IP esterno, che è di 20 byte per IPv4 e di 40 byte per IPv6. L’incapsulamento totale è quindi di 60 byte quando il tuo Endpoint è un indirizzo IPv4 e di 80 byte quando è IPv6. La pagina del protocollo WireGuard documenta il formato dei messaggi da cui derivano questi valori.
wg-quick non fa una stima. Legge l’MTU dell’interfaccia che instrada il traffico verso il tuo Endpoint, quindi sottrae 80. Su un normale percorso Ethernet da 1500 byte, il risultato è 1420, che è il valore stampato da ip link show wg0. Sottrae 80 invece di 60 affinché lo stesso valore resti sicuro se in futuro quell’endpoint viene raggiunto tramite IPv6, dove l’header esterno è più grande di 20 byte.
The data behind this chart
[
{
"label": "Ethernet, IPv4 endpoint",
"path_mtu": 1500,
"encap_overhead": 60,
"usable_wg0_mtu": 1440
},
{
"label": "Ethernet, IPv6 endpoint",
"path_mtu": 1500,
"encap_overhead": 80,
"usable_wg0_mtu": 1420
},
{
"label": "PPPoE DSL, IPv4 endpoint",
"path_mtu": 1492,
"encap_overhead": 60,
"usable_wg0_mtu": 1432
},
{
"label": "Extra tunnel in the path",
"path_mtu": 1400,
"encap_overhead": 80,
"usable_wg0_mtu": 1320
}
]Le 4 righe riportano calcoli aritmetici, non misurazioni. Su un percorso integro da 1500 byte con un endpoint IPv4, 1440 sarebbe sufficiente, quindi il valore predefinito 1420 lascia inutilizzati 20 byte. Questo margine è intenzionale e non costituisce un problema.
L’ultima riga è quella critica. Quando un collegamento del percorso supporta soltanto 1400 byte, un tunnel ancora impostato su 1420 produce, per ogni segmento di dimensione massima, un pacchetto esterno da 1500 byte, cioè 100 byte in più rispetto a quanto quel collegamento può accettare. Il valore compatibile è 1320.
Non copiare nemmeno 1320. L’MTU del percorso dipende dal tuo percorso. L’unico modo per conoscerlo è misurarlo.
Come si manifesta un MTU errato
Il problema non è graduale. Si osserva una separazione netta tra i pacchetti piccoli e quelli grandi.
pingattraverso il tunnel funziona con qualsiasi dimensione normale.- Il login SSH viene completato e la digitazione resta reattiva.
curl -I https://example.comrestituisce subito gli header.curl https://example.comsu una pagina grande si blocca dopo i primi kilobyte.scpdi un file grande inizia e poi si arresta a una determinata percentuale.- Una sessione SSH si blocca nel momento in cui si esegue un comando che stampa molto output.
Tutto questo accade perché una connessione TCP costruisce segmenti di dimensione massima solo quando deve trasferire una quantità consistente di dati. L'handshake e la prima richiesta rientrano nell'MTU di qualsiasi collegamento del percorso. Il blocco inizia con il primo segmento di dimensione massima. Per questo la connessione sembra funzionare correttamente fino al momento in cui diventa inutilizzabile.
Un pacchetto esterno più grande dell'MTU del collegamento successivo può avere uno di due esiti.
Viene frammentato. Un router lo divide e l'estremità remota riassembla i frammenti. Il trasferimento funziona, ma è più lento, perché per un singolo pacchetto vengono ora trasmessi due pacchetti e il destinatario deve mantenere lo stato finché non li riceve entrambi. La perdita di un frammento comporta la perdita dell'intero pacchetto originale. Di conseguenza, un percorso con una perdita dell'1% si comporta come un percorso molto peggiore. Molti firewall eliminano inoltre i frammenti IP in base ai criteri configurati, trasformando questo esito nel successivo.
Viene eliminato e potresti non ricevere alcuna segnalazione. Un router che non può frammentare invia al mittente un messaggio ICMP (internet control message protocol) "fragmentation needed", indicando l'MTU accettato. Se il messaggio arriva, il rilevamento dell'MTU del percorso funziona e il mittente riduce autonomamente la dimensione dei segmenti. Molte reti filtrano ICMP, quindi spesso il messaggio non arriva. Nessun altro componente segnala la perdita. Questo è il black hole: il pacchetto parte, non torna nulla, nei log di nessuna delle due estremità compare un errore e il trasferimento resta bloccato finché una delle operazioni non va in timeout.
Come trovare l'MTU corretto?
Misura il percorso, quindi esegui la sottrazione. Testa l'underlay anziché il tunnel: invia quindi un ping all'indirizzo pubblico del server dal client e impedisci la frammentazione.
ping -M do -s 1472 -c 3 203.0.113.10-M do imposta il bit DF (don't fragment), quindi nessun router lungo il percorso può suddividere il pacchetto. -s indica la dimensione del payload ICMP. Un pacchetto IPv4 completo è costituito da questo payload, più 8 byte di intestazione ICMP e 20 byte di intestazione IP. Di conseguenza, -s 1472 invia esattamente 1500 byte sulla rete.
Sono importanti tre risultati. Le risposte regolari indicano che 1500 byte passano e che l'MTU non è il problema. Un errore locale indica che l'interfaccia locale è già più piccola della dimensione richiesta:
ping: local error: message too long, mtu=1500Una risposta da parte di un router intermedio fornisce direttamente il valore da usare e puoi fermarti:
From 203.0.113.1 icmp_seq=1 Frag needed and DF set (mtu = 1492)Una perdita del 100% dei pacchetti a 1472, con risposte regolari a una dimensione inferiore, indica un black hole. Nessun router comunica informazioni, quindi devi trovare il limite dimezzando l'intervallo. Mantieni una dimensione che sai essere valida e una che sai essere troppo grande, testa il punto intermedio e sposta il limite corrispondente al risultato. A ogni iterazione l'intervallo residuo si dimezza, quindi bastano cinque o sei iterazioni.
Esempio di ricerca per bisezione, un'iterazione alla volta
Ogni riga è un comando eseguito sul client verso l'indirizzo pubblico del server. Il commento indica il risultato ottenuto.
ping -M do -s 1472 -c 3 203.0.113.10 # 100% loss, so 1500 is too big
ping -M do -s 1272 -c 3 203.0.113.10 # replies, so 1300 fits
ping -M do -s 1372 -c 3 203.0.113.10 # replies, so 1400 fits
ping -M do -s 1422 -c 3 203.0.113.10 # 100% loss, so 1450 is too big
ping -M do -s 1397 -c 3 203.0.113.10 # 100% loss, so 1425 is too big
ping -M do -s 1384 -c 3 203.0.113.10 # 100% loss, so 1412 is too bigIl payload massimo che ha ricevuto risposta è 1372. Questo percorso supporta quindi almeno 1400 byte e meno di 1412. Usa il limite sicuro. Un MTU del percorso pari a 1400, meno 80 byte di incapsulamento, produce un MTU di 1320 per wg0.
tracepath esegue autonomamente la stessa ricerca e conviene eseguirlo una volta prima di iniziare la bisezione:
tracepath -n 203.0.113.10L'ultima riga riporta il risultato:
Resume: pmtu 1492 hops 12 back 12Considera entrambi gli strumenti come un punto di partenza, non come una prova. Alcuni host limitano la frequenza o scartano completamente il traffico ICMP. Di conseguenza, la bisezione può indicare un MTU inferiore a quello effettivamente supportato dal percorso. Il trasferimento che aveva generato il problema è il test più attendibile.
Applica prima il valore temporaneamente, perché in caso di stima errata puoi annullarla con un solo comando:
sudo ip link set mtu 1320 dev wg0Riprova il trasferimento che rimaneva bloccato. Se termina correttamente, rendi permanente il valore nel blocco [Interface] del client:
[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
MTU = 1320sudo wg-quick down wg0 && sudo wg-quick up wg0
ip link show wg0ip link show wg0 dovrebbe ora stampare mtu 1320. Se stampa ancora il valore precedente, wg-quick non ha letto il file modificato. Verifica di aver modificato /etc/wireguard/wg0.conf e che MTU si trovi sotto [Interface], non sotto [Peer], dove viene ignorato.
L'MTU è una proprietà di una singola interfaccia e non viene mai negoziato tra i peer. Impostarlo sul client riduce soltanto la dimensione dei pacchetti inviati dal client. Il server continua a generare pacchetti usando il proprio MTU di wg0. Di conseguenza, i download possono ancora finire in un black hole dopo che gli upload hanno iniziato a funzionare. Imposta il valore su entrambi i lati oppure limita l'MSS sul server.
Perché il clamping dell'MSS risolve TCP e nient'altro
Se il server inoltra il traffico dei peer, come avviene in qualsiasi configurazione standard di WireGuard su VPS che usa NAT (network address translation), una sola regola risolve il problema per TCP di tutti i peer e evita di dover cercare un valore su ogni client che non si controlla direttamente.
MSS (maximum segment size) è un'opzione TCP che ciascun lato inserisce nel pacchetto SYN per indicare la dimensione massima del segmento che è disposto a ricevere. Il clamping riscrive questa opzione durante il transito, adattandola all'MTU effettivo del percorso, così entrambi gli estremi concordano su segmenti più piccoli prima di trasferire dati. Funziona perché viene applicato durante l'handshake e perché non dipende da un messaggio ICMP che il percorso probabilmente filtra.
Con nftables, aggiungere questa tabella a /etc/nftables.conf sotto le tabelle esistenti:
table inet mangle {
chain forward {
type filter hook forward priority mangle; policy accept;
tcp flags syn tcp option maxseg size set rt mtu
}
}Ricaricare la configurazione con sudo systemctl reload nftables. Su un sistema che usa iptables, l'equivalente è una singola riga:
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtuVerificare che la regola si trovi sul percorso seguito dai pacchetti. Eseguire sudo nft list table inet mangle o sudo iptables -t mangle -L FORWARD -n -v mentre un client apre nuove connessioni e monitorare l'incremento del contatore. Un contatore fermo a zero indica che i pacchetti non attraversano quell'hook, quindi la regola non sta facendo nulla.
Ora vediamo i limiti effettivi. Il clamping riguarda solo TCP e solo il traffico inoltrato, quindi un servizio in esecuzione direttamente sul server WireGuard non attraversa mai l'hook forward e non viene mai sottoposto a clamping. Inoltre, il clamping riguarda le connessioni aperte dopo il caricamento della regola: le sessioni già esistenti mantengono l'MSS concordato in precedenza.
UDP non viene modificato, perché UDP non prevede un handshake da riscrivere. Nella maggior parte dei casi, il traffico UDP continua comunque a funzionare. QUIC, il trasporto usato da HTTP/3, verifica la dimensione dei pacchetti utilizzabile e inizia volutamente con pacchetti piccoli. Non funziona invece il traffico UDP che invia un singolo datagramma di grandi dimensioni e ne richiede la consegna completa, ad esempio una risposta DNSSEC (DNS security extensions) superiore a 1400 byte. Queste query scadono e vengono ritentate tramite TCP; per l'utente il risultato è un sito lento, non un sito non raggiungibile.
Il processore CPU del mio VPS è il limite?
WireGuard cifra il traffico con ChaCha20-Poly1305 e scambia le chiavi usando Curve25519. Nel percorso dei dati non viene usato AES, e questo comporta una conseguenza spesso fraintesa: le istruzioni AES-NI della CPU non apportano alcun vantaggio a WireGuard. Un host che dichiara il supporto ad AES-NI non offre quindi una funzionalità di prestazioni per WireGuard. ChaCha20 è stato scelto perché è veloce anche in puro software, incluse le CPU prive di qualsiasi accelerazione crittografica.
Questo non significa che WireGuard non consumi risorse. Su un VPS con 1 vCPU, un core gestisce sia la cifratura sia gli interrupt di rete, oltre al carico generato dall'applicazione.
Esegui la misurazione mentre è in corso un trasferimento:
sudo apt install -y sysstat
mpstat -P ALL 1Leggi le tre colonne. %soft indica il tempo trascorso in softirq, dove viene gestita l'elaborazione dei pacchetti nel kernel. %steal indica il tempo assegnato dall'hypervisor a un altro workload. %idle è il tempo rimanente.
Un valore di %soft vicino a 100 sull'unico core indica che il server ha raggiunto il limite di elaborazione dei pacchetti. È un limite reale, che un numero maggiore di core può aumentare. top mostra ksoftirqd/0 in cima all'elenco dei processi nello stesso momento. È la stessa situazione osservata da un'altra prospettiva.
Un valore di %steal superiore a pochi punti percentuali indica che il limite non è risolvibile da te, perché l'host è sovrallocato e la tua vCPU è in attesa di un core fisico. È una situazione comune nei piani condivisi più economici e varia durante la giornata. Il tempo sottratto da un vicino rumoroso richiede un'analisi specifica, e nessun valore MTU può risolvere il problema.
Un altro fattore è l'implementazione usata dal client. Il modulo del kernel Linux è il percorso più veloce e distribuisce la cifratura di un peer tra più core. wireguard-go, l'implementazione in userspace, è più lenta ed è quella usata dai client macOS e iOS, perché queste piattaforme non consentono a un'applicazione di caricare un modulo del kernel.
È il percorso o il collegamento del peer?
Prima di modificare i parametri, raccogli due valori dallo stesso client a distanza di pochi minuti: il throughput senza tunnel e quello con il tunnel. Senza questa coppia di valori, procedi per ipotesi.
Esegui iperf3 -s sul server. Il test diretto richiede che la porta TCP 5201 sia raggiungibile sull'indirizzo pubblico. Aprila per la durata del test e rimuovi la regola subito dopo. Verifica che la porta sia di nuovo chiusa invece di darlo per scontato.
# on the client, outside the tunnel
iperf3 -c 203.0.113.10
# then through the tunnel
iperf3 -c 10.8.0.1Se i due valori sono simili, WireGuard introduce un costo minimo e il limite è nel percorso di rete. Se il valore tramite tunnel è molto più basso di quello diretto mentre %soft è rimasto basso, torna a verificare l'MTU. La frammentazione riduce il throughput senza interrompere il funzionamento, quindi in questo caso si manifesta come una perdita percentuale e non come un blocco.
Esegui il test in entrambe le direzioni, perché le connessioni domestiche sono generalmente asimmetriche. iperf3 -c 10.8.0.1 -R inverte il flusso, così è il server a inviare. Un client su una linea 500/20 non potrà mai inviare nel tunnel più di 20 Mbit in upload. Nessuna modifica sul server può cambiare questo limite.
Esegui quindi il test con flussi paralleli:
iperf3 -c 10.8.0.1 -P 4Se quattro flussi trasferiscono complessivamente molto più di uno solo, una singola connessione TCP non riesce a sfruttare tutta la capacità del percorso. Il throughput di un singolo flusso è limitato dalla finestra di ricezione divisa per il tempo di andata e ritorno. Perciò, su un percorso con latenza di 150 ms serve una finestra ampia per trasferire una quantità elevata di dati. Leggi i tuoi limiti:
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmemIl terzo valore di ogni output è il limite massimo a cui Linux può arrivare con l'autotuning. Anche la perdita di pacchetti limita fortemente un singolo flusso, perché è la perdita a determinare la reazione del controllo della congestione TCP e, su un percorso lungo, il recupero è costoso. Esegui mtr -rwc 100 203.0.113.10 dal client per cento cicli, così puoi vedere in quale punto del percorso compare la perdita. Una perdita che inizia a un hop e continua fino all'hop finale è reale. Una perdita presente su un singolo hop intermedio e assente negli hop successivi indica che quel router assegna una priorità bassa a ICMP e non ha alcun significato.
Per ottenere un quadro ripetibile del server, separato dalla rete, esegui il benchmark della VPS con un metodo documentato, così potrai ripetere lo stesso test dopo una modifica e confrontare risultati omogenei.
Cosa non può risolvere WireGuard
WireGuard è un tunnel. Non può essere più veloce del collegamento più lento lungo il percorso che utilizza e la sua aggiunta rende sempre il percorso leggermente più lento.
Non esegue la compressione. Non esiste un equivalente di comp-lzo di OpenVPN e non è previsto, perché comprimere i dati prima di cifrarli divulga informazioni sul testo in chiaro. La maggior parte dei dati voluminosi è già compressa, quindi nella pratica questo non comporta costi. È una delle differenze effettive da valutare quando si confronta WireGuard con OpenVPN ed è una scelta progettuale intenzionale.
Un tunnel completo modifica il percorso seguito da ogni pacchetto. Il traffico che prima passava da te a una CDN vicina ora passa da te al tuo VPS e poi alla CDN. Se il VPS si trova in un altro continente, ogni richiesta subisce questa deviazione e il tempo di andata e ritorno aumenta di conseguenza. Nessun valore di configurazione può ridurlo. Sposta il VPS più vicino oppure usa uno split tunnel, in modo che solo il traffico che richiede la VPN percorra il tragitto più lungo. Il traffico che passa da un percorso o dall'altro è deciso interamente da AllowedIPs e il routing basato su cryptokey spiega come viene presa questa decisione.
I limiti del provider sono esterni al tunnel e si dimenticano facilmente. Un piano con una soglia mensile di larghezza di banda spesso limita la velocità della porta a un valore molto più basso quando la soglia viene superata; a quel punto il tunnel sembra non funzionare. Controlla il pannello del provider prima di dedicare un'intera serata al parametro MTU.
PersistentKeepalive non influisce sulla velocità di trasferimento. Serve a mantenere aperta una mappatura NAT, in modo che il server possa ancora raggiungere un client dietro un router domestico. Ridurlo al di sotto di 25 secondi aggiunge pacchetti e non risolve nulla.
Misurare in questo ordine
- Riprodurre il problema e annotare se si tratta di un blocco o di un rallentamento uniforme. Un blocco indica un possibile problema di MTU. Un rallentamento uniforme no.
- Eseguire il bisect con
ping -M dodal client all'indirizzo pubblico del server e annotare il path MTU. - Sottrarre 80, impostare questo valore di MTU su wg0 a entrambe le estremità e ripetere il test del trasferimento che non riusciva.
- Aggiungere il MSS clamping sul server se inoltra traffico per i peer.
- Eseguire
mpstat -P ALL 1durante un trasferimento e leggere%softe%steal. - Eseguire
iperf3fuori dal tunnel e al suo interno, in entrambe le direzioni, con un singolo stream e con-P 4. - Eseguire
mtr -rwc 100verso il server e verificare se la perdita persiste fino all'ultimo hop.
Modificare il piano del server solo dopo che il passaggio 5 ha stabilito che il limite è la CPU. I passaggi da 1 a 4 non hanno costi e risolvono la maggior parte delle segnalazioni relative a un tunnel lento.
FAQ
Perché il mio tunnel WireGuard è veloce per il ping ma lento nei download?
Questa differenza indica un problema di MTU. I pacchetti piccoli rientrano nella capacità di ogni collegamento del percorso, quindi ping e un accesso SSH funzionano. Un trasferimento consistente invia segmenti della dimensione massima. La versione incapsulata è più grande di quanto alcuni collegamenti accettino. Se il router li scarta senza che un messaggio ICMP riesca a tornare indietro, nessun componente segnala la perdita e il trasferimento si blocca. Individua la MTU del percorso con una bisezione ping -M do verso l'indirizzo pubblico del server, sottrai 80 byte per l'incapsulamento e imposta il risultato come MTU di wg0 su entrambe le estremità.
Quale MTU devo impostare per WireGuard?
Non esiste un valore universale. Per questo il valore predefinito 1420 non funziona per tutti. 1420 è il risultato di 1500 meno 80 byte per l'header WireGuard, l'header UDP e l'header IPv6 esterno. Se il percorso trasporta meno di 1500 byte, situazione normale sulle connessioni DSL PPPoE e quando il traffico attraversa un altro tunnel, devi usare un valore inferiore. Misura prima la MTU del percorso, quindi sottrai 80.
Il MSS clamping sostituisce l'impostazione della MTU?
No. Il clamping riscrive l'opzione MSS durante l'handshake TCP, così entrambe le estremità inviano segmenti più piccoli senza modificare l'interfaccia. UDP non usa un handshake da riscrivere, quindi non viene influenzato. Il clamping si applica inoltre soltanto al traffico inoltrato dal server, quindi non offre vantaggi a un servizio in esecuzione direttamente sul server WireGuard. Usa entrambi: una MTU corretta sull'interfaccia e il clamping per gestire i peer la cui configurazione non controlli.
Un piano VPS più veloce renderà WireGuard più veloce?
Solo se il limite è la CPU, e un comando consente di verificarlo. Esegui mpstat -P ALL 1 durante un trasferimento. %soft vicino al 100 sull'unico core disponibile indica che l'elaborazione dei pacchetti è il limite e che un numero maggiore di core aumenterà le prestazioni. Un valore elevato di %steal indica che l'host è sovraccarico, quindi la soluzione è scegliere un piano o un host diverso. Se entrambi i valori sono bassi mentre il tunnel rimane lento, la CPU è inattiva e un piano più grande non cambia nulla.
Perché il mio Mac è più lento del client Linux sulla stessa rete?
Il client Linux usa il modulo WireGuard integrato nel kernel. Elabora i pacchetti nello spazio del kernel e distribuisce la cifratura di un peer tra più core della CPU. Le applicazioni per macOS e iOS usano wireguard-go, un'implementazione nello spazio utente, perché queste piattaforme non consentono a un'applicazione di caricare un modulo del kernel. Lo spazio utente copia ogni pacchetto tra il kernel e l'applicazione, e questa copia riduce il throughput. La differenza è prevista e nessuna impostazione del client può eliminarla.