Notes · Article
Tailscale Serve : HTTPS pour 50 applis auto-hébergées, aucune sur Internet
Je fais tourner une cinquantaine de petites applis sur un Mac mini chez moi : tableaux de bord, PWA, API pour mes applis mobiles, un Ollama local. Aucune n’est joignable depuis Internet, et aucune n’écoute non plus sur mon réseau domestique. Chaque appli n’écoute que sur 127.0.0.1, et tailscale serve la publie sur mon tailnet à l’adresse https://<machine>.<tailnet>.ts.net:<port>, avec un vrai certificat, joignable uniquement depuis mes propres appareils, par le tunnel WireGuard chiffré de Tailscale.
Une commande par appli, rien d’autre à faire tourner :
tailscale serve --bg --https 5174 http://127.0.0.1:5174
Le chemin d’une requête
Internet
Tous les autresaucune route d’entrée
Mon tailnet
Mes appareils
Ouvrent l’URL ts.net
Mac mini
Funnel désactivé
tailscale serve
Le certificat s’arrête ici
127.0.0.1
51 entrées serve, toutes en tailnet only
En ce moment, tailscale serve status liste 51 entrées, toutes marquées tailnet only. Aucune n’utilise Funnel, la fonctionnalité qui publierait un port sur l’Internet public.
Pourquoi HTTPS, alors que le tunnel est déjà chiffré
WireGuard chiffre déjà le trafic entre mes appareils, donc du HTTP simple serait assez privé. HTTPS, c’est pour le navigateur. iOS n’exécute le service worker d’une PWA que sur une origine sécurisée : sans HTTPS, mes applis mobiles perdent le mode hors ligne et l’installation sur l’écran d’accueil. Tailscale émet le certificat pour le nom ts.net de la machine, donc il n’y a rien à renouveler à la main.
Un détail : utilisez le nom complet. https://<machine>:5174 échoue, parce que le certificat est émis pour le nom ts.net, pas pour le nom court.
Le tunnel, sans certificat
HTTP
Assez privé : WireGuard le chiffre
iOS refuse de lancer le service worker
Ni hors ligne, ni installation sur l’écran d’accueil
Le même tunnel, plus un certificat
HTTPS
Une origine sécurisée : le navigateur lui fait confiance
Service worker, hors ligne, installation
Tailscale émet le certificat : rien à renouveler
https://<machine>:5174✗ le certificat est émis pour le nom ts.net
https://<machine>.<tailnet>.ts.net:5174✓ correspond au certificat
Utilisez le nom completLe nom court ne correspond pas au certificat.
Pourquoi la loopback, et pas le LAN
Lier l’appli à 127.0.0.1, c’est faire de tailscale serve la seule entrée. Un invité sur mon Wi-Fi ne peut pas la joindre, un routeur mal configuré non plus. Dans Docker, c’est la partie hôte du mappage de ports :
ports:
- "127.0.0.1:5174:80" # loopback only; tailscale serve proxies to it
Un invité sur mon Wi-Fi
Adresse LAN ✗
rien n’écoute
Un routeur mal configuré
Adresse LAN ✗
rien n’écoute
Mon téléphone, sur le tailnet
tailscale serve
127.0.0.1 → l’appli
Le mappage de ports Docker
- 127.0.0.1adresse hôte : loopback uniquement
- 5174port hôte : tailscale serve y relaie les requêtes
- 80le port à l’intérieur du conteneur
Une seule entréePar tailscale serve, ou pas du tout.
Quatre pièges où je suis tombé
| Piège | Ce que j’ai vu | Correctif |
|---|---|---|
| Entrée serve perdue | L’appli répond sur 127.0.0.1, l’URL du tailnet ne se connecte pas | L’entrée avait disparu après un redémarrage de Tailscale. Vérifier tailscale serve status, la rajouter |
| Appli liée à l’IP du LAN | Serve renvoie des erreurs, la loopback est vide | Un changement avait lié Docker à l’adresse LAN. Retour à 127.0.0.1:port |
| Course au port (Ollama) | Après un redémarrage, Ollama plante en boucle sur address already in use | Le réglage « Expose to network » d’Ollama prenait 0.0.0.0:11434 alors que serve tenait le même port sur l’adresse du tailnet. J’ai désactivé Expose |
| Vérification du Host (Ollama) | 403 via serve, tout va bien en local | Serve transmet l’en-tête Host ts.net et Ollama sur la loopback le rejette. Un petit nginx intermédiaire réécrit le Host |
Les deux derniers n’ont touché qu’Ollama, mais la leçon est générale : serve relaie la requête telle quelle, donc une appli qui vérifie l’en-tête Host ou veut le port pour elle seule mérite un coup d’œil.
1Entrée serve perdue
Correctifla rajouter ; l’appli allait bien depuis le début
2Appli liée à l’IP du LAN
Correctifrevenir à 127.0.0.1
3Course au portOllama
Correctifdésactiver Expose to network
4Vérification du HostOllama
Correctifun nginx intermédiaire réécrit le Host
La leçonServe transmet la requête telle quelle.
Ce que je dirais à quelqu’un qui démarre
- Liez chaque appli à la loopback et laissez
tailscale serveêtre la seule porte. - Passez en HTTPS dès le premier jour si un téléphone doit l’ouvrir. Les PWA en ont besoin.
- Tenez une liste des ports. Avec 50 applis, un registre de ports évite que deux applis se disputent le même numéro.
- Vérifiez d’abord
tailscale serve statusquand une URL ne répond plus. La plupart de mes pannes venaient d’une entrée manquante, pas d’une appli cassée.
-
1Loopback uniquement
Serve est la seule porte
-
2HTTPS dès le premier jour
Si un téléphone l’ouvre, sa PWA en a besoin
-
3Une liste des ports
Deux applis ne se disputent jamais un numéro
-
4Le statut d’abord
La plupart des pannes : une entrée manquante, pas une appli cassée
Texte brut: tailscale-serve-self-hosted-apps.md · Flux Atom · Toutes les notes