Nginx Proxy Manager - Reverse Proxy avec SSL automatique
🌐 Pourquoi un Reverse Proxy?
Nginx Proxy Manager (NPM) permet d’exposer plusieurs services sur ports 80/443 avec:
- ✅ Sous-domaines:
jellyfin.example.com,sonarr.example.com - ✅ SSL automatique: Certificats Let’s Encrypt gratuits
- ✅ Interface graphique: Configuration sans éditer nginx.conf
- ✅ Access Lists: Authentification basique HTTP
- ✅ Custom locations: Réécriture URL avancée
Scénario sans proxy:
http://<SERVER_IP>:PORT → Jellyfin
http://<SERVER_IP>:PORT → Sonarr
http://<SERVER_IP>:PORT → Radarr
Avec proxy:
https://jellyfin.<your-domain>.com → <SERVER_IP>:PORT
https://sonarr.<your-domain>.com → <SERVER_IP>:PORT
https://radarr.<your-domain>.com → <SERVER_IP>:PORT
🐳 Installation Docker
docker-compose.yml
version: '3.8'
services:
nginx-proxy-manager:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- '80:80' # HTTP
- '443:443' # HTTPS
- '81:81' # Admin UI
environment:
DB_SQLITE_FILE: "/data/database.sqlite"
DISABLE_IPV6: 'true'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy
networks:
proxy:
driver: bridge
Déploiement
# Créer structure
mkdir -p nginx-proxy-manager/{data,letsencrypt}
cd nginx-proxy-manager
# Créer docker-compose.yml
# Démarrer
docker compose up -d
# Logs
docker compose logs -f
# Status
docker compose ps
Premier Accès
URL: http://<SERVER_IP>:81
Email: admin@example.com
Password: changeme
Immédiatement après login:
- Changer email
- Changer password (fort!)
- Mettre à jour nom/prénom
🔧 Configuration Initiale
1. Settings Globaux
Settings > Default Site
Enable Default Site: ☑️
Default Site: Create custom "503 Service Unavailable"
Settings > SSL
HTTP/2 Support: ☑️ (enabled par défaut)
HSTS Enabled: ☑️
HSTS Subdomains: ☑️
2. Certificats SSL Let’s Encrypt
SSL Certificates > Add SSL Certificate > Let’s Encrypt
Domain Names: *.<your-domain>.com
<your-domain>.com
Email: your-email@example.com
Use DNS Challenge: ☑️ (pour wildcard)
DNS Provider: (choisir provider)
Credentials: (API token provider)
Providers supportés:
- Cloudflare (le plus courant)
- OVH
- Google Cloud DNS
- AWS Route53
- …
Exemple Cloudflare:
DNS Provider: Cloudflare
Credentials File Content:
dns_cloudflare_email = your-email@cloudflare.com
dns_cloudflare_api_key = YOUR_GLOBAL_API_KEY
Cliquer Save → Certificat auto-généré (valide 90j, auto-renew)
🎯 Créer Proxy Hosts
Exemple: Jellyfin
Hosts > Proxy Hosts > Add Proxy Host
Details Tab:
Domain Names: jellyfin.<your-domain>.com
Scheme: http
Forward Hostname/IP: <SERVER_IP>
Forward Port: 8096
Cache Assets: ☑️
Block Common Exploits: ☑️
Websockets Support: ☑️
SSL Tab:
SSL Certificate: *.<your-domain>.com (sélectionner wildcard)
Force SSL: ☑️
HTTP/2 Support: ☑️
HSTS Enabled: ☑️
Advanced Tab (optionnel):
# Configuration custom nginx
client_max_body_size 0;
# Headers pour Jellyfin
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
Cliquer Save → Accessible via https://jellyfin.
Exemple: Sonarr
Details:
Domain Names: sonarr.<your-domain>.com
Scheme: http
Forward Hostname/IP: <SERVER_IP>
Forward Port: 8989
Websockets Support: ☑️
SSL: Même wildcard cert
Advanced:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
Exemple: Home Assistant
Details:
Domain Names: hass.<your-domain>.com
Scheme: http
Forward Hostname/IP: <SERVER_IP>
Forward Port: 8123
Websockets Support: ☑️ (OBLIGATOIRE pour HA)
SSL: Wildcard cert
Advanced:
# Home Assistant requires specific headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
Configuration Home Assistant (configuration.yaml):
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.0.0/24
- 172.17.0.0/16 # Docker network
🔐 Access Lists (Authentification)
Protéger services sensibles avec login/password HTTP Basic Auth.
Créer Access List
Access Lists > Add Access List
Name: Admin Services
Satisfy Any: ☐ (require ALL conditions)
Authorization Tab:
Username: admin
Password: YOUR_SECURE_PASSWORD
Ajouter plusieurs users si besoin.
Access Tab:
Allow: 192.168.0.0/24 (LAN bypass auth)
Deny: (vide = deny all others)
Cliquer Save.
Appliquer Access List
Éditer Proxy Host (ex: Sonarr):
Details Tab:
Access List: Admin Services (sélectionner)
Maintenant accéder à https://sonarr.<your-domain>.com demande login/password!
📍 Custom Locations
Servir plusieurs services sous même domaine.
Exemple: Multi-services
Créer Proxy Host:
Domain: services.<your-domain>.com
Forward: (laisser vide ou default service)
Custom Locations Tab > Add Location:
Location 1 (Jellyfin):
Define Location: /jellyfin
Scheme: http
Forward Hostname/IP: <SERVER_IP>
Forward Port: 8096
Advanced:
rewrite ^/jellyfin(/.*)$ $1 break;
Location 2 (Sonarr):
Define Location: /sonarr
Scheme: http
Forward Hostname/IP: <SERVER_IP>
Forward Port: 8989
Advanced:
proxy_set_header X-Forwarded-Prefix /sonarr;
Résultat:
https://services.<your-domain>.com/jellyfin→ Jellyfinhttps://services.<your-domain>.com/sonarr→ Sonarr
🌐 Configuration DNS
Domaine Public (Cloudflare/OVH)
Si exposé sur Internet:
Créer records DNS A/CNAME:
Type Name Target Proxy
A @ YOUR_PUBLIC_IP ☑️ (Cloudflare)
CNAME * <your-domain>.com ☑️
CNAME jellyfin <your-domain>.com ☑️
CNAME sonarr <your-domain>.com ☑️
Port Forwarding Router:
External Port 80 → <SERVER_IP>:80
External Port 443 → <SERVER_IP>:443
Domaine Local (Pi-hole/DNS local)
Si LAN uniquement (recommandé):
Ajouter DNS local records dans Pi-hole:
Pi-hole > Local DNS > DNS Records
Domain IP
jellyfin.<your-domain>.com <SERVER_IP>
sonarr.<your-domain>.com <SERVER_IP>
radarr.<your-domain>.com <SERVER_IP>
hass.<your-domain>.com <SERVER_IP>
Ou créer CNAME records:
jellyfin.<your-domain>.com → server.local
sonarr.<your-domain>.com → server.local
Avantage: Pas d’exposition Internet, fonctionne que sur LAN.
🛡️ Sécurité
1. Fail2ban
Bannir IPs après tentatives login échouées.
Installer Fail2ban (sur host):
sudo apt install fail2ban -y
Config NPM (/etc/fail2ban/filter.d/npm-general.conf):
[Definition]
failregex = ^.*"client".*"status":401.*$
^.*"client".*"status":403.*$
ignoreregex =
Jail (/etc/fail2ban/jail.d/npm.conf):
[npm-general]
enabled = true
port = http,https
filter = npm-general
logpath = /home/user/nginx-proxy-manager/data/logs/*.log
maxretry = 3
bantime = 3600
findtime = 600
Restart:
sudo systemctl restart fail2ban
sudo fail2ban-client status npm-general
2. Rate Limiting
Limiter requêtes par IP.
Advanced config proxy host:
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
limit_req zone=one burst=20 nodelay;
3. Headers Sécurité
Advanced config:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
4. Limiter Access Admin UI
Port 81 uniquement accessible depuis LAN:
UFW Firewall:
sudo ufw allow from 192.168.0.0/24 to any port 81
sudo ufw deny 81
Ou modifier docker-compose.yml:
ports:
- '127.0.0.1:81:81' # Bind localhost only
📊 Logs & Monitoring
Consulter Logs
Web UI: Hosts > … menu > View Logs
Container logs:
docker compose logs -f
# Logs nginx access
docker compose exec nginx-proxy-manager tail -f /data/logs/proxy-host-*.log
# Logs errors
docker compose exec nginx-proxy-manager tail -f /data/logs/error.log
Stats Nginx
Advanced config pour activer stub_status:
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.0.0/24;
deny all;
}
Accès: http://<SERVER_IP>:81/nginx_status
🛠️ Maintenance
Renouvellement Certificats
Automatique! Let’s Encrypt valide 90 jours, NPM auto-renew à 30 jours.
Vérifier: SSL Certificates → Check expiration date
Forcer renewal manuel:
docker compose exec nginx-proxy-manager certbot renew --force-renewal
Backup Configuration
# Backup data + certs
tar -czf npm-backup-$(date +%Y%m%d).tar.gz \
nginx-proxy-manager/data \
nginx-proxy-manager/letsencrypt
# Restore
tar -xzf npm-backup-YYYYMMDD.tar.gz
docker compose up -d
Update NPM
docker compose pull
docker compose up -d --force-recreate
🐛 Troubleshooting
Certificat SSL échoue
Erreur: “Failed to obtain certificate”
Causes:
- DNS pas propagé → Wait 5-10 min
- Port 80/443 pas ouvert → Check firewall/router
- API credentials DNS provider invalides → Re-check token
Solution DNS Challenge (wildcard):
- Vérifier API token a permissions DNS edit
- Tester manuellement:
nslookup _acme-challenge.example.com
502 Bad Gateway
Erreur: Nginx retourne 502
Causes:
- Service backend down → Check
docker compose ps - Mauvais IP/port dans config → Vérifier forward settings
- Firewall block backend → Test
curl http://<SERVER_IP>:PORT
Debug:
# Check backend accessible
curl -I http://<SERVER_IP>:PORT
# Logs nginx
docker compose logs | grep 502
Websockets pas fonctionnels
Symptôme: Home Assistant/Jellyfin déconnexions
Solution:
- ☑️ Websockets Support dans proxy host
- Ajouter headers:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
🎓 Ressources
Status: 🟢 Running
Proxy Hosts: 8 configured
SSL Certs: 1 wildcard (auto-renew)
Uptime: 99.9%