Cybersécurité

Déployer Headscale sur VPS avec Caddy et HTTPS : tutoriel complet 2026

11 min de lecture

📍 Article principal de la série : Headscale 2026 : guide pratique. Lisez le guide général pour la vue d’ensemble.

Trente minutes pour passer d’un VPS vide à un Headscale de production avec HTTPS valide, prêt à accepter vos premiers clients Tailscale. Ce tutoriel détaille la procédure exacte sur Hetzner CX23 (4,51 €/mois) avec Caddy comme reverse proxy. Méthode validée chez plusieurs PME à Dakar, Abidjan, Lomé, et Ouagadougou.

Prérequis

  • VPS Hetzner CX23 ou OVH VPS-1 (Debian 12 ou Ubuntu 22.04 LTS).
  • Un nom de domaine avec enregistrement DNS A : headscale.votre-entreprise.com → IP du VPS.
  • Accès SSH root.
  • Niveau : intermédiaire (Linux, systemd, fichiers YAML).
  • Temps estimé : 30 à 45 minutes.

Étape 1 — Préparer le VPS

Mises à jour système et firewall :

apt update && apt upgrade -y
apt install -y ufw fail2ban
ufw allow 22 80 443
ufw allow 41641/udp  # DERP / WireGuard
ufw enable

Étape 2 — Installer Headscale via dépôt officiel

curl -fsSL https://github.com/juanfont/headscale/releases/download/v0.23.0/headscale_0.23.0_linux_amd64.deb -o /tmp/headscale.deb
dpkg -i /tmp/headscale.deb
systemctl enable headscale
mkdir -p /etc/headscale /var/lib/headscale

Étape 3 — Configuration de base

Éditer /etc/headscale/config.yaml :

server_url: https://headscale.votre-entreprise.com
listen_addr: 127.0.0.1:8080
metrics_listen_addr: 127.0.0.1:9090
private_key_path: /var/lib/headscale/private.key
noise:
  private_key_path: /var/lib/headscale/noise_private.key

prefixes:
  v4: 100.64.0.0/10
  v6: fd7a:115c:a1e0::/48

derp:
  server:
    enabled: false
  urls:
    - https://controlplane.tailscale.com/derpmap/default

database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite

log:
  level: info

dns:
  magic_dns: true
  base_domain: tailnet.votre-entreprise.com
  nameservers:
    global:
      - 1.1.1.1
      - 9.9.9.9

Étape 4 — Configurer Caddy pour HTTPS

apt install -y caddy

Éditer /etc/caddy/Caddyfile :

headscale.votre-entreprise.com {
  reverse_proxy 127.0.0.1:8080
  encode gzip
  
  log {
    output file /var/log/caddy/headscale.log {
      roll_size 100MB
      roll_keep 7
    }
  }
}

Recharger Caddy : systemctl reload caddy. Test : curl -I https://headscale.votre-entreprise.com/health. Doit retourner 200 OK.

Étape 5 — Démarrer Headscale

systemctl start headscale
systemctl status headscale

Logs : journalctl -u headscale -f.

Étape 6 — Créer le premier utilisateur et la première preauth key

headscale users create amadou
headscale preauthkeys create --user amadou --expiration 24h --reusable

La sortie contient une clé du type 1234567890abcdef.... Notez-la, elle expire dans 24h.

Étape 7 — Connecter le premier appareil

Sur un poste Linux :

curl -fsSL https://tailscale.com/install.sh | sh
tailscale up --login-server=https://headscale.votre-entreprise.com --auth-key=PREAUTH_KEY

Sur Mac : ouvrir Terminal, brew install --cask tailscale, puis lancer l’app, faire option-clic sur l’icône en haut à droite, choisir « Use Custom Coordination Server », saisir l’URL, puis Login.

Sur iOS / Android : ouvrir Settings dans l’app Tailscale officielle, faire 5 taps sur la version pour activer le mode debug, modifier l’URL du coordination server.

Étape 8 — Vérification

headscale nodes list

Vous voyez votre premier appareil enregistré, avec son IP attribuée (typiquement 100.64.0.1). Test de ping entre deux appareils :

tailscale ping autre-appareil
# Pong from autre-appareil via direct connection

Étape 9 — Configuration OIDC (optionnel mais recommandé)

Pour brancher Authelia ou Authentik comme fournisseur d’identité, ajouter dans config.yaml :

oidc:
  issuer: https://auth.votre-entreprise.com
  client_id: headscale
  client_secret: votre-secret
  scope: ["openid", "profile", "email"]
  extra_params:
    domain_hint: votre-entreprise.com
  allowed_users:
    - amadou@votre-entreprise.com

Erreurs fréquentes

Erreur Cause Solution
Client refuse de s’enregistrer URL serveur incorrecte Vérifier server_url et listen_addr
Pas de connectivité après registration Port UDP 41641 bloqué UFW : ufw allow 41641/udp
MagicDNS ne résout pas Base domain non configuré Définir base_domain dans config + restart
Mobile ne trouve pas l’option custom server Mode dev non activé 5 taps sur version dans Settings
Caddy 502 Bad Gateway Headscale non démarré systemctl status headscale
Préauth key expirée Délai 24h dépassé Régénérer avec --expiration 168h (7j)

Ce qui dévie dans la pratique locale

Trois ajustements pour une PME africaine. Choix du DERP relay : par défaut Headscale utilise les DERP de Tailscale (US et Europe). Pour optimiser la latence depuis l’Afrique de l’Ouest, vous pouvez auto-héberger un DERP sur un VPS Africa Data Centres (Lagos ou Le Cap) — option avancée. Pour la majorité, les DERP Frankfurt/Paris suffisent. Coût total annuel : 54 €/an de VPS + 12 €/an de domaine = 66 € pour une équipe illimitée. Gestion des employés temporaires : freelances et stagiaires sont fréquents en Afrique de l’Ouest. Headscale + preauth keys à durée limitée (24h, 7j, 30j) gèrent élégamment l’onboarding-offboarding.

Articles connexes

FAQ

Quelle différence entre Tailscale et Headscale au quotidien ? Aucune côté utilisateur. Les clients officiels Tailscale fonctionnent identiquement avec Headscale, l’interface graphique et les commandes CLI sont les mêmes.

Combien de temps prend la migration de Tailscale Cloud ? Pour 30 appareils, comptez une demi-journée pour scripts, une demi-journée pour la bascule. Voir tutoriel dédié.

Headscale supporte-t-il l’IPv6 ? Oui, dual-stack par défaut. Les peers obtiennent une IPv4 dans 100.64.0.0/10 et une IPv6 dans la plage configurée.

Comment monitorer Headscale ? Endpoint /metrics Prometheus, ingestion Grafana. Alertes sur nombre de nodes hors ligne, sur taille SQLite, sur erreurs API.

SQLite suffit pour combien d’appareils ? Jusqu’à 500 appareils confortablement. Au-delà, basculer sur PostgreSQL via database.type: postgres.

Dans la continuité

Pour appliquer ce tutoriel sur un vrai serveur

Hostinger reste l’option la plus simple pour démarrer. Lien partenaire — votre achat soutient le blog sans surcoût.

Réserver un VPS Hostinger →

Lien d affiliation. Si vous achetez via ce lien, le blog reçoit une petite commission sans surcoût pour vous.

Étape 1 — Pourquoi Headscale plutôt que Tailscale officiel pour une PME africaine

Tailscale est une solution VPN mesh excellente, mais son plan gratuit est limité à 3 utilisateurs et la version Business commence à 6 USD/utilisateur/mois (environ 3 950 FCFA/mois et par utilisateur en janvier 2026). Pour une équipe de 15 personnes à Dakar ou Cotonou, la facture annuelle dépasse 700 000 FCFA. Headscale, le coordinateur libre développé par Juan Font, fait exactement la même chose, gratuitement, sur votre propre VPS.

Avantage second : la souveraineté. Si votre équipe transporte des données clients soumises à la loi sénégalaise 2008-12 ou aux textes équivalents en Côte d’Ivoire, héberger le coordinateur à Dakar (sur un VPS Sonatel, Atlas, ou un OVH/Scaleway en Europe selon votre choix) limite la surface juridique aux seuls textes africains et européens.

Étape 2 — Préparer le VPS Ubuntu 24.04 LTS

Provisionnez un VPS basique : 1 vCPU, 1 Go RAM, 20 Go SSD suffisent pour 50 utilisateurs. À partir de janvier 2026, Ubuntu 24.04 LTS « Noble Numbat » est la cible recommandée (support jusqu’en avril 2029).

ssh root@vps.exemple.sn
apt update && apt upgrade -y
apt install -y ufw curl gnupg ca-certificates
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 3478/udp   # STUN, traversée NAT
ufw enable

Sortie attendue : Firewall is active and enabled on system startup. Vérifiez que vous gardez bien votre session SSH active avant de fermer le terminal — si vous oubliez d’autoriser le port 22, vous serez bloqué dehors.

Étape 3 — Installer Headscale en binaire officiel

La version stable courante en janvier 2026 est Headscale 0.26.x (vérifiez sur github.com/juanfont/headscale/releases avant d’exécuter — le numéro évolue). Téléchargez le .deb officiel, c’est le moyen le plus propre.

HS_VER=0.26.1
ARCH=amd64
curl -L -o /tmp/headscale.deb \
  https://github.com/juanfont/headscale/releases/download/v${HS_VER}/headscale_${HS_VER}_linux_${ARCH}.deb
dpkg -i /tmp/headscale.deb
systemctl status headscale --no-pager

Ce que vous devez voir : Active: active (running). Le service tourne, mais sur le port 8080 en HTTP — il faut maintenant le mettre derrière Caddy en HTTPS.

Étape 4 — Configurer Headscale (server_url et bases)

Éditez /etc/headscale/config.yaml. Les trois champs critiques sont server_url (URL publique HTTPS), listen_addr (interne 127.0.0.1) et noise.private_key_path (généré automatiquement au premier démarrage).

# /etc/headscale/config.yaml — extrait
server_url: https://hs.exemple.sn
listen_addr: 127.0.0.1:8080
metrics_listen_addr: 127.0.0.1:9090
private_key_path: /var/lib/headscale/private.key
noise:
  private_key_path: /var/lib/headscale/noise_private.key
database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite
ip_prefixes:
  - 100.64.0.0/10
  - fd7a:115c:a1e0::/48
systemctl restart headscale
journalctl -u headscale -n 30 --no-pager

Cherchez la ligne listening and serving HTTP on 127.0.0.1:8080. Si vous voyez une erreur de DB, vérifiez que /var/lib/headscale appartient à l’utilisateur headscale.

Étape 5 — Pointer le DNS et installer Caddy

Chez votre registrar (Sonatel pour .sn, Africa Registry, OVH, etc.), créez un enregistrement A : hs.exemple.sn → IP publique du VPS. Attendez 2–5 minutes la propagation, vérifiez avec dig hs.exemple.sn +short.

apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
  gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
  tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install -y caddy
caddy version   # doit afficher v2.8.x ou supérieur

Ce que vous devez voir : v2.8.4 h1:.... Caddy gère automatiquement les certificats Let’s Encrypt — c’est exactement pourquoi on le préfère à Nginx pour ce cas.

Étape 6 — Caddyfile reverse proxy avec HTTPS automatique

Remplacez /etc/caddy/Caddyfile par :

hs.exemple.sn {
    encode zstd gzip
    reverse_proxy 127.0.0.1:8080

    log {
        output file /var/log/caddy/headscale.log
        format json
    }
}
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy
journalctl -u caddy -n 20 --no-pager

Cherchez certificate obtained successfully et serving initial configuration. Testez curl -I https://hs.exemple.sn/health — vous devez recevoir HTTP/2 200 et un certificat Let’s Encrypt valide ~ 90 jours, renouvelé automatiquement par Caddy.

Étape 7 — Créer un utilisateur et générer une pre-auth key

L’utilisateur Headscale est un namespace logique (souvent un nom d’équipe). Une pre-auth key permet d’enrôler une machine sans interaction navigateur — pratique pour les VPS et serveurs sans GUI.

headscale users create equipe-dakar
headscale users list
# Sortie attendue :
# ID | Name         | Created
# 1  | equipe-dakar | 2026-01-15 10:32:11

headscale preauthkeys create --user equipe-dakar --reusable --expiration 24h
# Sortie : la clé apparaît, copiez-la (elle ressemble à 7b1a...c2)

Stockez cette clé dans un gestionnaire de mots de passe (Bitwarden self-hosted, KeePass) — ne la collez jamais dans Slack ou WhatsApp. Une pre-auth key qui fuit, c’est un accès complet au mesh.

Étape 8 — Connecter le premier client (Linux puis téléphone)

Sur un poste Linux à Abidjan :

curl -fsSL https://tailscale.com/install.sh | sh
tailscale up --login-server=https://hs.exemple.sn \
  --auth-key=tskey-auth-XXXX
tailscale status
# Sortie : la machine apparaît avec une IP 100.x.y.z

Sur Android/iOS, installez l’app officielle Tailscale, puis dans Paramètres → Comptes → utilisez « Use a custom coordination server » et collez https://hs.exemple.sn. Connectez-vous avec la pre-auth key. La machine reçoit son IP 100.64/10 et peut pinger les autres nœuds du mesh.

# Tester la connectivité depuis Dakar vers Cotonou
ping -c 3 100.64.0.2
# 64 bytes from 100.64.0.2: icmp_seq=1 ttl=64 time=42.1 ms

42 ms entre Dakar et Cotonou via votre VPS de coordination, c’est typique. Le trafic transite directement entre les nœuds (peer-to-peer), pas via le VPS — Headscale ne fait que la coordination des clés.

Étape 9 — Backup, monitoring et coûts

La SQLite Headscale est un fichier unique : sauvegardez-la chaque nuit avec un cron + rclone vers un S3 compatible (Wasabi, Backblaze B2, ou un MinIO auto-hébergé).

# /etc/cron.daily/backup-headscale
#!/bin/bash
DATE=$(date +%Y%m%d)
sqlite3 /var/lib/headscale/db.sqlite ".backup /tmp/hs-$DATE.db"
rclone copy /tmp/hs-$DATE.db wasabi:backups-headscale/
rm /tmp/hs-$DATE.db

Coût mensuel total typique : VPS 1 vCPU/1 Go ≈ 3 500–6 000 FCFA, domaine .sn ≈ 1 250 FCFA/mois lissé, stockage backup ≈ 500 FCFA. Total ≈ 6 000 FCFA/mois pour servir 50 utilisateurs, contre 200 000 FCFA/mois en Tailscale Business — économies annuelles environ 2,3 millions FCFA.

À lire ensuite, lisez notre tutoriel Astro 5 si vous voulez héberger un portail interne ultra-rapide accessible uniquement via le mesh Headscale, et notre guide LinkedIn B2B pour vendre cette prestation d’auto-hébergement aux PME de votre ville.

Étape 10 — ACL et politique d’accès par groupe

Par défaut, tous les nœuds peuvent se parler. Pour un usage entreprise, vous voulez restreindre : les commerciaux n’ont pas besoin d’accéder au serveur de base de données, les développeurs n’ont pas besoin de la GPO RH. Headscale supporte un fichier de politique JSON-like inspiré de Tailscale.

# /etc/headscale/policy.hujson
{
  "groups": {
    "group:dev":     ["alice@equipe-dakar", "moussa@equipe-dakar"],
    "group:sales":   ["fatou@equipe-dakar"],
    "group:admin":   ["mamadou@equipe-dakar"]
  },
  "acls": [
    { "action": "accept", "src": ["group:admin"], "dst": ["*:*"] },
    { "action": "accept", "src": ["group:dev"],   "dst": ["tag:db:5432","tag:web:443"] },
    { "action": "accept", "src": ["group:sales"], "dst": ["tag:crm:443"] }
  ]
}
headscale policy set -f /etc/headscale/policy.hujson
headscale policy check -f /etc/headscale/policy.hujson

Résultat type : policy is valid. Toute erreur de syntaxe affiche la ligne fautive — corrigez avant de pousser, sinon le mesh continue avec l’ancienne politique. Ce mécanisme évite la fuite latérale entre équipes : un commercial dont le poste est compromis ne peut pas atteindre la base de données.

Partager