/articoli
14 min di lettura

Nginx Proxy Manager: setup e sicurezza

Nginx Proxy Manager con Docker: Compose aggiornato alla 2.15.1, perché le credenziali di default non esistono più, porta 81, wildcard e backup.


Nginx Proxy Manager è un reverse proxy basato su Nginx che si configura da un'interfaccia web invece che da file di testo: gira come singolo container Docker, instrada i tuoi domini verso i servizi che hai in casa o su un server, e richiede i certificati HTTPS di Let's Encrypt con qualche clic. La versione corrente è la 2.15.1, pubblicata il 3 giugno 2026. Porta con sé un cambiamento che manda fuori strada chi segue un tutorial vecchio: le credenziali di default admin@example.com e changeme non esistono più dalla 2.13.0 del 4 novembre 2025, sostituite da una procedura guidata al primo accesso. Se stai seguendo istruzioni che ti dicono di entrare con quelle, è per questo che non entri.

Cos'è Nginx Proxy Manager e a cosa serve

È un pannello web che genera configurazioni Nginx al posto tuo e gestisce i certificati con Certbot dietro le quinte, distribuito come immagine Docker jc21/nginx-proxy-manager. Invece di scrivere un blocco server per ogni dominio, ricaricare Nginx a ogni modifica e programmare un cron job per il rinnovo dei certificati, apri una pagina, compili quattro campi e il servizio risponde sul suo sottodominio in HTTPS.

Il progetto è nato a dicembre 2017 con licenza MIT e oggi conta 33.759 stelle e 3.841 fork su GitHub, con oltre 263 milioni di download dell'immagine su Docker Hub. Oltre ai proxy host classici gestisce redirect, host 404 e stream TCP o UDP, liste di accesso con autenticazione HTTP basic, configurazione Nginx personalizzata e gestione utenti con permessi e log delle azioni.

La differenza rispetto a un reverse proxy pensato per i container sta nel modello di configurazione. Con Traefik come reverse proxy Docker descrivi l'instradamento con etichette dentro il docker-compose.yml, così la configurazione vive nello stesso file che committi in Git. Qui invece vive in un database e in file generati dentro un volume, e la interroghi da browser. Sono due modi di lavorare abbastanza diversi da meritare una sezione a parte.

Come si installa Nginx Proxy Manager con Docker Compose

Serve solo Docker con il plugin Compose e un file di poche righe. Questa è la configurazione minima con il database SQLite, che è il default del progetto e per un homelab è la scelta giusta:

services:
  app:
    image: 'jc21/nginx-proxy-manager:2.15.1'
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
      - '127.0.0.1:81:81'
    environment:
      TZ: 'Europe/Rome'
      # Da attivare solo se l'host non ha IPv6
      # DISABLE_IPV6: 'true'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

Poi docker compose up -d e il primo avvio fa tre cose: genera le chiavi JWT nella cartella data, crea la struttura delle tabelle nel database e prepara l'utente amministratore. Su hardware modesto può richiedere qualche minuto prima che la porta 81 risponda, quindi un timeout al primo tentativo non è un errore di configurazione.

Nell'esempio ci sono due scelte che vale spiegare. La prima è il tag fissato invece del solito latest, che su un reverse proxy significa accettare un aggiornamento a sorpresa al prossimo docker compose pull. La 2.15.0 è arrivata con una nuova immagine base Debian Trixie e un avviso esplicito che i plugin DNS potrebbero smettere di funzionare. La seconda è la porta 81 legata al solo indirizzo locale, e su quella torno tra poco.

Sull'hardware ARM l'immagine è multi architettura e copre amd64 e arm64, ma il supporto armv7 è stato rimosso dalla 2.14 in poi perché Node.js ha smesso di supportare armhf. Se hai un Raspberry Pi vecchio a 32 bit devi restare sul tag 2.13.7, l'ultima immagine costruita per quella architettura.

Perché admin@example.com e changeme non funzionano più

Perché quelle credenziali sono state rimosse dal codice nella versione 2.13.0, pubblicata il 4 novembre 2025, e le note di rilascio lo dicono in una riga: "New setup wizard; no more default initial user (but keeps the env vars for advanced users)". Fino alla 2.12.6 del 9 luglio 2025 lo script di inizializzazione creava l'utente con l'indirizzo admin@example.com e la password changeme quando non trovava nessun utente attivo, e da quella versione in avanti quel fallback non c'è più.

Oggi le strade sono due, e quella normale è aprire l'interfaccia sulla porta 81 e lasciarsi guidare dalla configurazione iniziale, dove scegli tu indirizzo e password del primo amministratore. La seconda serve a chi automatizza il deploy: impostando le variabili d'ambiente INITIAL_ADMIN_EMAIL e INITIAL_ADMIN_PASSWORD, il container crea l'utente con quei valori al primo avvio e salta la procedura guidata.

Su quella seconda strada c'è un problema che si vede solo leggendo il codice di inizializzazione. Quando le variabili sono presenti, il container scrive nei propri log una riga con la password in chiaro, perché il messaggio informativo compone indirizzo e password nello stesso testo, e quella riga finisce su disco e in qualunque sistema di raccolta log tu abbia collegato. Se non ti serve un deploy completamente automatico la procedura guidata resta la scelta più pulita, e se ti serve, cambia quella password dall'interfaccia subito dopo il primo accesso.

Quali porte servono e perché la 81 non va mai esposta

Servono tre porte, e solo due vanno verso internet. La 80 e la 443 sono quelle pubbliche, e devono essere raggiungibili dall'esterno perché Let's Encrypt possa validare i domini con la sfida HTTP e perché i tuoi servizi rispondano. La 81 è il pannello di amministrazione, e non ha niente a che vedere con il traffico dei tuoi siti.

Il pannello sulla 81 parla HTTP in chiaro, senza TLS. Quasi tutti gli esempi in circolazione lo pubblicano su tutte le interfacce con la riga '81:81', e se poi apri anche quella porta sul router stai esponendo un pannello di amministrazione senza cifratura, dove la password viaggia leggibile e chiunque trovi l'indirizzo può provare combinazioni. Nell'esempio più sopra è legata a 127.0.0.1, così risponde solo dalla macchina stessa e la raggiungi con un tunnel SSH o da una VPN. Se ti serve dalla rete locale, legala all'indirizzo privato della macchina.

Il rischio non è teorico, e la 2.15.0 lo dimostra: ha chiuso una falla per cui un utente autenticato qualsiasi poteva modificare il proprio campo dei ruoli con una richiesta PUT, promuovendosi da solo. Nella stessa versione sono state aggiornate le librerie OpenResty per tre CVE, identificate come CVE-2026-42945, CVE-2026-8711 e CVE-2026-9256. Un pannello raggiungibile solo dalla tua rete trasforma una falla del genere in un problema da sistemare al prossimo aggiornamento, invece che in una corsa contro chi scansiona internet. Se usi la funzione stream per inoltrare traffico TCP o UDP, quelle porte vanno pubblicate a mano nel Compose.

Come si configura il primo proxy host con HTTPS

Dalla schermata Hosts scegli Proxy Hosts, poi Add Proxy Host, e i campi che contano sono quattro: dominio, schema, host di destinazione e porta interna del servizio. Prima di compilarli serve un record DNS di tipo A che punti il dominio al tuo IP pubblico, altrimenti la richiesta del certificato fallisce senza che il pannello spieghi perché.

Il campo dell'host di destinazione è quello dove si sbaglia più spesso. Se il servizio gira in un altro container sulla stessa rete Docker del proxy, il valore giusto è il nome del container, non un indirizzo IP e soprattutto non 127.0.0.1, che dentro il container del proxy significa il proxy stesso. La porta da indicare è quella su cui il servizio ascolta dentro la sua rete, non quella pubblicata verso l'host. Un gestore di password come Vaultwarden dietro il suo reverse proxy o un'istanza di n8n installata con Docker Compose seguono la stessa regola: entrano nella rete condivisa e vengono raggiunti per nome.

Nella stessa schermata due interruttori risolvono problemi concreti. Websockets Support serve alle applicazioni che tengono aperta una connessione per aggiornarsi da sole, e senza quella spunta l'interfaccia sembra caricare ma poi resta ferma. Block Common Exploits aggiunge un filtro sulle richieste malformate più banali. Poi passi alla scheda SSL, chiedi un nuovo certificato Let's Encrypt e attivi Force SSL insieme a HTTP/2. Dalla 2.13.0 la richiesta del certificato non chiede più l'indirizzo email né l'accettazione dei termini di servizio, e un tutorial che te li descrive è più vecchio di quella versione.

Come ottenere un certificato wildcard con la sfida DNS

Un certificato per *.tuodominio.it non si può ottenere con la sfida HTTP, serve la sfida DNS, dove Certbot dimostra il controllo del dominio scrivendo un record TXT tramite le API del tuo provider. Nel pannello scegli il provider dalla lista quando richiedi il certificato, inserisci il token API e il container installa da solo il plugin Certbot corrispondente.

La lista dei provider supportati nella 2.15.1 conta 86 voci, definite in un file di configurazione del repository, e comprende Cloudflare, Route 53, DigitalOcean, DuckDNS, Azure, Hetzner e la maggior parte dei registrar diffusi. Su questi plugin la documentazione ufficiale è più onesta di quanto uno si aspetti: non sono mantenuti dal progetto. Sono pacchetti indipendenti, alcuni restano indietro rispetto alle versioni correnti di Certbot, alcuni installano dipendenze che entrano in conflitto con altri componenti, e non tutti vengono provati dentro Nginx Proxy Manager.

Da questo derivano due regole pratiche. La prima è usare un solo provider DNS per istanza, perché due plugin diversi nello stesso container possono litigare sulle dipendenze Python e rompersi a vicenda. La seconda è leggere i log del container quando un certificato wildcard non arriva: nove volte su dieci l'errore sta nell'installazione del pacchetto Python e non nel token o nel record DNS. La 2.15.0 ha portato Certbot alla 5.6.0 su una base Debian nuova, e le note di rilascio avvertono di procedere con cautela proprio per questo.

Quale database conviene usare

SQLite, che è già il default, e su una macchina domestica o su un singolo server non c'è motivo di cambiarlo. Il file del database vive nel volume data insieme al resto dello stato, non richiede un secondo container, e regge senza problemi le poche decine di host che un homelab arriva a gestire.

Il progetto supporta anche MySQL o MariaDB e Postgres, con le variabili DB_MYSQL_* e DB_POSTGRES_* documentate negli esempi ufficiali. Se prendi quella strada, attenzione a un comportamento che la documentazione segnala e che fa perdere tempo: le variabili DB_MYSQL_* hanno la precedenza su quelle di SQLite, per cui se le lasci nel file anche solo per prova, SQLite non verrà più usato. Su Postgres lo schema personalizzato non è supportato e viene usato sempre lo schema public.

Se trovi scritto di appoggiarsi a MySQL 5.7, lascia perdere. Il supporto esteso di Oracle per quella versione è terminato il 31 ottobre 2023: sono oltre due anni senza patch di sicurezza per il database che tiene le credenziali del tuo proxy. Quando serve davvero un database esterno, MariaDB in versione mantenuta o Postgres sono le opzioni sensate.

Come si fa il backup e come si verifica il ripristino

Tutto lo stato sta nelle due cartelle montate come volumi: data contiene il database SQLite, le chiavi JWT e le configurazioni Nginx generate, mentre letsencrypt contiene i certificati con le loro chiavi private. Copiare quelle due cartelle è il backup completo, e con SQLite non serve nient'altro.

La procedura che funziona è fermare il container prima di copiare, così il file SQLite non viene archiviato durante una scrittura, quindi creare un archivio e riavviare:

docker compose down
tar czf npm-backup-$(date +%F).tar.gz data letsencrypt
docker compose up -d

Il ripristino consiste nell'estrarre l'archivio in una cartella vuota accanto a un docker-compose.yml che dichiara la stessa versione dell'immagine da cui il backup è stato preso, poi avviare. Provalo almeno una volta in una cartella separata con porte cambiate. Finché non lo estrai, quell'archivio resta solo una speranza. Se perdi le chiavi JWT dentro data le sessioni aperte diventano non valide e tutti gli utenti devono rientrare, che non è grave ma spiega un comportamento altrimenti misterioso dopo un ripristino parziale. Se hai scelto un database esterno il dump va aggiunto alla procedura, perché in quel caso la cartella data da sola non contiene più i tuoi host.

Cosa fare quando un servizio risponde 502 Bad Gateway

Nella grande maggioranza dei casi il 502 significa che il proxy non riesce a raggiungere il servizio dietro di lui, e le cause sono sempre le stesse quattro. La prima è che il container di destinazione non sta sulla stessa rete Docker del proxy, quindi il suo nome non viene risolto: si verifica con docker network inspect, controllando che entrambi i container compaiano nell'elenco.

La seconda causa è l'host di destinazione impostato su localhost o 127.0.0.1, che dentro il container del proxy punta al proxy stesso e non alla macchina. La terza è la porta sbagliata, tipicamente quella pubblicata verso l'host invece di quella interna su cui il servizio ascolta davvero. La quarta è un servizio configurato per ascoltare solo sull'interfaccia di loopback dentro il proprio container, quindi raggiungibile da sé stesso e da nessun altro, e in quel caso la modifica va fatta in quel servizio e non nel proxy.

Quando nessuna delle quattro spiega il problema, i log stanno in due posti e conviene guardarli entrambi. Quelli del container si leggono con docker compose logs -f, mentre i log di Nginx per singolo host stanno nella cartella data/logs dentro il volume, con un file separato per ogni proxy host, ed è lì che compare l'errore preciso con l'indirizzo che Nginx ha provato a contattare. Quell'indirizzo confrontato con quello che ti aspettavi chiude la diagnosi in un minuto.

Quando conviene questo strumento e quando invece Traefik

Nginx Proxy Manager va bene quando i servizi sono pochi e cambiano raramente, quando preferisci un pannello a una sintassi da imparare, e soprattutto quando non sei l'unica persona che deve poter aggiungere un host. Esiste anche come script della community per chi parte da un ambiente virtualizzato come quello descritto negli script helper di Proxmox, e il tempo dall'installazione al primo servizio in HTTPS si misura in minuti.

Traefik conviene quando i container nascono e muoiono spesso, perché l'instradamento sta nelle etichette e segue il servizio invece di restare in un pannello separato. Ha senso anche quando vuoi middleware come limitazione delle richieste e header di sicurezza applicati per router, o quando ti interessa che la configurazione del proxy sia versionata insieme al resto dello stack. Caddy è la terza opzione ragionevole, con il file di configurazione più corto dei tre e HTTPS automatico, senza interfaccia grafica.

Il limite di questo strumento sta in come conserva la sua configurazione. Non è un file che committi, è un database più una serie di file generati, per cui ricostruire un'istanza identica da zero significa ripristinare un backup invece di rilanciare un Compose. Per un homelab personale il compromesso è accettabile. Su qualcosa che devi poter ricreare in modo riproducibile, per esempio l'infrastruttura descritta nel deploy di un'app Laravel su VPS con Docker Compose, un proxy configurato da file resta più adatto. Se stai imparando e vuoi vedere i tuoi servizi online in HTTPS stasera, parti da qui: quando la configurazione da browser ti starà stretta avrai comunque capito cosa fa un reverse proxy, e quella parte te la porti dietro su qualunque strumento.

Domande frequenti su Nginx Proxy Manager

Quali sono le credenziali di default di Nginx Proxy Manager? Non esistono più: l'utente admin@example.com con password changeme è stato rimosso nella versione 2.13.0 del 4 novembre 2025, sostituito da una configurazione guidata al primo accesso sulla porta 81, dove scegli tu indirizzo e password. Per creare l'utente in modo automatico durante un deploy esistono le variabili INITIAL_ADMIN_EMAIL e INITIAL_ADMIN_PASSWORD, con l'avvertenza che quella password finisce in chiaro nei log del container.

Su quale porta si accede a Nginx Proxy Manager? Sulla porta 81, mentre la 80 e la 443 servono al traffico dei siti che il proxy inoltra. La 81 parla HTTP senza cifratura, quindi conviene legarla a 127.0.0.1 o all'indirizzo della rete locale e raggiungerla via tunnel SSH o VPN, mai aprirla sul router.

Nginx Proxy Manager è sicuro da esporre su internet? Le porte 80 e 443 sono fatte per essere esposte, ed è così che funziona qualunque reverse proxy. Il pannello sulla 81 non deve mai essere raggiungibile da internet, perché non usa TLS e perché la 2.15.0 ha corretto una falla che permetteva a un utente autenticato di modificare i propri ruoli. Versione aggiornata e pannello sulla rete privata coprono la parte più grande del rischio reale.

Serve un dominio vero per avere HTTPS? Sì, per un certificato Let's Encrypt valido serve un dominio o sottodominio che punti al tuo indirizzo pubblico, con le porte 80 e 443 raggiungibili dall'esterno per la sfida HTTP. La sfida DNS è l'alternativa che non richiede porte aperte, ma richiede un provider supportato e un token API.

Funziona su un Raspberry Pi? Sì sulle schede a 64 bit, perché l'immagine copre arm64 oltre a amd64. Sulle schede più vecchie a 32 bit il supporto armv7 è stato rimosso dalla 2.14 in avanti, quindi l'ultima immagine utilizzabile è quella con tag 2.13.7, che però non riceve più correzioni.

Posso usarlo se la porta 80 è già occupata da un altro server web? Non sulla stessa porta, perché due processi non possono ascoltare sullo stesso indirizzo. Le strade sono due: spostare l'altro server web dietro il proxy, trasformandolo in uno dei servizi inoltrati, oppure pubblicare il proxy su porte diverse. La prima è quasi sempre la scelta giusta, perché avere un solo punto di ingresso è il motivo per cui si installa un reverse proxy.


Grazie per aver letto fin qui.

Se l'articolo ti è stato utile, iscriviti alla newsletter per ricevere il prossimo direttamente in casella, oppure aggiungi il feed RSS al tuo lettore.

// similarity

// iscriviti

Ricevi il prossimo articolo, via email.