Headscale réseau maillé
Headscale vise à mettre en œuvre une alternative open source auto-hébergée au serveur de contrôle Tailscale. L’objectif de Headscale est de fournir aux auto-hébergeurs et aux amateurs un serveur open source qu’ils peuvent utiliser pour leurs projets et leurs laboratoires. Il met en œuvre une portée étroite, un réseau unique Tailscale (tailnet), adapté à un usage personnel, ou une petite organisation open source.
Headscale + DERP intégré
Procédure complète pour installer Headscale + DERP intégré sur un VPS Debian, derrière Caddy, en utilisant le nom headscale.xoyize.xyz
VPS
- IP: 164.132.198.38
- IPv6: 2001:41d0:404:200::7ec4
- Domaine xoyize.xyz, zone dns configurée headscale.xoyize.xyz
Le DERP intégré doit être déclaré avec ses deux adresses publiques pour améliorer la stabilité des connexions IPv4/IPv6.[headscale]
Architecture
On utilisera une seule URL publique :
1
https://headscale.xoyize.xyz
Répartition des ports :
| Service | Écoute locale/public | Rôle |
|---|---|---|
| Caddy | TCP 80, TCP 443 | HTTPS, certificat Let’s Encrypt, proxy Headscale et DERP |
| Headscale | 127.0.0.1:8080 |
API / serveur de coordination |
| DERP intégré | HTTPS via Caddy + UDP 3478 | Relais DERP et STUN |
| SSH | Ton port SSH non standard | Administration VPS |
Le trafic NFS ne doit jamais être ouvert sur ce VPS de coordination. Le VPS est seulement le point de contrôle/relay mesh, NFS restera accessible uniquement via l’interface mesh de ton serveur Debian.
Préparation VPS Debian
Se connecter au VPS puis mise à jour du système avec des utilitaitres :
1
2
3
4
5
6
7
8
9
sudo apt update
sudo apt full-upgrade -y
sudo apt install -y \
ca-certificates \
curl \
gnupg \
ufw \
jq \
dnsutils
Vérifier le nom DNS depuis le VPS :
1
2
dig +short A headscale.xoyize.xyz
dig +short AAAA headscale.xoyize.xyz
Résultat attendu :
1
2
164.132.198.38
2001:41d0:404:200::7ec4
Vérifier que le serveur possède bien ces IP :
1
2
3
ip -4 addr
ip -6 addr
ip -6 route
Résultat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
altname enp0s3
altname enxfa163e8b59df
inet 164.132.198.38/32 metric 100 scope global dynamic ens3
valid_lft 81962sec preferred_lft 81962sec
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:41d0:404:200::7ec4/128 scope global
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe8b:59df/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
2001:41d0:404:200::7ec4 dev ens3 proto kernel metric 256 pref medium
2001:41d0:404:200::/64 dev ens3 proto static metric 1024 pref medium
fe80::/64 dev ens3 proto kernel metric 256 pref medium
default via 2001:41d0:404:200::1 dev ens3 proto static metric 1024 pref medium
Pare-feu UFW
Avant d’activer UFW, adapter le port SSH à la configuration réelle. Ne pas copier
22si le VPS utilise un autre port.
Pour une sécurité de base, laisser le pare-feu en refus entrant par défaut
1
2
3
4
5
6
7
8
9
10
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 55038/tcp comment 'SSH administration'
sudo ufw allow 80/tcp comment 'Caddy ACME HTTP'
sudo ufw allow 443/tcp comment 'Headscale and DERP HTTPS'
sudo ufw allow 3478/udp comment 'Headscale DERP STUN'
sudo ufw enable
sudo ufw status verbose
Il faut impérativement d’avoir une session SSH fonctionnelle ouverte avant
sudo ufw enable.
Il faut ouvrir les règles en IPv4 et IPv6 à vérifier sur /etc/default/ufw
1
grep 'IPV6' /etc/default/ufw # renvoie si ok: IPV6=yes
Le DERP intégré Headscale requiert STUN ; la documentation Headscale liste STUN parmi les exigences lorsque le DERP intégré est activé.[headscale]
Installer Caddy
Installer Caddy depuis le dépôt officiel :
1
2
3
4
5
6
7
8
9
10
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
Vérification
1
2
systemctl status caddy --no-pager
caddy version # v2.11.4 h1:XKxkMTgNSizEvKG6QHue6cAsFOteU2qA61w2tKkCWi0= au 26/09/2026
Caddy gère HTTPS automatiquement lorsqu’un nom de domaine est déclaré dans le Caddyfile, et le conserve renouvelé.[caddyserver][caddyserver]
Caddy doit s’exécuter sous l’utilisateur système caddy, avec /var/lib/caddy accessible en écriture par cet utilisateur. Le mécanisme de certificats automatiques dépend de ce répertoire.[caddyserver][caddyserver]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sudo systemctl stop caddy
sudo install -d \
-o caddy \
-g caddy \
-m 0750 \
/var/lib/caddy
sudo install -d \
-o caddy \
-g caddy \
-m 0750 \
/var/lib/caddy/.config/caddy
sudo chown -R caddy:caddy /var/lib/caddy
sudo systemctl start caddy
sudo systemctl status caddy --no-pager -l
Vérifier ensuite droits et propriétaire :
1
2
3
sudo ls -ld /var/lib/caddy
sudo ls -ld /var/lib/caddy/.config
sudo ls -ld /var/lib/caddy/.config/caddy
On doit voir caddy caddy comme propriétaire et groupe
1
2
3
drwxr-x--- 4 caddy caddy 4096 26 sept. 12:12 /var/lib/caddy
drwxr-xr-x 3 caddy caddy 4096 26 sept. 12:12 /var/lib/caddy/.config
drwxr-x--- 2 caddy caddy 4096 26 sept. 12:12 /var/lib/caddy/.config/caddy
Installer Headscale
Au moment de l’installation, utiliser la dernière version stable publiée dans les releases Headscale. La documentation propose des paquets .deb pour Debian/Ubuntu.headscale
Définis d’abord la version que tu souhaites installer en consultant les releases officielles, puis remplace X.Y.Z ci-dessous :
1
2
3
4
5
6
7
HEADSCALE_VERSION="0.29.4"
curl -fsSLO \
"https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_amd64.deb"
sudo apt install -y \
"./headscale_${HEADSCALE_VERSION}_linux_amd64.deb"
Vérifier les fichiers installés :
1
2
3
headscale version
systemctl status headscale --no-pager
sudo ls -la /etc/headscale
À ce stade, arrêter Headscale pour modifier proprement sa configuration :
1
sudo systemctl stop headscale
Configurer Headscale et DERP
Sauvegarder la configuration générée par le paquet :
1
2
sudo cp -a /etc/headscale/config.yaml \
"/etc/headscale/config.yaml.bak.$(date +%F-%H%M%S)"
Édition:
1
sudo nano /etc/headscale/config.yaml
Vérifier ou remplacer les blocs pertinents. Les noms exacts de quelques paramètres peuvent varier selon la version : pars du fichier d’exemple correspondant exactement à la version installée, plutôt que d’écraser la configuration complète avec un exemple d’une autre release.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
server_url: https://headscale.xoyize.xyz
listen_addr: 127.0.0.1:8080
metrics_listen_addr: 127.0.0.1:9090
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
derp:
server:
enabled: true
region_id: 999
region_code: xoyize
region_name: Xoyize DERP
stun_listen_addr: "0.0.0.0:3478"
private_key_path: /var/lib/headscale/derp_server_private.key
automatically_add_embedded_derp_region: true
ipv4: 164.132.198.38
ipv6: 2001:41d0:404:200::7ec4
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
urls: []
paths: []
auto_update_enabled: false
Les éléments essentiels sont :
server_urldoit être exactement l’URL HTTPS publique.listen_addr: 127.0.0.1:8080évite d’exposer directement l’API Headscale.derp.server.enabled: trueactive le DERP intégré.ipv4etipv6déclarent les IP publiques réellement annoncées aux clients.stun_listen_addrrend STUN disponible sur UDP 3478.- Les listes
urlsetpathsvides, avecauto_update_enabled: false, évitent de charger les relais publics par défaut : tes nœuds utiliseront ton DERP intégré. La documentation Headscale indique que le DERP intégré est désactivé par défaut, qu’il doit être activé explicitement, et montre la déclaration des adresses publiques IPv4/IPv6.[headscale]
Créer le répertoire de données et corriger les permissions si nécessaire :
1
2
sudo install -d -o headscale -g headscale -m 0750 /var/lib/headscale
sudo chown -R headscale:headscale /var/lib/headscale
Puis démarrer et vérifier :
1
2
3
sudo systemctl enable --now headscale
sudo systemctl status headscale --no-pager -l
sudo journalctl -u headscale -n 100 --no-pager -l
Résultat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
● headscale.service - headscale coordination server for Tailscale
Loaded: loaded (/usr/lib/systemd/system/headscale.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-09-26 11:55:32 CEST; 80ms ago
Invocation: eeb5eacfe7a049cbbdb2b17506ebc794
Main PID: 5841 (headscale)
Tasks: 4 (limit: 4561)
Memory: 5.4M (peak: 5.4M)
CPU: 51ms
CGroup: /system.slice/headscale.service
└─5841 /usr/bin/headscale serve
sept. 26 11:41:22 vps-56d1c3c5 systemd[1]: Stopped headscale.service - headscale coordination server for Tailscale.
sept. 26 11:55:32 vps-56d1c3c5 systemd[1]: Started headscale.service - headscale coordination server for Tailscale.
Si le service ne démarre pas, lancer directement une validation de configuration avec la commande supportée par la release ou consulter immédiatement :
1
sudo journalctl -xeu headscale --no-pager
Configurer Caddy
Créer le Caddyfile suivant :
1
2
3
4
5
6
7
sudo tee /etc/caddy/Caddyfile >/dev/null <<'EOF'
headscale.xoyize.xyz {
encode zstd gzip
reverse_proxy 127.0.0.1:8080
}
EOF
Dans cette architecture, Caddy termine TLS sur TCP 443 et transmet les requêtes HTTP vers Headscale en local. La directive reverse_proxy est la directive Caddy prévue à cet effet.[caddyserver][caddyserver]
Valider
1
2
3
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
# si message WARN Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix...
sudo caddy fmt --overwrite --config /etc/caddy/Caddyfile
et recharger :
1
2
3
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager -l
sudo journalctl -u caddy -n 100 --no-pager -l
Résultat
1
2
3
sept. 26 12:02:40 vps-56d1c3c5 systemd[1]: Reloaded caddy.service - Caddy.
...
sept. 26 12:02:40 vps-56d1c3c5 systemd[1]: Reloaded caddy.service - Caddy.
Tester le certificat et le proxy :
1
2
curl -4 -I https://headscale.xoyize.xyz
curl -6 -I https://headscale.xoyize.xyz
Un code HTTP 404, 401 ou une réponse d’API est acceptable : ce qui compte est d’obtenir une connexion HTTPS valide et une réponse de Headscale. Une erreur de certificat, un timeout ou une connexion refusée doit être corrigé avant d’enrôler des machines.
Vérifications DERP/STUN
Depuis le VPS :
1
sudo ss -lntup | grep -E ':(80|443|3478|8080)\b'
Résultat
1
sudo ss -lntup | grep -E ':(80|443|3478|8080)\b'
Attendu :
- Caddy sur TCP 80 et 443.
- Headscale uniquement sur
127.0.0.1:8080. - DERP/STUN sur UDP 3478, IPv4 et IPv6 si le système et la configuration sont corrects.
Vérifie l’écoute UDP IPv6 explicitement :
1
2
3
4
5
udp UNCONN 0 0 *:443 *:* users:(("caddy",pid=5996,fd=7))
udp UNCONN 0 0 *:3478 *:* users:(("headscale",pid=5841,fd=11))
tcp LISTEN 0 128 127.0.0.1:8080 0.0.0.0:* users:(("headscale",pid=5841,fd=13))
tcp LISTEN 0 4096 *:80 *:* users:(("caddy",pid=5996,fd=8))
tcp LISTEN 0 4096 *:443 *:* users:(("caddy",pid=5996,fd=6))
On doit voir idéalement à la fois *:3478 *:*
Clients Tailscale
Créer utilisateur et clé inscription
Headscale a fait évoluer sa CLI ; vérifier d’abord les sous-commandes exactes delta version :
1
2
headscale users --help
headscale preauthkeys --help
Dans les versions récentes, créer un utilisateur, ici yann et yick :
1
2
3
4
5
6
7
8
9
10
11
sudo headscale users create yann
sudo headscale users create yick
sudo headscale users create yano
sudo headscale users create skim
sudo headscale users create leno
sudo headscale users create debjek
sudo headscale users create yannig
sudo headscale users create ugra
sudo headscale users create yai
sudo headscale users create yiak
Liste utilisateur:
1
sudo headscale users list
1
2
3
4
5
6
7
8
9
10
11
ID | Name | Username | Email | Created
1 | | yann | | 2026-09-26 10:27:03
2 | | yick | | 2026-09-26 12:01:00
3 | | yano | | 2026-09-26 12:35:05
4 | | skim | | 2026-09-26 13:06:49
5 | | leno | | 2026-09-26 15:55:20
6 | | debjek | | 2026-09-26 15:55:20
7 | | yannig | | 2026-09-26 15:55:21
8 | | ugra | | 2026-09-26 15:55:21
9 | | yai | | 2026-09-26 15:55:22
10 | | yiak | | 2026-09-26 15:55:22
Créer une clé d’inscription limitée dans le temps, par exemple 24 heures :
1
2
3
4
5
6
# syntaxe
sudo headscale preauthkeys create \
--user ID \
--expiration 24h
# ou
sudo headscale preauthkeys create -u 1 -e 24h
Copier la clé générée dans un gestionnaire de mots de passe, elle permettra à un appareil de rejoindre le mesh.
Pour des serveurs, créer plutôt une clé distincte à usage unique et avec un tag prédéfini — par exemple tag:server — lorsque la politique ACL sera en place. Ne pas réutiliser une clé longue durée sur plusieurs machines.
Lors de la connexion, Tailscale enregistre la machine dans le tailnet et lui attribue sa propre identité, notamment une clé de nœud. Le service peut ensuite redémarrer et se reconnecter sans réutiliser la clé d’inscription.
Vous pouvez donc :
- supprimer la clé du terminal, d’un script ou d’un gestionnaire de secrets ;
- révoquer la clé dans la console d’administration Tailscale si elle ne doit plus servir ;
- conserver une clé uniquement si elle est utilisée pour inscrire automatiquement d’autres machines.
Attention : cela ne signifie pas que la machine est définitivement indépendante de l’authentification. Selon la configuration du tailnet, elle peut devoir se réauthentifier si sa clé de nœud expire, si la machine est supprimée du tailnet ou si vous exécutez :
1
sudo tailscale logout
Dans ce dernier cas, il faudra généralement exécuter à nouveau :
1
sudo tailscale up
Pour une machine serveur, vérifiez simplement que l’état reste connecté après un redémarrage :
1
2
sudo systemctl restart tailscaled
tailscale status
Par prudence, si la clé d’inscription a été exposée dans un historique ou un script, révoquez-la même si elle est à usage unique ou déjà utilisée.
Installer application tailscale
Debian
La méthode officielle installe le dépôt Tailscale puis le paquet adapté à votre version de Debian :
1
2
3
curl -fsSL https://tailscale.com/install.sh | sh
# Activer et démarrer le service
sudo systemctl enable --now tailscaled
Arch Linux
Installer le paquet depuis les dépôts officiels :
1
2
3
sudo pacman -S tailscale
# Activer et démarrer le service
sudo systemctl enable --now tailscaled
Le client Tailscale est ensuite identique, quel que soit le serveur Headscale.
Activer le client
Sur le client, installer Tailscale, puis utiliser l’URL Headscale et la clé créée :
1
2
3
sudo tailscale up \
--login-server=https://headscale.xoyize.xyz \
--authkey=COLLER_ICI_LA_CLE
Sur le VPS Headscale, vérifier l’enregistrement :
1
sudo headscale nodes list
list
1
2
3
4
5
ID | Hostname | Name | MachineKey | NodeKey | User | Tags | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
2 | alder | alder | [3Ccdq] | [qQ/Is] | yick | | 100.64.0.2 | false | 2026-09-26 12:11:30 | N/A | online | no
| | | | | | | fd7a:115c:a1e0::2 | | | | |
3 | pc1 | pc1 | [zvVf2] | [2OpsD] | yann | | 100.64.0.3 | false | 2026-09-26 12:32:08 | N/A | online | no
| | | | | | | fd7a:115c:a1e0::3 | | | | |
Puis, côté client :
1
2
tailscale status
tailscale netcheck
1
2
3
4
5
6
7
8
9
10
11
12
100.64.0.1 ouestline yann linux -
Report:
* Time: 2026-09-26 13:43:53.886284382+02:00
* UDP: true
* IPv4: yes, 83.228.219.134:40800
* IPv6: yes, [2001:1600:18:102::c9]:44340
* MappingVariesByDestIP:
* PortMapping:
* Nearest DERP: Xoyize DERP
* DERP latency:
- xoyize: 9ms (Xoyize DERP)
Ajouter ensuite le serveur Debian NFS et un seul client Arch, avant tout le reste.
- Vérifier d’abord SSH via le nom mesh ou l’adresse overlay
- puis tester le montage NFS.
Très bien. Maintenant que le contrôle Headscale, le DERP et le HTTPS fonctionnent, la bonne suite consiste à définir le modèle de confiance avant d’inscrire le reste du parc, puis à verrouiller NFS à trois niveaux : politique mesh, pare-feu local, et export NFS.
Je te propose de séparer les rôles ainsi :
| Rôle | Tag | Machines typiques | Accès NFS |
|---|---|---|---|
| Administration | tag:admin |
Desktop/laptop Arch de confiance | SSH vers les serveurs ; éventuellement NFS si également client |
| Serveur NFS | tag:nfs-server |
Le Debian qui héberge le dossier | Reçoit TCP 2049 seulement depuis les clients NFS |
| Client NFS | tag:nfs-client |
Desktop/laptop Arch et VM Debian qui montent le partage | TCP 2049 uniquement vers le serveur NFS |
| Mobile | tag:mobile |
Android | Aucun accès NFS |
| Serveur standard | tag:server |
VPS/VM Debian sans NFS | SSH administré depuis tag:admin |
Les ACL Headscale se configurent dans un fichier de politique HuJSON dont le chemin doit être défini via policy.path dans config.yaml. Les tags sont appliqués à l’enregistrement ou administrés avec headscale nodes tag.[headscale][headscale]
Politique Headscale
Inventaire et rôles
| Alias | Système / rôle | Adresse actuelle | Tag(s) Headscale | Accès NFS |
|---|---|---|---|---|
cwwk |
Serveur Debian NFS | 192.168.0.205 |
tag:server, tag:nfs-server |
Héberge l’export |
e6230w |
Laptop CachyOS | 192.168.10.90 |
tag:admin, tag:nfs-client |
Monte l’export |
pc1 |
Desktop EndeavourOS | LAN, non listé dans CSV | tag:admin, tag:nfs-client |
Monte l’export |
proxmox |
Hôte Proxmox | 192.168.0.215 |
tag:server |
Non |
vm105 |
VM Debian | 192.168.0.229 |
tag:server |
Non initialement |
skrime |
VPS IPv6-only | 2a14:7c0:1002:19a8:: |
tag:server |
Non |
yannig |
VPS Debian | 51.38.37.240 |
tag:server |
Non |
xoyaz |
VPS Debian | 51.254.133.45 |
tag:server |
Non |
yannir |
VPS Debian | 144.91.89.149 |
tag:server |
Non |
yiak |
VPS Debian | 83.228.219.134 |
tag:server |
Non |
sxb |
VPS Headscale / DERP | 164.132.198.38, 2001:41d0:404:200::7ec4 |
Aucun, ou tag:control-plane |
Non |
| Android | Mobile | Variable | tag:mobile |
Jamais |
skrime est le test prioritaire de ton IPv6-only : ajoute-le assez tôt, après e6230w et cwwk, afin de confirmer le chemin vers Headscale et DERP sans dépendre d’IPv4.
Vérifier le nom d’utilisateur
Avant de déposer cette politique, vérifier le nom exact de l’utilisateur Headscale :
1
sudo headscale users list
Si la sortie est, par exemple :
1
2
ID | Name
1 | yann
garder :
1
"group:admin": ["yann"]
S’il est affiché autrement, utiliser ce nom exact. Ne pas mettre yann@ sans raison : Headscale attend l’identité configurée dans sa propre base, pas nécessairement une identité de fournisseur externe.
Déclarer le chemin dans Headscale
Sur le VPS Headscale, localiser la configuration et vérifier le chemin actuellement défini :
1
2
sudo grep -nE '^(policy:| *path:)' /etc/headscale/config.yaml
sudo grep -n -A5 -B2 '^policy:' /etc/headscale/config.yaml
On a un bloc de ce type :
1
2
3
4
5
6
7
8
9
10
11
220: path: /var/lib/headscale/db.sqlite
295:policy:
300: path: ""
293-# ACLs: https://tailscale.com/docs/features/access-control/acls
294-# Grants: https://tailscale.com/docs/features/access-control/grants
295:policy:
296- # The mode can be "file" or "database" that defines
297- # where the policies are stored and read from.
298- mode: file
299- # If the mode is set to "file", the path to a HuJSON file containing policies.
300- path: ""
On ajoute
1
2
policy:
path: "/etc/headscale/policy.hujson"
Créer /etc/headscale/policy.hujson sur sxb par cette politique. Elle est restrictive par défaut :
- Les postes d’administration peuvent joindre chaque serveur uniquement sur son port SSH actuel.
- Les clients NFS peuvent joindre
cwwkseulement en TCP/2049. - Android n’obtient aucune règle vers NFS.
- Aucun accès implicite « tous ports, toutes machines » n’existe.
Créer le fichier de politique avec des permissions restrictives :
1
2
3
4
sudo install -o root -g headscale -m 0640 \
/dev/null /etc/headscale/policy.hujson
sudo nano /etc/headscale/policy.hujson
La politique suivante est plus sûre et ne suppose pas que les utilisateurs des machines sont identiques. Elle donne au seul compte d’administration yann@ le droit de gérer les tags, tout en appliquant les droits réseau par rôle de machine :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
"groups": {
"group:mesh-admin": ["yann@"]
},
"tagOwners": {
"tag:admin": ["group:mesh-admin"],
"tag:server": ["group:mesh-admin"],
"tag:nfs-server": ["group:mesh-admin"],
"tag:nfs-client": ["group:mesh-admin"],
"tag:mobile": ["group:mesh-admin"],
"tag:control-plane": ["group:mesh-admin"]
},
"acls": [
// Tes deux postes d'administration, une fois marqués tag:admin,
// peuvent administrer les serveurs via leurs ports SSH existants.
{
"action": "accept",
"src": ["tag:admin"],
"dst": [
"tag:server:51002", // skrime vm9471
"tag:server:55045", // xoyaz
"tag:server:55134", // yiak
"tag:server:55149", // yannir
"tag:server:55215", // proxmox
"tag:server:55229", // vm105
"tag:server:55240", // yannig
"tag:server:56230", // e6230
"tag:nfs-server:55205" // cwwk alder
]
},
// Partage NFS : exclusivement les nœuds tag:nfs-client,
// exclusivement le nœud tag:nfs-server, exclusivement TCP/2049.
{
"action": "accept",
"src": ["tag:nfs-client"],
"dst": ["tag:nfs-server:2049"]
}
]
}
Dans cette version :
cwwkrecevra :tag:server,tag:nfs-server.e6230wrecevra :tag:admin,tag:nfs-client.pc1recevra :tag:admin,tag:nfs-client.proxmox,vm105,skrime,yannig,xoyaz,yannir,yiakreçoivent :tag:server.- Android reçoit :
tag:mobileseulement. sxbne doit pas recevoir detag:nfs-*; il peut rester non inscrit, ou être classétag:control-planesi tu installes un client Tailscale pour l’administrer, mais ce n’est pas nécessaire.
Après enregistrement :
1
2
3
sudo systemctl restart headscale
sudo systemctl status headscale --no-pager -l
sudo journalctl -u headscale -n 100 --no-pager
Si Headscale refuse de démarrer, restaurer immédiatement le fichier précédent et lire l’erreur de syntaxe dans le journal.
Préserver le service pendant les tests
Ne redémarrer pas Headscale directement après une modification non validée. La version expose les outils de politique nécessaires :
1
2
3
4
5
6
7
8
9
# Sauvegarde
sudo headscale policy get > /tmp/policy-current.hujson
sudo cp -a /etc/headscale/policy.hujson \
"/etc/headscale/policy.hujson.bak.$(date +%F-%H%M%S)"
# Modifier et vérifier
sudo headscale policy check \
--file /etc/headscale/policy.hujson
# si OK --> Policy is valid
Redémarrer
1
sudo systemctl restart headscale
Ordre d’enrôlement
N’ajoute pas les dix machines d’un coup. Cet ordre isole les problèmes réseau, ACL et NFS.
pc1: premier poste d’administration et premier client NFS.cwwk: serveur NFS.e6230w: second client NFS.skrime: VPS IPv6-only.proxmoxetvm105.- Les VPS publics restants :
yannig,xoyaz,yannir,yiak. - Android, taggé seulement
tag:mobile.
Avant de créer les clés, consulte la syntaxe exacte de ta version :
1
2
sudo headscale preauthkeys create --help
sudo headscale nodes tag --help
Inscription de PC1
Comme pc1 est un poste humain et non un serveur, il peut être enregistré puis taggé depuis Headscale. Sur sxb :
1
2
3
sudo headscale preauthkeys create \
--user 1 \ # yann --> ID=1
--expiration 1h
Sur pc1 :
1
2
3
4
5
6
sudo pacman -Syu tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up \
--login-server=https://headscale.xoyize.xyz \
--authkey=COLLE_LA_CLE
Sur sxb, liste les nœuds et relève l’ID de PC1 :
1
sudo headscale nodes list
Puis tague-le :
1
2
3
sudo headscale nodes tag \
-i ID_PC1 \
-t tag:admin,tag:nfs-client
Ces tags donnent à PC1 ses droits de SSH d’administration et de montage NFS.
Inscription de cwwk
Crée une clé à usage court, avec les tags serveur et NFS :
1
2
3
4
sudo headscale preauthkeys create \
--user yann \
--expiration 1h \
--tags tag:server,tag:nfs-server
Sur cwwk :
1
2
3
4
5
6
7
sudo apt update
sudo apt install -y tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up \
--login-server=https://headscale.xoyize.xyz \
--authkey=COLLE_LA_CLE
Puis, sur sxb :
1
sudo headscale nodes list
Tu dois voir cwwk avec les tags tag:server et tag:nfs-server. Sinon :
1
2
3
sudo headscale nodes tag \
-i ID_CWWK \
-t tag:server,tag:nfs-server
Ne lance pas --advertise-tags en plus si la clé porte déjà ces tags : cela évite la confusion.
Inscription de e6230w
Sur sxb :
1
2
3
4
sudo headscale preauthkeys create \
--user yann \
--expiration 1h \
--tags tag:admin,tag:nfs-client
Sur le laptop CachyOS :
1
2
3
4
5
6
sudo pacman -Syu tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up \
--login-server=https://headscale.xoyize.xyz \
--authkey=COLLE_LA_CLE
Puis valide :
1
2
3
tailscale status
tailscale netcheck
tailscale ping cwwk
Sur le VPS :
1
sudo headscale nodes list
Inscription de skrime IPv6-only
Cette machine est importante parce qu’elle valide l’objectif qui t’avait bloqué avec Nebula.
Sur sxb, crée une clé serveur :
1
2
3
4
sudo headscale preauthkeys create \
--user yann \
--expiration 1h \
--tags tag:server
Sur skrime :
1
2
3
4
5
6
7
8
9
10
11
12
13
getent ahosts headscale.xoyize.xyz
curl -6 -I https://headscale.xoyize.xyz
sudo apt update
sudo apt install -y tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up \
--login-server=https://headscale.xoyize.xyz \
--authkey=COLLE_LA_CLE
tailscale netcheck
tailscale status
La commande getent ahosts doit au minimum faire apparaître l’IPv6 2001:41d0:404:200::7ec4, et curl -6 doit réussir avant même l’installation du client. tailscale netcheck indiquera ensuite si UDP et IPv6 sont utilisables depuis ce VPS.
Serveurs restants
Pour chaque serveur Debian, crée une clé courte et taggée tag:server :
1
2
3
4
sudo headscale preauthkeys create \
--user yann \
--expiration 1h \
--tags tag:server
Puis, sur chaque serveur :
1
2
3
4
5
6
7
sudo apt update
sudo apt install -y tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up \
--login-server=https://headscale.xoyize.xyz \
--authkey=COLLE_LA_CLE
Répète avec une nouvelle clé pour chaque machine. À la fin de chaque enrôlement :
1
sudo headscale nodes list
Tu pourras distinguer les hôtes par leurs noms Tailscale ; ajuste le hostname Linux avant l’inscription si nécessaire :
1
sudo hostnamectl set-hostname vm105
Fais-le avant tailscale up, ou réinscris/renomme proprement le nœud s’il apparaît sous un nom inutilisable.
Attribution manuelle des tags
Après qu’une machine est enregistrée avec son utilisateur correspondant, relèver son ID :
1
sudo headscale nodes list
Puis appliquer son ou ses tags depuis le VPS.
Pour cwwk :
1
2
3
sudo headscale nodes tag \
-i ID_CWWK \
-t tag:server,tag:nfs-server
Pour e6230w :
1
2
3
sudo headscale nodes tag \
-i ID_E6230W \
-t tag:admin,tag:nfs-client
Pour pc1 :
1
2
3
sudo headscale nodes tag \
-i ID_PC1 \
-t tag:admin,tag:nfs-client
Pour chacun des autres serveurs :
1
2
3
sudo headscale nodes tag \
-i ID_SERVEUR \
-t tag:server
Pour Android :
1
2
3
sudo headscale nodes tag \
-i ID_ANDROID \
-t tag:mobile
Puis contrôle le résultat :
1
sudo headscale nodes list
Ne jamais donner le tag
tag:nfs-clientà Android,skrime, aux VPS publics ou au VPS Headscale/DERP.
Verrouiller NFS sur cwwk
Adresses IP sur cwwk
1
2
3
4
5
6
7
8
23: tailscale0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1280 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 100.64.0.2/32 scope global tailscale0
valid_lft forever preferred_lft forever
inet6 fd7a:115c:a1e0::2/128 scope global
valid_lft forever preferred_lft forever
inet6 fe80::f61e:fceb:e27b:233f/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
Lorsque pc1, cwwk et e6230w apparaissent dans headscale nodes list
1
2
3
4
5
6
7
8
ID | Hostname | Name | MachineKey | NodeKey | User | Tags | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
2 | alder | alder | [3Ccdq] | [qQ/Is] | tagged-devices | tag:nfs-server | 100.64.0.2 | false | 2026-09-26 14:29:55 | N/A | online | no
| | | | | | tag:server | fd7a:115c:a1e0::2 | | | | |
3 | pc1 | pc1 | [zvVf2] | [2OpsD] | tagged-devices | tag:admin | 100.64.0.3 | false | 2026-09-26 14:29:54 | N/A | online | no
| | | | | | tag:nfs-client | fd7a:115c:a1e0::3 | | | | |
4 | e6230 | e6230 | [fwlWZ] | [ZHTpC] | tagged-devices | tag:admin | 100.64.0.4 | false | 2026-09-26 14:29:54 | N/A | online | no
| | | | | | tag:nfs-client | fd7a:115c:a1e0::4 | | | | |
5 | vm9471 | vm9471 | [LmbOQ] | [PonWl] | tagged-devices | tag:server | 100.64.0.5 | false | 2026-09-26 14:29:54 | N/A | online | no
relèver les IP mesh de pc1 et e6230w
1
2
3
cwwk 100.64.0.2
pc1 100.64.0.3
e6230w 100.64.0.4
Sur cwwk, dans /etc/exports :
1
2
3
/sharenfs \
100.64.0.3(rw,sync,no_subtree_check,root_squash,sec=sys) \
100.64.0.4(rw,sync,no_subtree_check,root_squash,sec=sys)
Appliquer :
1
2
sudo exportfs -rav
sudo exportfs -v
Puis protèger le port NFS localement :
1
2
3
4
5
6
7
sudo ufw allow in on tailscale0 to any port 2049 proto tcp \
comment 'NFSv4 private mesh only'
sudo ufw deny in 2049/tcp \
comment 'Block NFS outside mesh'
sudo ufw status numbered
Status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Status: active
To Action From
-- ------ ----
443 ALLOW Anywhere
DNS ALLOW Anywhere
55205/tcp ALLOW Anywhere
Anywhere ALLOW 192.168.0.0/24
Anywhere ALLOW 192.168.10.0/24
Anywhere DENY 132.196.69.70
2049/tcp on tailscale0 ALLOW Anywhere # NFSv4 private mesh only
2049/tcp DENY Anywhere # Block NFS outside mesh
443 (v6) ALLOW Anywhere (v6)
DNS (v6) ALLOW Anywhere (v6)
55205/tcp (v6) ALLOW Anywhere (v6)
2049/tcp (v6) on tailscale0 ALLOW Anywhere (v6) # NFSv4 private mesh only
2049/tcp (v6) DENY Anywhere (v6) # Block NFS outside mesh
NFS est donc protégé par :
- L’absence d’exposition Internet volontaire.
- L’ACL Headscale : seuls les nœuds avec
tag:nfs-clientatteignent TCP/2049. - Le pare-feu cwwk : TCP/2049 accepté seulement par
tailscale0. /etc/exports: seules les IP mesh de PC1 et e6230w peuvent monter l’export.root_squash: root sur un client ne devient pas root surcwwk.
/etc/exports contrôle explicitement quels clients peuvent monter un chemin, et root_squash fait partie des protections NFS usuelles.[man7][man7]
SSH - migration vers les IP mesh
Crée progressivement des entrées parallèles dans ton fichier ~/.ssh_servers_private, sans supprimer les endpoints actuels tant que le mesh est en validation.
Exemple, après avoir relevé les IP mesh :
1
2
3
Alias;User;IPouDomain;Port_SSH;SSH_Key;Local;Distant;Ping;Opt;IP4_6
cwwk-mesh;yick;100.64.0.2;55205;yick-ed25519;;;Y;;4
skrime-mesh;skim;fd7a:115c:a1e0::1234;51002;skrime-ed25519;;;Y;;6
Tu peux aussi utiliser les noms DNS Headscale si MagicDNS fonctionne correctement, mais les IP mesh sont le meilleur point de départ pour isoler les problèmes de DNS, ACL ou SSH.
Vérification finale
Depuis pc1 :
1
2
3
4
# IP alder= 160.664.0.2
tailscale status
tailscale ping alder
nc -vz 160.664.0.2 2049
Depuis skrime, Android ou un VPS tel que xoyaz :
1
nc -vz 160.664.0.2 2049
Puis monter dans alder cwwk :
1
2
3
4
5
sudo mkdir -p /sharenfs
sudo mount -t nfs4 \
-o vers=4.2,proto=tcp,sec=sys \
160.664.0.2:/sharenfs \
/sharenfs
Eléments critiques :
1
2
3
4
/etc/headscale/
/var/lib/headscale/
/etc/caddy/
/var/lib/caddy/
Le VPS
sxbne doit pas rejoindre le partage NFS et ne doit jamais exposer, monter ni relayer NFS. Il reste limité aux fonctions Headscale, DERP, STUN, Caddy et administration SSH.
