Pendant deux ans, j’ai exposé tous mes services homelab avec Nginx Proxy Manager. Ça fonctionnait. Quand j’ai monté K3s sur heighliner, NPM a commencé à montrer ses limites – configurer manuellement chaque service dans une UI alors que Kubernetes voulait gérer ça par labels, c’était une friction constante. J’ai basculé sur Traefik v3. Voici ce que j’aurais voulu savoir avant.
Ce que j’avais avant
NPM installé dans un LXC Debian 12 sur Proxmox VE 8.3, exposé sur le VLAN Serveurs (30). La stack était simple : une douzaine de services Docker sur heighliner, chacun enregistré à la main dans l’UI de NPM avec son hostname et son port. Let’s Encrypt géré par NPM via le DNS challenge OVH. Ça marchait, sans réfléchir.
Ma topologie réseau : les VLANs sont isolés, le VLAN Management (1) pour Proxmox, le VLAN Serveurs (30) pour les VMs et LXC applicatifs, le VLAN IoT (20) pour les trucs qu’on ne fait pas confiance. Le reverse proxy vit sur le VLAN 30 et expose vers l’extérieur via le routeur ZenWifi XT8, forwarding ports 80/443 uniquement.
Nginx Proxy Manager : pour et contre sans concession
NPM a un avantage réel que je ne vais pas minimiser : tu le déploies, tu ouvres l’UI, tu rentres un hostname et un port, tu coches « SSL », tu valides. Trois clics et le service est exposé avec un certificat valide. Pour quelqu’un qui démarre ou qui gère moins de dix services stables, c’est exactement ce qu’il faut.
La gestion des certificats Let’s Encrypt dans NPM est solide. Le support des DNS challenges couvre les principaux providers. Le renouvellement automatique fonctionne sans intervention. L’UI affiche clairement l’état de chaque certificat, la date d’expiration, les logs de renouvellement. Quand ça tombe en erreur, le message est lisible par un humain.
Les limites sont structurelles. NPM ne sait pas que tes containers Docker existent. Chaque fois que tu lances un nouveau service, tu retournes dans l’UI, tu crées une nouvelle Proxy Host entry, tu renseignes l’IP ou le nom du container, le port. Si tu reconstruis un container avec une IP différente, tu mets à jour à la main. C’est gérable à dix services. À vingt-cinq, c’est une corvée. Avec Kubernetes, c’est simplement incompatible avec la philosophie de déploiement déclaratif.
NPM n’a pas de système de middlewares au sens Traefik du terme. Pas de basic auth intégré qui s’applique à un groupe de services, pas de rate limiting configurable par route, pas d’injection de headers automatisée. Tu peux faire du custom Nginx config en mode avancé, mais tu perds l’ergonomie de l’UI et tu te retrouves à écrire du Nginx à la main – autant utiliser Nginx directement.
Traefik v3 : ce que ça change vraiment
Traefik v3 (je tourne en 3.1.2 au moment où j’écris) repose sur un modèle inverse : c’est lui qui observe ce qui tourne, pas toi qui lui indiques ce qui tourne. Via les providers Docker et Kubernetes, il lit les labels sur les containers ou les Ingress resources et configure les routes automatiquement. Tu déploies un container avec les bons labels, Traefik détecte le changement et crée la route. Tu supprimes le container, la route disparaît. Zéro intervention manuelle.
Le dashboard Traefik (port 8080 en HTTP, à ne jamais exposer sur l’extérieur) montre en temps réel les routers, services, middlewares, certificats actifs. C’est utile pour déboguer – on voit exactement comment Traefik a interprété tes labels.
Les métriques Prometheus sont exposées nativement sur /metrics. J’ai branché ça sur hubble.arewel.com (Grafana + Prometheus + Loki + Alertmanager), et j’ai maintenant des graphes sur les request rates par service, les codes HTTP, les durées de réponse. Avec NPM, cette visibilité n’existait pas.
La configuration initiale demande un effort. Tu ne cliques pas dans une UI : tu écris du YAML. Le fichier de configuration statique (/etc/traefik/traefik.yml) définit les entrypoints, les providers, le cert resolver. La configuration dynamique vient des labels Docker ou des fichiers dans un dossier surveillé. Cette séparation statique/dynamique déroute au début.
Cas pratique : exposer Nextcloud avec les deux
Nextcloud est un bon candidat pour comparer les deux approches parce que c’est un service qui a des exigences particulières sur les headers HTTP. Il a besoin que X-Forwarded-Proto soit correctement propagé, que HSTS soit activé, et il genère des avertissements dans son panneau d’administration si la configuration du reverse proxy est incomplète.
Avec NPM, la configuration tient en quelques champs dans l’UI. Tu crées une Proxy Host, tu renseignes le hostname (nextcloud.ton-domaine.com), l’IP du container et le port (souvent 80 ou 8080 selon l’image). Tu actives SSL, tu sélectionnes ou crées un certificat. Dans l’onglet « Advanced », tu ajoutes ce bloc custom Nginx pour les headers que Nextcloud attend :
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Cinq minutes de travail si tu sais quoi mettre dans ce champ. La documentation Nextcloud pour les reverse proxies est complète et mentionne NPM explicitement.
Avec Traefik, tout passe par les labels dans le docker-compose.yml de Nextcloud. Voici la section labels d’un déploiement réel, avec les middlewares nécessaires :
services:
nextcloud:
image: nextcloud:28-apache
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.ton-domaine.com`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
- "traefik.http.routers.nextcloud.middlewares=nextcloud-headers,nextcloud-redirect"
- "traefik.http.middlewares.nextcloud-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.nextcloud-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.nextcloud-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.nextcloud-redirect.redirectregex.regex=/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nextcloud-redirect.redirectregex.replacement=/remote.php/dav/"
- "traefik.http.middlewares.nextcloud-redirect.redirectregex.permanent=true"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
C’est verbeux. Et ça m’a pris deux heures à faire fonctionner correctement.
Ce qui a coincé avec Traefik
La redirection HTTP vers HTTPS est la première source de confusion. Avec NPM, tu coches une case. Avec Traefik, il faut comprendre la distinction entre l’entrypoint web (port 80) et websecure (port 443), et configurer explicitement un middleware de redirection soit au niveau global dans traefik.yml, soit par route via un label. J’ai d’abord configuré la redirection globale dans le fichier statique :
# /etc/traefik/traefik.yml
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
Ce qui a coincé : j’avais un middleware de redirection défini en double, une fois en global et une fois en label sur le router. Traefik acceptait ça sans erreur, mais le résultat était une boucle de redirection. La première heure passée à déboguer s’est terminée en lisant les logs Traefik au niveau DEBUG (--log.level=DEBUG dans traefik.yml), ce qui donne des traces très détaillées sur quels middlewares s’appliquent dans quel ordre.
Le deuxième problème : Nextcloud vérifiait X-Forwarded-Proto et retournait quand même des avertissements dans son panneau d’administration. La cause : j’utilisais customRequestHeaders mais l’image Apache de Nextcloud lisait l’header sous une forme légèrement différente. J’ai dû ajouter dans le config.php de Nextcloud :
'overwriteprotocol' => 'https',
'overwritehost' => 'nextcloud.ton-domaine.com',
'trusted_proxies' => ['172.16.0.0/12'],
Avec NPM, le proxy_set_header dans la config custom Nginx avait résolu ça automatiquement sans toucher au config.php. Ce n’est pas un défaut de Traefik, c’est une différence de comportement entre Nginx et le proxying de Traefik vis-à-vis des headers Apache.
HSTS avec includeSubDomains m’a causé une troisième friction : je l’avais activé globalement avant d’avoir des certificats valides pour tous mes sous-domaines. Résultat : des sous-domaines non encore configurés devenaient inaccessibles depuis les navigateurs qui avaient mis en cache la directive HSTS. J’ai appris à activer HSTS par service, pas globalement, jusqu’à avoir la stack complète en HTTPS.
Verdict : lequel choisir
La question n’est pas « lequel est meilleur » – elle est « lequel correspond à ta situation ».
Si tu gères moins de dix services stables, sans Kubernetes, sans besoin d’auth middleware et sans monitoring sérieux : NPM. L’UI est agréable, la prise en main est immédiate, la maintenance est faible. Tu passes ton temps à configurer tes services, pas ton proxy. C’est une proposition valide.
Si tu as K3s ou si tu prévois d’en avoir, la réponse est Traefik sans discussion. L’intégration native avec les Ingress Kubernetes et les CRDs IngressRoute supprime complètement la gestion manuelle des routes. Les middlewares d’authentification (Forward Auth vers Authelia, Authentik) s’appliquent à des groupes de routes via des labels. Les métriques Prometheus arrivent sans configuration supplémentaire.
Et si tu as Docker sans Kubernetes mais une vingtaine de services avec des besoins en auth ? Traefik reste pertinent, mais la courbe d’apprentissage initiale est réelle. Compte une demi-journée pour comprendre le modèle statique/dynamique et faire fonctionner un premier service proprement.
Je garde Traefik maintenant que K3s tourne sur heighliner. La configuration par labels dans les docker-compose.yml et les manifests Kubernetes est cohérente, versionnée dans Git, et ne nécessite aucune intervention dans une UI. Quand je déploie un nouveau service, la route existe dès que le container démarre. C’est exactement ce que je voulais.