Gluetun unhealthy ou bloqué en redémarrage
Votre conteneur gluetun redémarre en boucle ou reste unhealthy et entraîne tous les autres services avec lui. Lisez ses logs, trouvez la cause, corrigez le compose.
Pourquoi gluetun redémarre en boucle ou reste unhealthy
Un conteneur gluetun qui redémarre en boucle s'arrête presque toujours en code 1 pendant la lecture de sa configuration: le programme refuse un réglage, écrit la raison dans ses logs, quitte, et la politique restart de votre compose le relance aussitôt. Un conteneur qui reste unhealthy est une panne différente: le processus tourne toujours, mais son contrôle de santé interne ne joint plus rien à travers le tunnel. Le symptôme visible est le même dans les deux cas, parce que tous les services qui passent par gluetun perdent le réseau avec lui. La première étape consiste donc à savoir laquelle des deux pannes vous avez, pas à changer une variable au hasard.
gluetun écrit la cause exacte de son refus avant de s'arrêter. On lit ses logs d'abord, on touche au fichier compose ensuite.
Tous les exemples de ce guide sont épinglés sur l'image qmcgaw/gluetun:v3.41.3, publiée le 30 juillet 2026 et vérifiée le 26 septembre 2026. Les noms des variables d'environnement de gluetun changent d'une version à l'autre, donc un extrait copié sur un forum peut être juste pour une autre version que la vôtre.
Séparer les deux pannes en deux commandes
docker ps -a --filter name=gluetun
docker inspect --format '{{.State.ExitCode}}' gluetun
docker inspect --format '{{json .State.Health}}' gluetunSi la colonne d'état affiche Restarting suivi d'un code de sortie, le processus meurt puis Docker le relance: la cause est dans la configuration, et le tunnel n'a jamais été monté. Si elle affiche Up suivi de (unhealthy), le processus vit et c'est le contrôle de santé qui échoue. Le champ Health retourné par docker inspect contient les dernières sorties du test, ce qui vous dit ce que Docker a réellement exécuté et ce qu'il a lu en retour.
Un point que beaucoup de tutoriels laissent croire faux: Docker ne redémarre jamais un conteneur parce qu'il est unhealthy. Les politiques restart réagissent à la fin du processus, pas à l'état de santé. Si votre conteneur passe en unhealthy et redémarre, alors soit gluetun quitte de lui-même, soit un outil tiers de votre pile (un conteneur de type autoheal, ou un gestionnaire de mises à jour) agit sur cet état.
Lire les logs de gluetun avant tout
docker logs --tail 80 gluetun
docker logs -f gluetunAu démarrage, gluetun affiche les versions d'Alpine, d'OpenVPN et d'iptables, puis un résumé complet des réglages qu'il a retenus. Ce résumé est votre source de vérité. Si une variable que vous croyez avoir posée n'y apparaît pas avec la valeur attendue, c'est qu'elle n'a pas été lue: faute de frappe dans le nom, mauvais service dans le fichier compose, ou fichier .env non chargé. Ajoutez LOG_LEVEL=debug quand le résumé ne suffit pas.
Si vous avez repris un extrait ancien, surveillez aussi les avertissements de dépréciation. gluetun garde les anciens noms de certaines variables et prévient avec un message qui commence par You are using the old, puis nomme la variable actuelle. Ce message est une bonne nouvelle: la valeur a été lue. Le cas dangereux est l'inverse.
Pourquoi épingler une version, et jamais :latest
Le tag :latest de gluetun ne désigne pas la dernière version stable: il suit la branche master, donc du code de développement. C'est documenté par le projet lui-même, qui recommande un tag de version pour la stabilité. Écrivez qmcgaw/gluetun:v3.41.3 (ou ghcr.io/qdm12/gluetun:v3.41.3), et changez ce numéro quand vous décidez de mettre à jour, pas quand le registre décide pour vous.
Le deuxième argument est plus concret. Dans la v3.41.3, HEALTH_TARGET_ADDRESS est encore accepté comme ancien nom de HEALTH_TARGET_ADDRESSES, avec l'avertissement décrit plus haut. En revanche, HEALTH_VPN_DURATION_INITIAL, qui traîne encore dans beaucoup d'extraits de forum, n'est plus lu du tout par cette version: aucun avertissement, aucun effet. Vous croyez avoir allongé un délai, et vous n'avez rien changé. C'est exactement pour cela qu'une configuration gluetun doit être lue en face de la documentation de la version que vous faites tourner.
Cause 1: NET_ADMIN absent, ou /dev/net/tun inaccessible
gluetun écrit des règles de pare-feu et crée une interface réseau. Sans la capacité NET_ADMIN, il ne peut faire ni l'un ni l'autre, et il s'arrête. C'est la panne la plus fréquente, parce que c'est la ligne la plus facile à perdre en recopiant un extrait.
services:
gluetun:
image: qmcgaw/gluetun:v3.41.3
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ./gluetun:/gluetun
ports:
- 8888:8888/tcp
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=votre_cle_base64
- SERVER_COUNTRIES=France
- TZ=Europe/Paris
restart: unless-stoppedDeux réglages distincts, deux erreurs distinctes. Quand NET_ADMIN manque, l'échec vient de la partie pare-feu, et le wiki du projet documente un Permission denied (you must be root) observé quand Portainer n'applique pas la capacité comme prévu. Quand le périphérique TUN manque, gluetun essaie d'abord de créer le nœud /dev/net/tun lui-même, échoue, et le message parle de la création du nœud de périphérique TUN en vous suggérant explicitement d'ajouter --device /dev/net/tun à la commande du conteneur. Le projet documente aussi un operation not supported à l'ouverture du périphérique, qui signifie que le module noyau tun n'est pas chargé sur l'hôte, ce qui n'est pas un problème de compose.
Sur un VPS en virtualisation KVM, le périphérique existe et la ligne devices suffit. Sur un conteneur LXC, le cas est différent: le projet documente l'erreur operation not permitted à la création du nœud, avec la commande Proxmox pct set 12345 -dev0 /dev/net/tun suivie de pct reboot 12345 pour exposer le périphérique au conteneur. En Podman sans privilèges, la documentation cite container_use_devices=true côté SELinux, et l'ajout de NET_RAW en plus de NET_ADMIN pour la partie pare-feu. Vérifiez côté hôte avec ls -l /dev/net/tun avant d'accuser votre fichier compose.
Cause 2: un fournisseur ou un VPN_TYPE que l'image ne connaît pas
VPN_SERVICE_PROVIDER et VPN_TYPE sont validés au démarrage. Un nom inconnu donne une erreur qui contient VPN provider name is not valid, et une valeur autre que openvpn ou wireguard donne VPN type is not valid. Ces deux lignes sont claires, mais on ne les voit que si on lit les logs jusqu'au bout.
Le piège est ailleurs. L'image porte un fournisseur par défaut (Private Internet Access), donc oublier VPN_SERVICE_PROVIDER ne produit pas l'erreur que vous attendez: gluetun valide la configuration du fournisseur par défaut en OpenVPN et se plaint d'un identifiant vide avec un message contenant user is empty. Vous cherchez alors un problème de clé WireGuard, alors que le programme ne sait même pas que vous visez WireGuard.
Pour les fournisseurs qu'un lecteur français a le plus de chances d'avoir, les valeurs exactes documentées sont protonvpn, mullvad et nordvpn. Recopiez la valeur depuis la page du fournisseur dans le wiki de gluetun plutôt que de la deviner, et ne supposez pas qu'une majuscule ou un espace sera pardonné.
Cause 3: une clé collée dans la mauvaise forme
WIREGUARD_PRIVATE_KEY attend une clé de 32 octets en base64, et rien d'autre. Beaucoup de lecteurs collent la ligne entière de leur fichier de configuration, PrivateKey = ..., ou le fichier .conf complet. gluetun tente alors d'analyser cette chaîne, échoue, et l'erreur contient private key is not valid, suivie de la raison technique de l'analyse. Retirez le nom du champ, le signe égal, les espaces autour, et tout retour à la ligne qui se serait invité.
Le second réglage dépend du fournisseur, et c'est une différence réelle entre eux. Chez Mullvad, WIREGUARD_ADDRESSES est obligatoire et attend l'adresse de l'interface au format CIDR, telle qu'elle figure dans la configuration téléchargée. Chez ProtonVPN et chez NordVPN, la documentation ne demande que la clé privée. Si l'adresse manque là où elle est requise, l'erreur contient interface address is not set. Pour ProtonVPN, notez que la clé se récupère en générant une configuration WireGuard depuis votre compte, et que cette valeur vaut pour tous leurs serveurs.
Une clé qui a fonctionné pendant des mois peut cesser de fonctionner sans que vous ayez rien touché. La documentation de gluetun place d'ailleurs la validité des identifiants et des clés en premier dans la liste des causes d'un échec répété au démarrage, et mentionne le cas WireGuard chez ProtonVPN. Régénérez la clé avant de réécrire tout votre compose.
Cause 4: un filtre de serveur qui ne correspond à aucun serveur
gluetun embarque une liste de serveurs dans l'image, et vos variables SERVER_* la filtrent. Deux échecs différents en découlent. Si la valeur n'existe pas dans le référentiel, la validation la rejette avec un message du type the country specified is not valid, the city specified is not valid ou the server name specified is not valid. Si les valeurs existent mais que leur combinaison ne laisse aucun serveur, la sélection échoue plus loin, avec un message contenant no connection to pick from. Ce second cas est le plus déroutant, parce que chaque variable prise séparément semble correcte.
docker run --rm -v ./gluetun:/gluetun qmcgaw/gluetun:v3.41.3 format-servers -protonvpnCette commande affiche les serveurs connus de l'image pour un fournisseur, avec leur pays, leur ville et leur nom d'hôte. C'est la référence à utiliser pour écrire vos filtres. Le wiki prévient que SERVER_HOSTNAMES est le filtre le plus étroit: si le serveur nommé disparaît, le conteneur reste en panne jusqu'à ce que vous changiez la valeur. Les filtres propres à un fournisseur se combinent vite en ensemble vide, par exemple un pays précis plus SECURE_CORE_ONLY ou PORT_FORWARD_ONLY chez ProtonVPN, OWNED_ONLY chez Mullvad, SERVER_CATEGORIES chez NordVPN. Retirez tous les filtres, vérifiez que le tunnel monte, puis remettez-les un par un. Si votre besoin réel est un port entrant, la logique est décrite dans le fonctionnement de la redirection de port avec gluetun plutôt que dans un filtre de plus.
Épingler une version a un coût ici: la liste embarquée vieillit avec l'image. Vous pouvez la rafraîchir sans changer de version, avec la commande update du même binaire, ou laisser UPDATER_PERIOD faire le travail. La documentation recommande une période d'au moins 360 heures, donc quinze jours.
Cause 5: un healthcheck qui tue un tunnel encore en train de monter
L'image v3.41.3 déclare son propre contrôle de santé:
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheckCe test interroge le serveur de santé interne, qui vit par défaut sur 127.0.0.1:9999. Ce serveur répond selon trois contrôles: un contrôle au démarrage, avec un délai d'attente de six secondes, qui ouvre une connexion TCP puis TLS vers les adresses de HEALTH_TARGET_ADDRESSES (cloudflare.com:443 et github.com:443 par défaut), un petit contrôle toutes les minutes, en ICMP vers les adresses de HEALTH_ICMP_TARGET_IPS (1.1.1.1 et 8.8.8.8 par défaut), et un contrôle complet toutes les cinq minutes. Quand un contrôle échoue, gluetun redémarre le client VPN à l'intérieur du conteneur, pas le conteneur. Ce comportement s'éteint avec HEALTH_RESTART_VPN, ce qui est très utile le temps d'un diagnostic: le tunnel reste en place, les logs arrêtent de défiler, et vous voyez enfin la vraie erreur au lieu d'une suite de reconnexions.
Si votre réseau bloque l'ICMP, gluetun le détecte et bascule définitivement sur l'autre type de contrôle pour la durée de vie du conteneur, en l'annonçant dans ses logs. La variable HEALTH_SMALL_CHECK_TYPE permet de forcer ce choix; sa liste de valeurs acceptées est sur la page healthcheck du wiki, et c'est la version de votre image qui décide, pas un extrait de forum.
Dernier cas, et il est fréquent: votre fichier compose déclare son propre bloc healthcheck pour gluetun, avec un start_period trop court. Docker marque alors le conteneur unhealthy pendant que le tunnel monte, et tout ce qui dépend de cet état part en cascade. Écrire ce bloc demande de connaître le temps de démarrage réel du service, comme expliqué dans la façon d'écrire un healthcheck Docker Compose qui laisse le service démarrer. La règle simple: si l'image fournit déjà un contrôle de santé, ne le remplacez pas sans raison.
Mes autres conteneurs tombent aussi: qui est en cause?
Un service routé par gluetun utilise network_mode: "service:gluetun" dans le même fichier compose, ou network_mode: "container:gluetun" s'il est lancé séparément. La documentation précise que depends_on n'est pas nécessaire dans le premier cas. Ce partage d'espace réseau explique la cascade: tant que gluetun n'est pas en marche, Docker refuse de démarrer un conteneur qui doit rejoindre son espace réseau, et un conteneur déjà lancé perd tout accès réseau pendant que gluetun redémarre.
Le test d'isolement prend une minute. Retirez temporairement la ligne network_mode du service dépendant et démarrez-le seul: s'il monte correctement, il est victime et non coupable, et tout votre travail se concentre sur gluetun. La manière de relancer un seul service sans toucher au reste de la pile est décrite dans le démarrage d'un seul service avec docker compose up.
Deux détails de configuration produisent des symptômes proches sans être des pannes de gluetun. Les ports d'un conteneur routé par le tunnel se publient sur le service gluetun, jamais sur le conteneur dépendant, ce qui est la première chose à vérifier quand une interface web devient injoignable alors que le tunnel est sain, et l'accès depuis votre réseau local aux conteneurs placés derrière gluetun traite le sujet en détail. Ensuite, changer une variable d'environnement recrée le conteneur gluetun, pas seulement son processus: après un docker compose up -d, vérifiez que les dépendants sont bien revenus, et relancez-les sinon. La différence entre relancer et recréer est expliquée dans ce que font vraiment restart, recreate et rebuild avec Compose.
Quand la configuration de départ est saine, la panne suivante à comprendre n'est plus le démarrage mais la coupure: un tunnel qui tombe en cours de route, et ce que le pare-feu interne laisse passer à ce moment. C'est le sujet de ce que fait le kill switch de gluetun quand le VPN tombe. Si tout passe sauf la résolution de noms, la piste est ailleurs encore, du côté du résolveur, et la correction du DNS à travers un tunnel WireGuard couvre ce cas. Si le tunnel monte mais que les gros transferts se bloquent, regardez le réglage du MTU et les lenteurs WireGuard, car gluetun expose WIREGUARD_MTU pour cette raison. Et pour repartir d'une base propre plutôt que d'un extrait de forum, une configuration gluetun qui fonctionne du premier coup sert de référence.
La boucle de diagnostic, dans l'ordre
- Regardez l'état avec
docker ps -a:Restartingveut dire que le processus quitte,unhealthyveut dire qu'il tourne mais ne joint rien. - Lisez
docker logs --tail 80 gluetunjusqu'à la dernière ligne, et cherchez le résumé des réglages retenus juste après les lignes de version. - Comparez ce résumé à ce que vous croyez avoir écrit, plutôt qu'à votre fichier compose.
- Posez
HEALTH_RESTART_VPNsur la valeur qui désactive le redémarrage automatique, le temps de lire une erreur stable au lieu d'une boucle. - Réduisez la configuration au minimum, sans aucun filtre de serveur, vérifiez que le tunnel monte, puis remettez un réglage à la fois.
Cette boucle vaut mieux qu'une liste de correctifs, parce qu'elle marche aussi pour les pannes que ce guide ne décrit pas. Le conteneur vous dit ce qui ne va pas. Le travail consiste à le lire.
FAQ
Pourquoi gluetun affiche unhealthy alors que mes téléchargements passent?
Le contrôle de santé de gluetun ne mesure pas votre trafic: il ouvre ses propres connexions vers des cibles fixes, en TCP plus TLS vers les adresses de HEALTH_TARGET_ADDRESSES et en ICMP vers celles de HEALTH_ICMP_TARGET_IPS. Si votre fournisseur VPN ou le serveur choisi bloque l'ICMP, ou si l'une de ces cibles est injoignable, l'état passe à unhealthy alors que le tunnel transporte bien vos données. Regardez la sortie du test avec docker inspect --format '{{json .State.Health}}' gluetun, puis changez la cible du contrôle au lieu de changer votre configuration VPN.
Ai-je besoin de /dev/net/tun en plus de NET_ADMIN?
Ce sont deux permissions différentes. NET_ADMIN autorise gluetun à écrire ses règles de pare-feu et à configurer l'interface, et sans elle l'échec ressemble à un problème de droits root. Le périphérique TUN est ce dont le tunnel a besoin pour exister: gluetun essaie de créer /dev/net/tun lui-même, et quand il n'y arrive pas, son message d'erreur vous suggère explicitement d'ajouter --device /dev/net/tun au conteneur. Mettez les deux, c'est ce que fait l'exemple officiel.
Mes autres conteneurs tombent avec gluetun, faut-il les corriger aussi?
En général non. Un conteneur en network_mode: "service:gluetun" partage l'espace réseau de gluetun, donc il perd le réseau quand gluetun redémarre, et Docker refuse même de le démarrer tant que gluetun n'est pas en marche. Pour trancher, retirez la ligne network_mode et lancez ce service seul: s'il démarre normalement, la panne est entièrement dans gluetun. Pensez aussi à vérifier que vos ports sont publiés sur le service gluetun et non sur le service dépendant.
Faut-il utiliser l'image latest ou une version épinglée?
Épinglez. Le tag :latest de gluetun suit la branche de développement master, pas la dernière version stable, donc il peut changer de comportement sans que vous ayez rien touché. Les noms des variables d'environnement évoluent aussi: dans la v3.41.3, certains anciens noms sont encore acceptés avec un avertissement dans les logs, et d'autres, comme HEALTH_VPN_DURATION_INITIAL, ne sont plus lus du tout et ne déclenchent aucun message. Écrivez un tag de version dans votre fichier compose, gardez la documentation de cette version sous la main, et changez le numéro quand vous décidez de mettre à jour.