Vous avez un site web public servi par Traefik, mais vous souhaitez proteger uniquement certains repertoires sensibles avec un mot de passe ? Ce tutoriel vous guide pas a pas pour configurer BasicAuth sur un chemin specifique, tout en gardant le reste du site accessible publiquement.
1. Contexte et Prerequis
situation de depart
Nous avons un site web statique servi par nginx derriere Traefik (reverse proxy). Le site est accessible publiquement sur https://ops-imperium.com.
Nous voulons proteger uniquement le repertoire /formations/ avec un mot de passe, tout en laissant le reste du site accessible publiquement.
prerequis
- Un serveur avec Docker et Docker Compose
- Traefik configure comme reverse proxy
- Acces SSH au serveur
htpasswdinstalle (paquetapache2-utilssur Debian/Ubuntu)
stack technique
| composant | version | role |
|---|---|---|
| Traefik | v3.2 | Reverse proxy + SSL automatique |
| nginx | alpine | Serveur web statique |
| Docker Compose | v2+ | Orchestration |
2. Architecture Actuelle
structure des fichiers sur le serveur
/opt/ops-imperium/
+-- docker-compose.yml # Configuration Docker
+-- acme.json # Certificats Let's Encrypt
+-- html/ # Fichiers statiques
+-- index.html
+-- blog/
+-- formations/ # <-- Repertoire a proteger
+-- index.html
+-- 0801-iac-generation/
+-- 0802-iac-context/
+-- ...
configuration traefik actuelle
Voici le docker-compose.yml initial (sans protection):
services:
traefik:
image: traefik:v3.2
container_name: traefik
restart: always
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=your@email.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./acme.json:/acme.json"
networks:
- traefik-public
website:
image: nginx:alpine
container_name: ops-imperium-web
restart: always
volumes:
- ./html:/usr/share/nginx/html:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.website.rule=Host(`ops-imperium.com`)"
- "traefik.http.routers.website.entrypoints=websecure"
- "traefik.http.routers.website.tls.certresolver=letsencrypt"
- "traefik.http.services.website.loadbalancer.server.port=80"
networks:
- traefik-public
networks:
traefik-public:
external: true
Cette configuration route TOUT le trafic vers nginx sans authentification.
3. Generer le Hash du Mot de Passe
pourquoi hasher le mot de passe ?
Traefik utilise le format htpasswd pour stocker les credentials. Le mot de passe n'est jamais stocke en clair - on utilise un hash bcrypt.
etape 3.1 : installer htpasswd (si necessaire)
Etape 3.1# Sur Debian/Ubuntu
sudo apt-get update && sudo apt-get install -y apache2-utils
# Sur Alpine (dans un container)
apk add --no-cache apache2-utils
etape 3.2 : generer le hash
Etape 3.2Commande:
htpasswd -nB team
-n: Affiche le resultat sans creer de fichier-B: Utilise bcrypt (recommande, plus securise)
Resultat:
team:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
etape 3.3 : echapper les $ pour docker compose
Etape 3.3important
Dans un fichier docker-compose.yml, le caractere $ est interprete comme une variable d'environnement. Il faut doubler chaque $.
# Commande qui echappe automatiquement
echo $(htpasswd -nB team aidd2026forthewin) | sed -e 's/\$/\$\$/g'
Resultat echappe (exemple):
team:$$2y$$05$$xxxx...
Astuce: Gardez le hash non-echappe dans un gestionnaire de mots de passe, et l'echappe dans votre docker-compose.
4. Modifier la Configuration Traefik
concept : deux routers, un service
Traefik permet de definir plusieurs routers pour un meme service. On va creer:
- Router
website: Pour tout le site (sans auth) - Router
formations: Pour/formations/uniquement (avec BasicAuth)
etape 4.1 : sauvegarder la configuration actuelle
Etape 4.1ssh ops-imperium "cp /opt/ops-imperium/docker-compose.yml /opt/ops-imperium/docker-compose.yml.backup"
etape 4.2 : ajouter les labels basicauth
Etape 4.2Ajoutez ces labels au service website dans docker-compose.yml:
labels:
# === ROUTER PRINCIPAL (tout le site, sans auth) ===
- "traefik.enable=true"
- "traefik.http.routers.website.rule=Host(`ops-imperium.com`) || Host(`www.ops-imperium.com`)"
- "traefik.http.routers.website.entrypoints=websecure"
- "traefik.http.routers.website.tls.certresolver=letsencrypt"
- "traefik.http.services.website.loadbalancer.server.port=80"
- "traefik.http.routers.website.middlewares=security-headers"
# === ROUTER FORMATIONS (avec BasicAuth) ===
- "traefik.http.routers.formations.rule=Host(`ops-imperium.com`) && PathPrefix(`/formations`)"
- "traefik.http.routers.formations.entrypoints=websecure"
- "traefik.http.routers.formations.tls.certresolver=letsencrypt"
- "traefik.http.routers.formations.service=website"
- "traefik.http.routers.formations.middlewares=formations-auth,security-headers"
# === MIDDLEWARE BASICAUTH ===
- "traefik.http.middlewares.formations-auth.basicauth.users=team:$$2y$$05$$VOTRE_HASH_ICI"
Points cles:
service=website: reutilise le meme backend nginx- Les
$$sont les$echappes pour Docker Compose
important : priorite des routeurs
Par defaut, Traefik calcule la priorite des routeurs basee sur la longueur de la regle. Cependant, avec des regles utilisant || (OR), ce calcul peut etre imprevisible. Solution: Definir des priorites explicites (voir section 4.3).
etape 4.3 : configuration complete
Etape 4.3Voici le docker-compose.yml complet avec BasicAuth:
services:
# --- TRAEFIK v3 (Reverse Proxy + SSL automatique) ---
traefik:
image: traefik:v3.2
container_name: traefik
restart: always
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik-public"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=victor@ops-imperium.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
- "--log.level=INFO"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./acme.json:/acme.json"
networks:
- traefik-public
# --- SITE WEB (Nginx) ---
website:
image: nginx:alpine
container_name: ops-imperium-web
restart: always
volumes:
- ./html:/usr/share/nginx/html:ro
labels:
- "traefik.enable=true"
# === ROUTER PRINCIPAL (tout le site) ===
- "traefik.http.routers.website.rule=Host(`ops-imperium.com`) || Host(`www.ops-imperium.com`)"
- "traefik.http.routers.website.entrypoints=websecure"
- "traefik.http.routers.website.tls.certresolver=letsencrypt"
- "traefik.http.services.website.loadbalancer.server.port=80"
- "traefik.http.routers.website.middlewares=security-headers"
# Priorite BASSE pour le routeur general
- "traefik.http.routers.website.priority=10"
# === ROUTER FORMATIONS (avec BasicAuth) ===
- "traefik.http.routers.formations.rule=Host(`ops-imperium.com`) && PathPrefix(`/formations`)"
- "traefik.http.routers.formations.entrypoints=websecure"
- "traefik.http.routers.formations.tls.certresolver=letsencrypt"
- "traefik.http.routers.formations.service=website"
- "traefik.http.routers.formations.middlewares=formations-auth,security-headers"
# Priorite HAUTE pour le routeur specifique (CRITIQUE!)
- "traefik.http.routers.formations.priority=100"
# === MIDDLEWARE BASICAUTH ===
- "traefik.http.middlewares.formations-auth.basicauth.users=team:$$2y$$05$$HASH_A_REMPLACER"
# === MIDDLEWARE SECURITY HEADERS ===
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.security-headers.headers.stsPreload=true"
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.security-headers.headers.browserXssFilter=true"
- "traefik.http.middlewares.security-headers.headers.referrerPolicy=strict-origin-when-cross-origin"
networks:
- traefik-public
networks:
traefik-public:
external: true
5. Deployer et Tester
etape 5.1 : deployer la nouvelle configuration
Etape 5.1cd /opt/ops-imperium
docker-compose up -d
Sortie attendue:
[+] Running 2/2
Container traefik Running
Container ops-imperium-web Started
etape 5.2 : verifier les logs traefik
Etape 5.2docker logs traefik --tail 50
Cherchez les lignes indiquant la creation des routers:
level=info msg="Adding route for formations..."
level=info msg="Adding route for website..."
etape 5.3 : tester l'acces
Etape 5.3Test 1: Page publique (sans auth)
curl -I https://ops-imperium.com/
Devrait retourner 200 OK
Test 2: Page protegee (sans credentials)
curl -I https://ops-imperium.com/formations/
Devrait retourner 401 Unauthorized
Test 3: Page protegee (avec credentials)
curl -I -u team:aidd2026forthewin https://ops-imperium.com/formations/
Devrait retourner 200 OK
etape 5.4 : tester dans le navigateur
Etape 5.4- Ouvrir
https://ops-imperium.com/formations/ - Une popup d'authentification apparait
- Entrer
team/aidd2026forthewin - La page s'affiche
6. Depannage
erreur : 404 not found sur /formations/
Cause: Le fichier index.html n'existe pas dans /formations/
Solution:
ls -la /opt/ops-imperium/html/formations/
# Verifier que index.html existe
erreur : 500 internal server error
Cause: Erreur de syntaxe dans les labels Docker
Solution:
# Verifier la syntaxe YAML
docker-compose config
# Regarder les logs Traefik
docker logs traefik --tail 100 | grep -i error
erreur : basicauth ne fonctionne pas (pas de popup)
Causes possibles:
- Le hash contient des
$non echappes - Le middleware n'est pas attache au router
- Le routeur general capture le trafic avant le routeur protege (cause la plus frequente!)
Diagnostic:
# Test rapide : la page protegee retourne-t-elle 401 ?
curl -s -o /dev/null -w "%{http_code}" https://ops-imperium.com/formations/
# Si retourne 200 au lieu de 401 -> probleme de priorite
Solution: Ajouter des priorites explicites
# Routeur general = priorite BASSE
- "traefik.http.routers.website.priority=10"
# Routeur protege = priorite HAUTE
- "traefik.http.routers.formations.priority=100"
astuce
Plus le numero est eleve, plus la priorite est haute. Le routeur avec la plus haute priorite est evalue en premier.
le mot de passe ne fonctionne pas
Cause: Hash incorrect ou mal echappe
Solution: Regenerer le hash et verifier l'echappement des $
7. Pour Aller Plus Loin
ajouter plusieurs utilisateurs
- "traefik.http.middlewares.formations-auth.basicauth.users=user1:$$hash1,user2:$$hash2"
utiliser un fichier externe pour les credentials
- "traefik.http.middlewares.formations-auth.basicauth.usersfile=/path/to/.htpasswd"
personnaliser le message d'authentification
- "traefik.http.middlewares.formations-auth.basicauth.realm=Formations AIDD"
proteger plusieurs chemins
- "traefik.http.routers.protected.rule=Host(`example.com`) && (PathPrefix(`/admin`) || PathPrefix(`/api`))"
retirer la protection (temporairement)
Commenter les labels du router formations et redeployer:
docker-compose up -d
Vos 4 Points Cles
Deux routers, un service
Traefik permet de definir plusieurs routers pointant vers le meme backend nginx, chacun avec ses propres regles et middlewares.
Echappement des $ obligatoire
Dans docker-compose.yml, chaque $ du hash bcrypt doit etre double ($$) pour eviter l'interpretation comme variable.
Priorites explicites
Definissez toujours des priorites explicites (ex: 100 vs 10) pour garantir que le routeur specifique est evalue avant le routeur general.
Tester avec curl d'abord
Validez avec curl -I avant de tester dans le navigateur. Les codes 200/401 confirment que le routage fonctionne.
Besoin d'aide sur Traefik ?
Je propose des missions d'accompagnement sur l'infrastructure Docker, Traefik et l'automatisation DevOps.
Discutons de votre projet