SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor

Headscale: come ospitare il tuo server Tailscale

Installa headscale su un VPS dal pacchetto .deb ufficiale, imposta server_url, riavvia il servizio e collega il primo nodo alla tua rete privata.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Che cos’è headscale

Headscale è un’implementazione self-hosted del server di controllo Tailscale. La macchina che coordina la rete privata è quindi un VPS di tua proprietà. È un progetto della community e non è gestito da Tailscale Inc. Ogni macchina esegue comunque il client ufficiale tailscale, indirizzato al tuo server con un’unica flag, --login-server.

Il server di controllo è il componente che stabilisce chi appartiene alla rete. Assegna a ogni nodo un indirizzo appartenente a 100.64.0.0/10, distribuisce le chiavi pubbliche e comunica ai nodi dove trovarsi a vicenda. I tunnel restano basati su WireGuard e vengono creati direttamente tra i nodi. Il traffico tra due macchine non passa attraverso il server headscale, a meno che non sia possibile creare un percorso diretto e i nodi debbano usare un relay. Gestire autonomamente questo ruolo cambia chi lo controlla, non le sue funzioni. Prima di considerare la migrazione un miglioramento della sicurezza, è quindi utile capire a quali risorse può e non può accedere un server di controllo in questo modello.

Ogni istanza di headscale gestisce un solo tailnet, cioè una sola rete Tailscale. Il progetto lo considera adatto all’uso personale o a una piccola organizzazione. Con tre o quattro macchine, una semplice VPN WireGuard su un VPS di tua proprietà richiede meno software da gestire e offre meno componenti che possono rompersi. Headscale diventa utile quando non vuoi più scrivere manualmente un blocco [Peer] per ogni nuovo laptop. Spesso il costo è il motivo iniziale per cui si valuta questa soluzione. Prima di assumere la gestione di un server, conviene quindi leggere che cosa include realmente il piano gratuito hosted, perché alcune macchine personali rientrano normalmente nei suoi limiti. Se hai già superato quella soglia, confronta il costo con quello dei piani a pagamento, calcolato per utente e non per dispositivo, perché una famiglia che usa un unico account può mantenere costi contenuti anche quando il numero di dispositivi aumenta. Se vuoi un piano di controllo self-hosted, ma preferisci usare un client proprietario e un’interfaccia web per gestire i peer invece di un sostituto diretto di Tailscale, NetBird su un singolo VPS è l’alternativa da valutare. Per un confronto più ampio tra i due modelli, consulta le differenze tra WireGuard e Tailscale.

Prerequisiti per l'installazione

  • Un VPS con Ubuntu 24.04, un indirizzo IPv4 pubblico e accesso sudo. Se il server è nuovo, consulta prima i primi dieci minuti su un nuovo VPS.
  • Un record DNS A che punti a quell'indirizzo. Questa guida usa headscale.example.com.
  • Un secondo dominio o sottodominio per MagicDNS. Questa guida usa tailnet.example.net. Non deve essere lo stesso dominio indicato in server_url.
  • Un client da aggiungere, con Linux, macOS, Windows, Android o iOS.

Installare headscale dal pacchetto .deb ufficiale

Il progetto pubblica pacchetti .deb nella pagina delle release su GitHub. A ottobre 2026 la release corrente è 0.29.4. Controlla prima la tua architettura, perché il nome del file la include.

sudo apt update
sudo apt install -y wget
dpkg --print-architecture

Su un normale VPS x86 restituisce amd64, mentre su un piano basato su Ampere o Graviton restituisce arm64. Inserisci il risultato nella variabile seguente.

HEADSCALE_VERSION="0.29.4"
HEADSCALE_ARCH="amd64"
wget --output-document=headscale.deb \\
  "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
sudo apt install -y ./headscale.deb
headscale version

Il prefisso ./ davanti al nome del file è obbligatorio. Senza di esso, apt cerca nei repository un pacchetto chiamato headscale.deb e restituisce un errore.

Il pacchetto crea un utente di sistema headscale, scrive un file di configurazione predefinito /etc/headscale/config.yaml, installa un'unità systemd, quindi abilita e avvia il servizio. Quando apt restituisce il prompt, headscale è già in esecuzione e sudo systemctl is-active headscale stampa active. Tuttavia, il servizio usa ancora la configurazione fornita dal pacchetto, che imposta server_url su http://127.0.0.1:8080. Questo indirizzo non è raggiungibile da nessun tuo client. Non è ancora esposto nulla, perché anche listen_addr fornito dal pacchetto è in ascolto sull'interfaccia loopback. Non aggiungere però alcun client finché non avrai impostato server_url e riavviato il servizio. I due passaggi successivi eseguono queste operazioni.

Configura server_url prima che si colleghi qualsiasi client

Modifica /etc/headscale/config.yaml con sudo nano /etc/headscale/config.yaml oppure applica le stesse tre modifiche con sed. Conserva una copia dell’originale, perché il file è lungo e contiene molti commenti, quindi resta il riferimento migliore per le altre impostazioni.

sudo cp /etc/headscale/config.yaml /etc/headscale/config.yaml.orig
sudo sed -i 's|^server_url:.*|server_url: https://headscale.example.com|' /etc/headscale/config.yaml
sudo sed -i 's|^listen_addr:.*|listen_addr: 127.0.0.1:8080|' /etc/headscale/config.yaml
sudo sed -i 's|^  base_domain:.*|  base_domain: tailnet.example.net|' /etc/headscale/config.yaml
sudo grep -E '^(server_url|listen_addr):|^  base_domain:' /etc/headscale/config.yaml

server_url è l’indirizzo che headscale inserisce in ogni registrazione del client. Da quel momento i client contattano sempre quella stringa esatta. Deve quindi essere il nome pubblico con https:// all’inizio, mai 127.0.0.1. La documentazione di headscale indica che è possibile usare HTTP in chiaro oppure HTTPS su una porta diversa da 443, ma raccomanda vivamente HTTPS sulla porta 443 in produzione. Il resto di questa guida presuppone questa configurazione.

listen_addr indica l’indirizzo su cui il processo resta in ascolto. Lascialo sull’interfaccia di loopback. Un reverse proxy sullo stesso server termina TLS (Transport Layer Security) e inoltra le richieste al processo. Nessun sistema esterno deve quindi raggiungere la porta 8080.

base_domain è il suffisso MagicDNS, cioè il dominio sotto il quale i nodi ricevono i propri nomi. Deve essere un nome di dominio completo senza punto finale. Deve inoltre essere diverso dal dominio indicato in server_url, altrimenti i due spazi dei nomi entrerebbero in conflitto.

Lascia invariata la sezione del database. Il valore predefinito è SQLite in /var/lib/headscale/db.sqlite, in una directory creata e gestita dal pacchetto. SQLite è sufficiente per una tailnet di queste dimensioni.

Riavviare headscale e verificare che sia in esecuzione

sudo systemctl restart headscale
sudo systemctl is-active headscale
curl -sS -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:8080/health

is-active stampa active e curl stampa 200. Il riavvio applica le modifiche. headscale legge la configurazione una sola volta, all'avvio, quindi il processo avviato dal pacchetto continua a distribuire il valore server_url incluso nel pacchetto finché non lo riavvii. sudo systemctl enable --now headscale non svolge questa operazione: il pacchetto ha già abilitato e avviato il servizio, quindi quel comando non modifica nulla e il vecchio valore resta attivo. L'avvio automatico è già configurato e sudo systemctl is-enabled headscale stampa enabled per confermarlo.

Se is-active stampa un valore diverso da active oppure curl non stampa 200, consulta il journal con sudo journalctl -u headscale -n 50 --no-pager. In questa fase, il problema è quasi sempre nel file di configurazione, perché headscale analizza l'intero file prima di aprire un socket. Un'indentazione errata o una chiave sconosciuta arresta quindi il processo prima che possa mettersi in ascolto. Correggi il file, quindi esegui sudo systemctl restart headscale. Ogni modifica successiva alla configurazione richiede lo stesso riavvio. I client si riconnettono automaticamente. Se non conosci ancora le unità systemd, eseguire autonomamente servizi e timer con systemd illustra i comandi usati in questa procedura.

Controlla i file di stato mentre hai la shell aperta:

stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.key

Entrambe le righe iniziano con headscale, l'utente senza privilegi creato dal pacchetto. noise_private.key identifica il server presso i client. Non eliminarlo. Se lo elimini, headscale ne genera uno nuovo e ogni nodo deve registrarsi nuovamente.

Configura TLS davanti a headscale

I client devono raggiungere server_url tramite HTTPS. Caddy è la soluzione più rapida, perché richiede e rinnova autonomamente il certificato.

sudo apt install -y caddy

Sostituisci /etc/caddy/Caddyfile con il blocco riportato nella documentazione di headscale:

headscale.example.com {
    reverse_proxy 127.0.0.1:8080 {
        header_up True-Client-IP {remote_host}
        header_up X-Real-IP {remote_host}
    }
}
sudo caddy validate --adapter caddyfile --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl is-active caddy

validate restituisce adapted config to JSON quando il file viene analizzato correttamente. Un avviso che segnala la mancata formattazione del file è soltanto estetico. Dal laptop, anche curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health dovrebbe restituire 200. Questo singolo controllo dimostra che DNS, firewall, certificato e proxy funzionano correttamente insieme.

Ecco il dettaglio del proxy che spesso richiede ore di troubleshooting. La connessione di controllo Tailscale usa un aggiornamento HTTP, viene avviata con una richiesta POST anziché GET e il valore dell'header Upgrade è tailscale-control-protocol. Caddy inoltra questi elementi senza configurazione aggiuntiva. nginx non lo fa, quindi un front end nginx deve inoltrare esplicitamente gli header Upgrade e Connection. La documentazione di headscale utilizza una mappa di aggiornamento:

map $http_upgrade $connection_upgrade {
    default keep-alive;
    ''      close;
}

server {
    listen 443 ssl;
    server_name headscale.example.com;
    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_buffering off;
        proxy_pass http://127.0.0.1:8080;
    }
}

Se ometti queste righe, le richieste ordinarie continuano ad avere esito positivo. Per questo /health restituisce 200 e tutto sembra funzionare, mentre la connessione di controllo persistente non viene mai stabilita. I nodi si registrano e poi restano offline. Se scegli nginx, la guida Certbot su Ubuntu 24.04 con nginx illustra la parte relativa al certificato.

Quali porte aprire in UFW

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

La porta 443 trasporta la connessione di controllo di ogni client a headscale. Se in seguito abiliti il relay DERP integrato, serve anche la porta 3478/udp, descritta di seguito. La porta 80 serve per la challenge HTTP di ACME (ambiente per la gestione automatica dei certificati) e per il redirect a HTTPS. Caddy può comunque ottenere un certificato con la porta 80 chiusa, perché quando la challenge HTTP fallisce passa alla challenge TLS-ALPN sulla porta 443. Con la porta 80 aperta, però, entrambe le challenge sono disponibili e le richieste http:// in chiaro vengono reindirizzate.

La porta 8080 resta chiusa. listen_addr è 127.0.0.1:8080, quindi il proxy raggiunge headscale tramite l'interfaccia loopback e non è necessaria alcuna regola del firewall. Aprire la porta 8080 in UFW non esporrebbe nulla, perché dall'esterno non è in ascolto alcun servizio su quella porta e non offrirebbe comunque alcun vantaggio. Tieni presente che la maggior parte dei provider gestisce un secondo firewall nel pannello di controllo, separato da UFW. Di conseguenza, una porta può essere aperta sul server e restare chiusa sul perimetro della rete. Nozioni di base sul firewall UFW su un VPS illustra in maggiore dettaglio la sintassi delle regole.

Crea un utente e una chiave preautenticata

sudo headscale users create alice
sudo headscale users list

Il comando headscale è un client. Comunica con il demone in esecuzione tramite il socket Unix /var/run/headscale/headscale.sock, che ha modalità 0770 ed è di proprietà del gruppo headscale. Questo comporta due conseguenze. Il comando non funziona se il servizio è arrestato, motivo per cui l'ordine indicato in questa guida è importante, e richiede sudo, a meno che tu non aggiunga il tuo account al gruppo headscale.

users list stampa un ID accanto a ogni nome. Ti serve quel numero, perché il comando per creare la chiave richiede un ID utente numerico e non un nome.

sudo headscale preauthkeys create --user 1 --expiration 24h

La chiave viene stampata una sola volta. Copiala subito. Una chiave preautenticata è monouso e resta valida per un'ora, salvo diversa indicazione. Per questo conviene impostare --expiration 24h durante la fase di test. Aggiungi --reusable per creare una chiave che registri più macchine. Trattala come una password, perché chiunque ne sia in possesso può unirsi alla tua rete.

Collega il primo client con --login-server

Sulla macchina che vuoi aggiungere:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server https://headscale.example.com --auth-key 'hskey-auth-PASTE-YOUR-KEY-HERE'
tailscale status
tailscale ip -4

tailscale ip -4 stampa l'indirizzo assegnato da headscale, ad esempio 100.64.0.1. Tornando sul server, sudo headscale nodes list mostra il nodo con il relativo ID, l'utente e lo stato online.

Il valore di --login-server deve corrispondere esattamente a server_url, incluso lo schema e senza slash finale. I valori vengono confrontati come stringhe. Se non corrispondono, il client si registra su un indirizzo e viene poi istruito a comunicare con un altro.

Una macchina che aveva già effettuato l'accesso al servizio hosted di Tailscale conserva quell'accesso. Esegui prima sudo tailscale logout, quindi esegui tailscale up con --login-server.

Se ometti --auth-key, il client stampa invece un URL. Aprilo: la pagina mostra l'identificatore del tentativo di registrazione, che puoi approvare sul server:

sudo headscale auth register --user alice --auth-id PASTE-THE-ID-FROM-THE-PAGE

Questa procedura è più pratica per il tuo laptop. Le chiavi di preautenticazione sono preferibili per le attività automatizzate, perché non richiedono la presenza di un operatore. Una volta che il VPS è diventato un nodo, può anche trasportare il traffico Internet delle altre macchine, cioè configurare il nodo di uscita. L'unica differenza è che devi approvare la route pubblicizzata sul server con il comando headscale, invece di farlo in una console di amministrazione hosted. Se vuoi raggiungere una rete privata situata dietro quel VPS, anziché usare il VPS come punto di uscita verso Internet, la stessa procedura di approvazione consente di pubblicizzare quella subnet al resto del tuo tailnet. Pubblicare una singola applicazione da un nodo, invece di instradare intere reti attraverso di esso, è un'attività diversa. serve e funnel sono i due metodi per farlo, ma entrambi utilizzano i certificati e il sistema di ingresso di Tailscale. Considerali quindi funzionalità del tailnet hosted, non strumenti forniti da headscale.

DERP e il componente che inoltra il traffico quando il percorso diretto non è disponibile

DERP (designated encrypted relay for packets) è il percorso di fallback. Quando due nodi non possono aprire una connessione WireGuard diretta, in genere perché si trovano entrambi dietro NAT (network address translation) restrittivi, inviano invece i pacchetti tramite un relay. Il relay non possiede chiavi, quindi non può leggere il traffico. Può però vedere quali nodi comunicano e quanti dati vengono trasferiti.

È importante capire cosa fa la configurazione predefinita. Headscale viene fornito configurato per usare https://controlplane.tailscale.com/derpmap/default con auto_update_enabled: true e update_frequency: 3h, quindi il control plane è sotto il tuo controllo, mentre i relay appartengono a Tailscale. Per la maggior parte degli utenti è un compromesso ragionevole. In caso contrario, esegui un relay autonomo.

Per eseguire un relay autonomo, imposta enabled: true sotto derp.server in config.yaml, riavvia headscale e apri la porta STUN (session traversal utilities for NAT) con sudo ufw allow 3478/udp. Il file di configurazione esplicita chiaramente il requisito: server_url deve usare https, perché DERP richiede TLS. Svuotando l'elenco derp.urls rimuovi i relay di Tailscale dalla mappa. Se lo fai senza un relay embedded funzionante, qualsiasi coppia di nodi che non riesce a connettersi direttamente non potrà connettersi in alcun modo.

Da un client, tailscale netcheck mostra la latenza verso ogni area relay conosciuta e tailscale status contrassegna ogni peer come direct con un indirizzo oppure relay con un codice area. Un peer bloccato su relay indica un problema di NAT, non un problema di headscale. Un peer che è direct ma resta lento richiede un'analisi diversa; la causa più comune è MTU, non il tunnel in sé.

Perché un nodo risulta offline?

Il proxy scarta l'upgrade. È il caso più comune. Il segnale caratteristico è che tutto il resto sembra funzionare: /health restituisce 200, headscale nodes list mostra il nodo, ma il nodo non risulta mai online. La connessione di controllo è una richiesta POST che trasporta Upgrade: tailscale-control-protocol. Un proxy che non la inoltra interrompe l'unico canale che comunica lo stato del nodo. Confronta la configurazione nginx con le direttive per l'upgrade riportate sopra oppure passa a Caddy per escludere il proxy come causa.

server_url è cambiato dopo la registrazione dei nodi. I nodi continuano a connettersi al valore ricevuto durante la registrazione. Se lo hai modificato, esegui sudo tailscale up --login-server https://headscale.example.com --force-reauth su ogni nodo.

Il client non è in esecuzione. Sul nodo, esegui sudo systemctl is-active tailscaled e sudo journalctl -u tailscaled -n 50 --no-pager. Se il client non riesce a risolvere il tuo dominio o a raggiungerlo, registra qui i tentativi di riconnessione.

La chiave è scaduta. Questo caso è descritto nella sezione successiva.

Per monitorare il lato server durante il test, esegui sudo journalctl -u headscale -f sul VPS e riavvia tailscaled sul client. Un nodo che raggiunge headscale produce immediatamente righe nei log. Se non compare nulla, la richiesta non sta arrivando. Controlla quindi DNS, firewall e proxy prima di esaminare headscale.

Scadenza delle chiavi e nodo che smette di funzionare dopo alcune settimane

Esistono due scadenze distinte. Confonderle fa perdere tempo.

Le chiavi di preautenticazione scadono rapidamente per impostazione predefinita. La durata predefinita è un'ora e una sola utilizzazione. Se tailscale up rifiuta la chiave, generatene una nuova sul server invece di modificare qualcosa sul client.

Le chiavi dei nodi sono la parte a lunga durata. La sezione node di config.yaml imposta expiry: 0 e 0 indica che non esiste alcuna scadenza predefinita: un nodo registrato rimane valido finché non lo fate scadere. I nodi con tag non scadono mai. Impostate expiry: 180d se volete che le registrazioni scadano automaticamente e valutate con attenzione la conseguenza: ogni nodo senza tag dovrà quindi eseguire sudo tailscale up --login-server https://headscale.example.com --force-reauth secondo questa pianificazione, mentre un server senza interfaccia a cui nessuno esegue nuovamente l'autenticazione verrà rimosso dalla rete automaticamente.

Eseguite questa procedura manualmente quando qualcuno perde un laptop. sudo headscale nodes list fornisce l'ID, quindi sudo headscale nodes expire -i 3 disconnette quel nodo e sudo headscale nodes delete -i 3 lo rimuove completamente dalla rete.

Backup e aggiornamenti

/var/lib/headscale e /etc/headscale insieme costituiscono l’intero server. Arrestare il servizio prima di copiarli, perché SQLite potrebbe avere operazioni di scrittura in corso e una copia del database eseguita sotto carico potrebbe risultare incoerente.

sudo systemctl stop headscale
sudo tar czf /root/headscale-state.tgz -C /var/lib headscale
sudo tar czf /root/headscale-config.tgz -C /etc headscale
sudo systemctl start headscale
sudo chmod 600 /root/headscale-*.tgz

Spostare entrambi i file fuori dal server. Contengono le chiavi private e tutte le registrazioni, quindi devono essere protetti con la stessa attenzione riservata al server. backup restic da un VPS spiega come eseguire questa operazione in modo pianificato e con cifratura.

Gli aggiornamenti ripetono la procedura di installazione: scaricare il nuovo .deb, sudo apt install ./headscale.deb, quindi riavviare ed eseguire nuovamente i controlli is-active e /health. Dalla versione 0.29 il percorso di aggiornamento è vincolante. Non è possibile saltare una versione minor e non è possibile eseguire il downgrade a una versione minor precedente. Procedere una versione minor alla volta, creare un backup prima di ogni passaggio e leggere prima le note di rilascio della versione interessata, perché quella release ha modificato il comportamento dei criteri ACL e spostato diverse chiavi di configurazione.

FAQ

headscale è già in esecuzione subito dopo l'installazione del pacchetto .deb?

Sì. Il pacchetto abilita e avvia il servizio al termine dell'installazione, usando il /etc/headscale/config.yaml incluso nel pacchetto, che resta in ascolto solo sull'interfaccia di loopback e imposta server_url su http://127.0.0.1:8080. Questo indirizzo non è utilizzabile dai client. Modifica quindi server_url, listen_addr e base_domain, poi esegui sudo systemctl restart headscale e verifica con sudo systemctl is-active headscale. Il valore deve essere restart: headscale legge il file solo all'avvio e enable --now su un servizio già in esecuzione non modifica nulla. Se il servizio non parte dopo il riavvio, sudo journalctl -u headscale -n 50 --no-pager indica il problema. In questa fase si tratta quasi sempre di un errore YAML, perché headscale analizza l'intero file prima di mettersi in ascolto su una porta.

devo installare il normale client Tailscale sui miei computer?

Sì. Headscale sostituisce solo il control server. Ogni nodo esegue il client ufficiale di Tailscale e lo configura per usare il server tramite sudo tailscale up --login-server https://headscale.example.com. Questo flag è disponibile nel client standard, quindi non è necessario applicare patch o ricompilare il software.

il traffico passa attraverso il server headscale?

Di solito no. Headscale coordina la rete e distribuisce chiavi e indirizzi, mentre il percorso dei dati usa direttamente WireGuard tra i nodi. Il traffico passa da un relay DERP solo quando due nodi non riescono a raggiungersi direttamente. Con la configurazione inclusa nel pacchetto, questi relay sono quelli pubblici di Tailscale. Esegui tailscale status su un nodo per verificare se un determinato peer è direct oppure usa un relay.

perché il mio nodo resta offline dopo la registrazione?

Un nodo che compare in headscale nodes list ma non passa mai online ha quasi sempre perso la connessione di controllo sul reverse proxy. Questa connessione usa un HTTP upgrade inviato con una richiesta POST e l'header Upgrade: tailscale-control-protocol. nginx lo elimina se non inoltri esplicitamente gli header Upgrade e Connection tramite proxy_set_header. La configurazione precedente lo fa nel relativo blocco map $http_upgrade $connection_upgrade. Caddy lo inoltra senza configurazioni aggiuntive. Per questo è un modo rapido per verificare se il problema dipende dal proxy.

servono un nome di dominio e TLS per headscale?

In pratica, sì. I client si connettono alla stringa specificata in server_url, i certificati vengono emessi per i nomi e non per gli indirizzi IP non associati a un nome e il file di configurazione indica che DERP richiede TLS. Un dominio con Caddy richiede circa cinque minuti di configurazione e fornisce un endpoint HTTPS che rinnova automaticamente il certificato. HTTP in chiaro è possibile, ma la documentazione di headscale raccomanda vivamente HTTPS sulla porta 443 in produzione. Inoltre, indica che in determinate situazioni il client Tailscale presume l'uso di HTTPS sulla porta 443.