Last updated on

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:

  1. Changer email
  2. Changer password (fort!)
  3. 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..com

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 → Jellyfin
  • https://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:

  1. DNS pas propagé → Wait 5-10 min
  2. Port 80/443 pas ouvert → Check firewall/router
  3. 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:

  1. Service backend down → Check docker compose ps
  2. Mauvais IP/port dans config → Vérifier forward settings
  3. 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:

  1. ☑️ Websockets Support dans proxy host
  2. 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%