SSD Nodes Learn Hosting plans →
Guides Matt ConnorPar Matt Connor

Tailscale SSH sur un VPS : fermer le port 22 public

Activez Tailscale SSH sur votre VPS, écrivez la règle ssh de la policy (accept, check, root), puis fermez le port 22 public avec ufw sans vous enfermer dehors.

Ce que vous obtenez à la fin

Avec Tailscale SSH, le port 22 de votre VPS n'est plus ouvert sur Internet. Vous tapez toujours ssh root@vps depuis votre portable, mais la connexion passe par le tailnet, et c'est tailscaled lui-même qui répond sur le port 22 de l'adresse Tailscale du serveur. Qui a le droit d'entrer, et sous quel compte Unix, se décide dans la policy du tailnet, pas dans sshd_config. Le pare-feu ufw ferme ensuite le port 22 public, et la console de secours de votre fournisseur reste votre porte de sortie si quelque chose casse.

Ce guide s'adresse à quelqu'un qui a déjà Tailscale sur son portable et qui loue un VPS (serveur privé virtuel) chez OVHcloud, Scaleway, Hetzner ou SSD Nodes, sous Ubuntu 24.04. Il ne traite que de l'accès administrateur au serveur. Publier un service web à travers le tailnet est un autre sujet.

Tailscale SSH n'est pas « Tailscale, puis ssh »

Beaucoup de gens font déjà ssh root@100.x.y.z à travers le tunnel Tailscale. Dans ce cas, le paquet sort du tunnel WireGuard sur l'interface tailscale0, traverse le noyau, et arrive dans sshd, l'OpenSSH classique. Vos clés, votre sshd_config, fail2ban et PAM s'appliquent comme d'habitude. Cela fonctionne, mais vous gardez deux systèmes d'authentification à entretenir : les comptes Tailscale d'un côté, les clés SSH de l'autre.

Tailscale SSH est une fonction distincte. Quand elle est activée sur le serveur, tailscaled intercepte lui-même les connexions TCP vers le port 22 de l'IP Tailscale du serveur, avant qu'elles n'atteignent le noyau. Le paquet n'est jamais écrit sur tailscale0. Il ne traverse ni netfilter, ni ufw, ni sshd, ni PAM. L'authentification, c'est l'identité Tailscale du pair qui se connecte, prouvée par la clé WireGuard de son nœud. Il n'y a pas de clé SSH à distribuer, et pas de mot de passe.

Vous pouvez le constater depuis votre portable, une fois la suite du guide appliquée :

ssh -v root@vps 2>&1 | grep 'remote software version'

Face à un OpenSSH classique, la ligne dit remote software version OpenSSH_9.6p1 suivi de la version du paquet Ubuntu. Face à Tailscale SSH, elle dit remote software version Tailscale. C'est la preuve que ce n'est plus sshd qui vous parle.

Installer Tailscale depuis le dépôt pkgs.tailscale.com

Sur un serveur, préférez le dépôt apt officiel de Tailscale au script curl | sh. Le dépôt s'appelle pkgs.tailscale.com, il est signé, et apt upgrade suivra ensuite les mises à jour avec le reste du système. Les lignes ci-dessous sont celles que Tailscale publie pour Ubuntu 24.04 (Noble) en septembre 2026. La même page donne l'équivalent pour Debian, Rocky ou Fedora.

curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt-get update && sudo apt-get install -y tailscale

Le paquet installe le service tailscaled et l'active au démarrage. Vérifiez-le tout de suite :

systemctl is-enabled tailscaled
systemctl is-active tailscaled

Les deux commandes doivent répondre enabled puis active. Sans enabled, le tunnel ne reviendrait pas après un redémarrage, et vous perdriez l'accès au serveur au premier apt upgrade qui change le noyau.

Activer Tailscale SSH : tailscale up --ssh

sudo tailscale up --ssh

La commande affiche To authenticate, visit: suivi d'une URL https://login.tailscale.com/a/.... Ouvrez-la depuis votre portable et connectez-vous avec le compte qui possède déjà votre portable. Le VPS apparaît dans le tailnet. Le flag --ssh fait deux choses : tailscaled génère ses propres clés d'hôte SSH, et il annonce au plan de contrôle qu'il accepte des sessions SSH selon la policy.

Si le serveur était déjà connecté avec d'autres options (--advertise-routes, par exemple), tailscale up --ssh refuse avec changing settings via 'tailscale up' requires mentioning all non-default flags. C'est parce que tailscale up remplace l'ensemble des préférences, pas une seule. Utilisez alors la forme qui ne touche qu'un réglage :

sudo tailscale set --ssh

Vérifiez que le réglage est pris, puis regardez qui est visible :

sudo tailscale debug prefs | grep RunSSH
tailscale status

La première commande doit afficher "RunSSH": true. La seconde liste le serveur en première ligne, puis votre portable avec active; direct ou active; relay. Le nom affiché est celui de la machine dans le tailnet, par défaut le hostname du VPS. Les images des fournisseurs donnent souvent un nom peu parlant comme vps-1a2b3c4d, et sudo tailscale set --hostname vps-paris le remplace. Puis testez depuis le portable :

ssh root@vps-paris

Le nom est résolu par MagicDNS, le DNS interne du tailnet. Si MagicDNS est désactivé, utilisez l'IP 100.x.y.z que tailscale status affiche.

Un piège à la première connexion : si vous aviez déjà fait ssh root@100.x.y.z vers l'OpenSSH classique, votre client refuse avec WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!. C'est attendu. La clé d'hôte est maintenant celle générée par Tailscale, pas celle de /etc/ssh/ssh_host_ed25519_key. Effacez l'ancienne entrée avec ssh-keygen -R 100.x.y.z (et ssh-keygen -R vps-paris si vous utilisiez le nom), puis reconnectez-vous.

La section ssh de la policy : accept, check et checkPeriod

La connexion ne s'ouvre que si la policy du tailnet le permet deux fois. D'abord dans acls, comme n'importe quel flux réseau vers le port 22. Ensuite dans la section ssh, qui ne connaît pas les ports (le 22 est implicite) mais connaît les comptes Unix. Ouvrez la console d'administration, onglet Access Controls. Un tailnet neuf contient cette règle :

"ssh": [
  {
    "action": "check",
    "src": ["autogroup:member"],
    "dst": ["autogroup:self"],
    "users": ["autogroup:nonroot", "root"]
  }
]

Lisez-la mot par mot, parce que chaque champ décide de quelque chose.

src vaut autogroup:member : tout utilisateur humain du tailnet. dst vaut autogroup:self : uniquement les machines qui appartiennent au même utilisateur que la source. Si votre VPS est enregistré avec votre compte, votre portable peut s'y connecter. Un collègue ne le peut pas, même avec cette règle. Une machine taguée (tag:vps) n'a pas de propriétaire, donc autogroup:self ne la couvre jamais : il faut une règle qui nomme le tag.

users est la liste des comptes Unix que la source a le droit de demander. autogroup:nonroot autorise n'importe quel utilisateur sauf root. root doit être ajouté explicitement, et la règle par défaut le fait. Le compte doit exister sur le serveur, car Tailscale SSH ne crée pas d'utilisateur. Sur une image OVHcloud ou Scaleway, vous avez souvent ubuntu ou debian en plus de root ; sur SSD Nodes ou Hetzner, vous êtes root. Adaptez la liste à ce qui existe.

action vaut accept ou check. accept ouvre la session dès que le tunnel a authentifié le pair. check exige en plus une réauthentification récente chez votre fournisseur d'identité (Google, Microsoft, GitHub, Apple ou un OIDC), y compris son MFA (authentification à plusieurs facteurs). Le serveur écrit alors une URL login.tailscale.com dans votre terminal, et la session reste en attente tant que vous ne l'avez pas ouverte. checkPeriod fixe la durée de validité de cette vérification : 12 heures par défaut, entre une minute et 168 heures, ou always pour vérifier à chaque connexion.

Voici une policy pour un VPS tagué, avec check d'une heure pour root et accept pour un compte deploy :

{
  "tagOwners": {
    "tag:vps": ["alice@example.com"]
  },
  "acls": [
    {"action": "accept", "src": ["*"], "dst": ["*:*"]}
  ],
  "ssh": [
    {
      "action": "check",
      "checkPeriod": "1h",
      "src": ["alice@example.com"],
      "dst": ["tag:vps"],
      "users": ["root"]
    },
    {
      "action": "accept",
      "src": ["autogroup:member"],
      "dst": ["tag:vps"],
      "users": ["deploy"]
    }
  ]
}

Le tag se pose au moment de l'authentification du serveur : sudo tailscale up --ssh --advertise-tags=tag:vps, lancé par un compte listé dans tagOwners. Gardez cette commande en tête, elle revient dans la section sur l'expiration des clés.

Quand une règle manque, le refus vient du serveur Tailscale, pas d'OpenSSH, et le message est net. tailnet policy does not permit you to SSH to this node : aucune règle ssh ne couvre le couple source/destination. tailnet policy does not permit you to SSH as user "root" : une règle couvre bien la destination, mais son champ users n'inclut pas le compte demandé. Ce sont les deux erreurs les plus fréquentes, et aucune des deux ne se corrige sur le serveur. Elles se corrigent dans la policy.

Ce qui ne s'applique plus : sshd_config, clés, fail2ban

Disons-le clairement, parce que c'est là que les habitudes trompent. Sur le chemin Tailscale SSH, /etc/ssh/sshd_config n'est pas lu. PermitRootLogin no n'empêche rien : c'est le champ users de la policy qui décide si root est accessible. ~/.ssh/authorized_keys n'est pas consulté : la clé du portable n'existe pas dans cette conversation, seule son identité de nœud compte. AllowUsers, MaxAuthTries, PasswordAuthentication, Port : aucun effet. Tailscale l'écrit d'ailleurs dans sa documentation : ces fichiers ne sont pas modifiés, ils sont simplement contournés pour ce trafic.

fail2ban non plus. fail2ban sur Ubuntu 24.04 lit les échecs de sshd dans le journal d'authentification. Une session Tailscale SSH refusée par la policy ne passe jamais par sshd, donc rien n'apparaît dans /var/log/auth.log et fail2ban n'a rien à compter. Les sessions Tailscale SSH sont tracées par tailscaled : sudo journalctl -u tailscaled les montre. C'est là qu'il faut regarder, pas dans les journaux d'OpenSSH.

Tout ce que vous avez fait pour durcir sshd reste utile pour sshd. Si un jour vous rouvrez ce chemin, par le tunnel ou par Internet, les réglages de durcissement d'OpenSSH sur un VPS redeviennent votre première ligne. Ils ne protègent simplement pas le chemin Tailscale SSH, qui a ses propres règles.

Le pare-feu, dans le bon ordre

L'ordre compte plus que les commandes. Fermez le port 22 avant d'avoir prouvé que le tailnet marche, et vous vous enfermez dehors.

Première étape : la preuve. Depuis le portable, ssh root@vps-paris doit ouvrir une session, et ssh -v doit montrer remote software version Tailscale. Gardez cette session ouverte pendant tout ce qui suit.

Deuxième étape : repérez la console de secours de votre fournisseur avant d'en avoir besoin. C'est la console web, KVM ou VNC, du panneau de gestion : elle vous donne un écran et un clavier sur la machine sans passer par le réseau, donc sans passer par ufw ni par Tailscale. Chez OVHcloud, Scaleway, Hetzner comme chez SSD Nodes, elle existe. Ouvrez-la une fois et connectez-vous avec le mot de passe root de la machine, pour vérifier que vous le connaissez. Si l'image n'a pas défini de mot de passe root (Scaleway, ou Hetzner quand vous avez fourni une clé SSH), définissez un mot de passe root sur votre VPS Ubuntu maintenant, depuis votre session Tailscale. Un serveur dont vous n'avez pas le mot de passe console est un serveur que vous perdrez à la première erreur de pare-feu.

Troisième étape seulement, ufw. Sur un VPS préparé selon les bases d'ufw sur un VPS, la politique par défaut refuse déjà tout en entrée, et une règle ouvre le 22 à tout le monde. Regardez :

sudo ufw status numbered

Vous verrez une ligne 22/tcp ALLOW IN Anywhere, ou OpenSSH ALLOW IN Anywhere si la règle vient du profil d'application. Ajoutez les nouvelles règles avant de retirer l'ancienne :

sudo ufw allow in on tailscale0 to any port 22 proto tcp
sudo ufw allow 41641/udp
sudo ufw delete allow 22/tcp
sudo ufw status verbose

Remplacez delete allow 22/tcp par delete allow OpenSSH si c'est le profil que vous aviez utilisé, ou par delete <numéro> avec le numéro de ligne affiché plus haut. Si vous supprimez plusieurs lignes par numéro, commencez par le plus grand : les numéros se décalent après chaque suppression.

Deux remarques sur ces règles. La ligne tailscale0 n'est pas ce qui autorise Tailscale SSH : ce trafic ne passe jamais par ufw, comme expliqué plus haut. Elle sert au cas où vous désactiveriez Tailscale SSH avec tailscale set --ssh=false. Les connexions vers 100.x.y.z:22 iraient alors au noyau puis à sshd, et sans cette règle ufw les bloquerait. C'est votre chemin de repli vers l'OpenSSH classique, accessible uniquement depuis le tailnet.

La ligne 41641/udp est le port d'écoute WireGuard de tailscaled, fixé dans /etc/default/tailscaled. Tailscale fonctionne sans, en passant par ses relais DERP, mais un VPS avec une IP publique et ce port ouvert obtient une connexion directe presque à coup sûr. Vérifiez depuis le portable avec tailscale ping vps-paris : pong from vps-paris (100.x.y.z) via 203.0.113.10:41641 signifie direct, via DERP(par) signifie relayé. Si vous restez sur un relais, la différence entre une connexion Tailscale directe et relayée explique ce qui bloque.

Prouvez ensuite la fermeture. Depuis le portable, visez l'IP publique du VPS, pas son nom Tailscale :

ssh -o ConnectTimeout=5 root@203.0.113.10

La réponse doit être ssh: connect to host 203.0.113.10 port 22: Connection timed out. Le délai d'attente, et non un refus immédiat, est le signe que ufw jette le paquet sans répondre. Enfin, redémarrez le serveur avec sudo reboot, attendez une minute, et refaites ssh root@vps-paris. Si cette dernière connexion échoue, la console de secours de l'étape deux est votre retour, et sudo ufw allow 22/tcp depuis cette console rouvre la porte le temps de comprendre.

Si votre fournisseur propose aussi un pare-feu réseau en amont de la machine (Hetzner Cloud Firewall, groupes de sécurité Scaleway), appliquez-lui le même ordre : d'abord la preuve, puis la fermeture du 22, en laissant l'UDP 41641 entrant si vous voulez des connexions directes. La sortie doit rester ouverte : tailscaled joint le plan de contrôle en HTTPS sortant, et les relais DERP aussi.

Une dernière chose à savoir, pour ne pas la prendre pour une panne : redémarrer tailscaled coupe toute session Tailscale SSH en cours. Un apt upgrade qui met à jour le paquet tailscale vous déconnectera donc. Reconnectez-vous, c'est tout.

Le piège des serveurs : la clé de nœud expire au bout de 180 jours

Chaque nœud du tailnet possède une clé de nœud, et par défaut elle expire après 180 jours. Sur un portable, ce n'est pas un problème : Tailscale vous demande de vous reconnecter, vous cliquez, c'est fini. Sur un serveur sans écran, c'est une coupure silencieuse. Un matin, ssh root@vps-paris n'aboutit plus et finit par expirer. Rien sur le serveur ne vous a prévenu, et le port 22 public est fermé. C'est le scénario que les gens cherchent quand ils tapent « tailscale ssh ne fonctionne plus » six mois après l'installation.

Vérifiez la date sur le serveur :

sudo apt-get install -y jq
tailscale status --json | jq -r '.Self.KeyExpiry'

Vous obtenez une date, par exemple 2027-03-15T10:12:00Z. Si elle est dans le passé, le nœud est expiré, et la console d'administration le marque comme tel sur la page Machines.

Si vous êtes déjà coupé, la réparation passe par la console de secours du fournisseur, puisque c'est le seul chemin qui reste. Connectez-vous dessus et relancez l'authentification :

sudo tailscale up --ssh --force-reauth

La commande affiche à nouveau To authenticate, visit: et une URL. Recopiez-la sur le portable (la console web permet en général de sélectionner du texte, sinon tapez-la), ouvrez-la, et le nœud revient. Ne lancez jamais --force-reauth depuis une session Tailscale SSH : l'aide du flag le dit elle-même, la commande coupe la connexion Tailscale, donc la session qui l'a lancée.

Pour que cela n'arrive plus, désactivez l'expiration pour ce serveur. Deux façons. Dans la console d'administration, page Machines, ouvrez le menu de la ligne du VPS et choisissez « Disable key expiry ». Ou taguez le serveur au moment de l'authentification (--advertise-tags=tag:vps, vu plus haut) : une machine taguée pour la première fois a l'expiration désactivée par défaut, parce que Tailscale considère un nœud tagué comme un serveur et non comme l'appareil d'une personne. Relancez ensuite la commande jq ci-dessus : elle affiche null, parce que le champ KeyExpiry n'est écrit que lorsqu'une date existe.

Désactiver l'expiration n'ouvre rien de plus. La policy ssh décide toujours qui entre, et vous pouvez retirer la machine du tailnet à tout moment depuis la console.

Le modèle de menace, sans enjoliver

Ce montage confie votre accès administrateur à des tiers, et il faut savoir lesquels pour décider si c'est acceptable pour ce serveur.

Vous faites confiance au plan de contrôle de Tailscale. Il distribue les clés publiques des nœuds et la policy. Il ne peut pas lire votre trafic, les clés WireGuard ne quittent jamais vos machines, mais il décide quels nœuds se connaissent et, avec Tailscale SSH, il porte la règle qui dit qui peut être root chez vous. Ce que Tailscale peut et ne peut pas voir détaille cette frontière. Contre le risque qu'un nœud soit ajouté à votre tailnet sans vous, le tailnet lock impose que chaque nouvelle clé de nœud soit signée par une de vos propres machines. Activez-le si ce VPS compte.

Vous faites confiance à votre fournisseur d'identité. Une personne qui prend le contrôle de votre compte Google ou GitHub, et d'un appareil enrôlé, obtient root sur le VPS. Le MFA chez le fournisseur d'identité, et action: check avec un checkPeriod court pour root, sont les deux réglages qui limitent cela.

Vous faites confiance à votre portable. Un portable déverrouillé et enrôlé est une session root ouverte. check aide ici aussi, puisque la réauthentification passe par le navigateur et le MFA.

Deux outils complètent cela. L'enregistrement des sessions (le champ recorder de la règle ssh, pointant vers un nœud enregistreur) garde une trace rejouable de chaque session ; sa disponibilité dépend de votre plan, et c'est la page des tarifs de Tailscale qui fait foi, pas ce guide. Et la console d'administration garde un journal des changements de policy, ce qui vous dit qui a donné root à qui, et quand.

Ce que ce montage ne remplace pas : un second chemin d'administration indépendant de Tailscale. La console de secours du fournisseur en est un. Si votre fournisseur n'en a pas, ou si elle est lente, gardez sshd joignable depuis une seule adresse IP fixe à vous, via une règle ufw restreinte, plutôt que de ne dépendre que d'un seul tiers.

FAQ

Tailscale SSH remplace-t-il mes clés SSH et mon sshd_config ?

Sur le chemin Tailscale SSH, oui. tailscaled intercepte les connexions vers le port 22 de l'IP Tailscale du serveur avant qu'elles n'atteignent sshd. sshd_config, authorized_keys, fail2ban et PAM ne sont pas consultés pour ces sessions ; la policy du tailnet décide seule, y compris pour root. ssh -v root@vps le montre : la ligne remote software version dit Tailscale et non OpenSSH. Vos réglages OpenSSH restent valables pour sshd, qui continue de tourner et de répondre sur les autres chemins que vous laissez ouverts.

Pourquoi mon accès SSH par Tailscale s'est-il coupé au bout de six mois ?

La clé de nœud du serveur a expiré. Par défaut, chaque nœud doit se réauthentifier après 180 jours, et un serveur sans écran ne le fait pas tout seul. tailscale status --json | jq -r '.Self.KeyExpiry' affiche alors une date dans le passé. Réparez depuis la console de secours du fournisseur avec sudo tailscale up --ssh --force-reauth, puis désactivez l'expiration pour cette machine dans la console d'administration (« Disable key expiry »), ou taguez-la, ce qui désactive l'expiration par défaut.

Que signifie « tailnet policy does not permit you to SSH as user root » ?

Une règle ssh couvre bien votre portable et ce serveur, mais son champ users n'inclut pas root. autogroup:nonroot autorise tous les comptes sauf root ; il faut ajouter "root" explicitement dans la liste. Le message voisin, does not permit you to SSH to this node, signifie qu'aucune règle ssh ne couvre le couple source/destination, par exemple parce que le serveur est tagué et que la règle par défaut ne vise que autogroup:self. Dans les deux cas, la correction se fait dans la policy, pas sur le serveur.

Dois-je fermer le port 22 public, ou seulement le limiter ?

Fermez-le, une fois que le chemin Tailscale est prouvé et que la console de secours du fournisseur est testée. Un port 22 ouvert à tout Internet ne sert à rien de plus que le tailnet, et il alimente fail2ban en tentatives toute la journée. Si vous voulez un second chemin qui ne dépende pas de Tailscale, limitez sshd à votre seule IP fixe avec une règle ufw restreinte, plutôt que de le laisser ouvert à tous.

Que se passe-t-il si le plan de contrôle de Tailscale est en panne ?

Les tunnels déjà établis continuent de fonctionner, parce que les clés WireGuard sont sur vos machines et que le trafic ne passe pas par Tailscale. Une session Tailscale SSH en mode accept s'ouvre tant que les deux nœuds se connaissent déjà. Le mode check a besoin de joindre login.tailscale.com pour la réauthentification, donc une nouvelle session check peut échouer pendant la panne. C'est une raison de plus pour garder la console de secours du fournisseur comme chemin indépendant.