Vai al contenuto

1. Headscale on‑prem con VPS, Cloudflare e Reverse SSH Tunnel

1.1. VPS Esterno

https://cloudserver.net/billing/index.php?rp=/login

1.2. Scopo del documento

Questo README serve come memoria tecnica permanente dell’architettura Headscale adottata. Va usato come riferimento in caso di:

  • problemi di connettività
  • migrazioni
  • ripristino dopo downtime
  • troubleshooting (Tunnel / TLS / DNS / Tailscale)

1.3. Obiettivo architetturale

  • Headscale self‑hosted on‑prem (VM Proxmox, IP dinamico)
  • Nessuna porta esposta verso Internet dalla rete domestica
  • Endpoint pubblico stabile e TLS valido
  • Compatibile con client Tailscale ufficiali (macOS, Android, Linux)

1.4. Schema logico

Text Only
[Tailscale Client]
        |
        | HTTPS (443)
        v
[Cloudflare Proxy]
        |
        | HTTPS (443) – Origin Cert
        v
[VPS pubblico]
  NGINX (443/80)
        |
        | proxy_pass → 127.0.0.1:8080
        v
[Reverse SSH Tunnel]
        |
        | SSH outbound only
        v
[VM Headscale on‑prem]
  headscale :8080

1.5. Componenti

1.5.1. VM on‑prem (Headscale)

  • Hypervisor: Proxmox
  • Rete: LAN domestica (IP dinamico)
  • Servizio:

  • headscale

  • Porta locale: 127.0.0.1:8080

Percorsi importanti:

Text Only
/etc/headscale/
/etc/systemd/system/headscale-tunnel.service
/root/.ssh/hs_tunnel

1.5.2. VPS pubblico

  • IP pubblico statico
  • DNS: headscale.enricomarogna.com
  • Servizi:

  • nginx

  • sshd

Porte aperte:

  • 80 (HTTP → redirect)
  • 443 (HTTPS)
  • 22 (SSH, solo key)

1.5.3. Cloudflare

  • DNS proxied (orange cloud)
  • Modalità SSL: Full (strict)
  • Certificati:

  • Origin Certificate Cloudflare installato su VPS


1.6. Reverse SSH Tunnel (cuore dell’architettura)

Il VPS non connette verso casa. È la VM Headscale che apre una connessione SSH outbound persistente verso il VPS.

1.6.1. Chiave SSH

Generata sulla VM Headscale:

Text Only
/root/.ssh/hs_tunnel
/root/.ssh/hs_tunnel.pub

Installata su VPS:

Text Only
/root/.ssh/authorized_keys

1.6.2. Systemd service (VM Headscale)

File:

Text Only
/etc/systemd/system/headscale-tunnel.service

Contenuto:

Text Only
[Unit]
Description=Reverse SSH tunnel for Headscale
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/lib/autossh/autossh -M 0 -N \
  -i /root/.ssh/hs_tunnel \
  -o BatchMode=yes \
  -o IdentitiesOnly=yes \
  -o StrictHostKeyChecking=yes \
  -o UserKnownHostsFile=/root/.ssh/known_hosts \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes \
  -R 127.0.0.1:8080:127.0.0.1:8080 root@<IP_VPS>
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Comandi utili:

Text Only
systemctl daemon-reload
systemctl enable --now headscale-tunnel
systemctl status headscale-tunnel
journalctl -u headscale-tunnel

1.7. NGINX (VPS)

1.7.1. VirtualHost HTTPS

Path tipico:

Text Only
/etc/nginx/sites-available/headscale
/etc/nginx/sites-enabled/headscale

Config:

Text Only
server {
  listen 443 ssl;
  server_name headscale.enricomarogna.com;

  ssl_certificate     /etc/ssl/cloudflare/headscale.crt;
  ssl_certificate_key /etc/ssl/cloudflare/headscale.key;

  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
  }
}

Test:

Text Only
nginx -t && systemctl reload nginx

1.8. Verifiche di funzionamento

1.8.1. Sul VPS

Text Only
ss -ltnp | grep 8080
curl http://127.0.0.1:8080/health

1.8.2. Da Internet

Text Only
curl https://headscale.enricomarogna.com/health

Risposta attesa:

Text Only
{"status":"pass"}

1.9. Headscale – gestione

1.9.1. Creazione Preauth Key

Text Only
headscale preauthkeys create \
  --user <USER_ID> \
  --reusable \
  --expiration 90d

1.9.2. Lista nodi

Text Only
headscale nodes list

1.10. Client Tailscale

1.10.1. Login macOS (CLI)

Text Only
tailscale up \
  --reset \
  --login-server=https://headscale.enricomarogna.com \
  --auth-key=<KEY> \
  --accept-routes

1.10.2. Stato

Text Only
tailscale status
tailscale ip

1.11. Considerazioni di sicurezza

  • Nessuna porta aperta verso la LAN domestica
  • Solo traffico outbound dalla VM on‑prem
  • Cloudflare protegge da:

  • scansioni

  • DDoS
  • esposizione diretta IP VPS
  • SSH solo con chiave

1.12. Punto chiave da ricordare

👉 Se qualcosa non funziona:

  1. verificare tunnel SSH
  2. verificare nginx sul VPS
  3. verificare Cloudflare (proxy ON)
  4. solo dopo guardare Headscale

1.13. Stato finale

✔ Headscale on‑prem ✔ Tunnel persistente ✔ TLS valido ✔ Client Tailscale ufficiali compatibili ✔ Nessuna dipendenza SaaS


Documento creato come single source of truth.

1.14. Operazioni Headscale

Questa sezione raccoglie tutti i comandi operativi di Headscale usati e da usare in futuro per la gestione quotidiana (utenti, nodi, chiavi, debug).

1.14.1. Percorsi e servizio

Bash
# binario
/usr/bin/headscale

# configurazione
/etc/headscale/config.yaml

# database (default sqlite)
/var/lib/headscale/db.sqlite

# servizio systemd
systemctl status headscale

1.14.2. Gestione utenti

Bash
# lista utenti
headscale users list

# crea nuovo utente
headscale users create NOMEUTENTE

# elimina utente
headscale users destroy NOMEUTENTE

Nota: l'utente NON è una mail reale. È solo un namespace logico (es: macosm2, laptop, server, ecc.).


1.14.3. Gestione preauth keys

Bash
# lista chiavi di un utente
headscale preauthkeys list --user NOMEUTENTE

# crea chiave riutilizzabile (90 giorni)
headscale preauthkeys create \
  --user NOMEUTENTE \
  --reusable \
  --expiration 90d

# crea chiave one-shot
headscale preauthkeys create \
  --user NOMEUTENTE \
  --expiration 24h

Output esempio:

Text Only
af9e3d8d674e3ee9eebd24ad12e798be0de4fef511fe1ff6

Sul client:

Bash
sudo tailscale up \
  --login-server=https://headscale.enricomarogna.com \
  --auth-key=CHIAVE \
  --accept-routes

1.14.4. Gestione nodi

Bash
# lista nodi
headscale nodes list

# rinomina nodo
headscale nodes rename ID_NODO nuovo-nome

# elimina nodo
headscale nodes delete ID_NODO

Campi importanti da controllare:

  • IP addresses (es: 100.64.0.x)
  • Connected (online/offline)
  • Expired

1.14.5. Debug e troubleshooting Headscale

Bash
# log runtime
journalctl -u headscale -f

# test endpoint salute
curl http://127.0.0.1:8080/health

# verifica porta locale
ss -ltnp | grep 8080

Se /health risponde {"status":"pass"} → Headscale OK.


1.14.6. Reset completo client (macOS / Linux)

Bash
sudo tailscale logout
sudo tailscale up --reset \
  --login-server=https://headscale.enricomarogna.com

1.14.7. Versioni client/server

⚠️ Warning frequente ma non bloccante:

Text Only
client version != tailscaled server version

Headscale non richiede versioni identiche, purché compatibili.