|
NGINX Proxy Manager: come creare una pagina 404 personalizzata per host non configurati con wildcard SSL

Se gestisci un server con Nginx Proxy Manager e un dominio principale con diversi sottodomini attivi, prima o poi ti trovi a fare i conti con una lacuna apparentemente banale: cosa succede quando qualcuno raggiunge un sottodominio che non esiste o non è piu configurato?
La risposta predefinita di NPM non è esattamente elegante. E di certo non dice nulla di utile ne ai visitatori umani, ne ai bot dei motori di ricerca che potrebbero ancora avere in cache vecchi URL. Ho affrontato questo problema su uno dei miei VPS e, dopo un pò di sperimentazione, sono arrivato a una soluzione pulita che voglio condividere.
Il problema
Con un wildcard DNS del tipo *.example.com che punta all’IP del server, qualsiasi sottodominio viene instradato a NPM. Ma se NPM non ha un proxy host configurato per quell’host, il comportamento dipende dalla configurazione del “Default Site”: di default dovrebbe mostrare la pagina di benvenuto di NPM.
Il vero problema emerge con HTTPS. Anche volendo personalizzare la risposta per HTTP, una richiesta HTTPS su un sottodominio sconosciuto causa un errore TLS nel browser (“Connessione sicura non riuscita”) perche NPM non ha un certificato valido da presentare per quell’host. L’utente non arriva mai a vedere nessuna pagina.
L’obiettivo e quindi duplice: intercettare sia le richieste HTTP che HTTPS verso host non configurati, mostrando in entrambi i casi una pagina 404 personalizzata senza errori TLS.
La soluzione: come creare una pagina 404 personalizzata in NGINX Proxy Manager
La soluzione richiede quattro configurazioni nella UI di NPM e due file da creare manualmente nel volume. Nessun file auto-generato da NPM viene modificato, quindi gli aggiornamenti del container teoricamente non rompono nulla.
| Componente | Dove | Scopo |
|---|---|---|
| Wildcard DNS | Provider DNS | Instrada tutti i sottodomini al server |
| Certificato wildcard LE | NPM UI | SSL valido per qualsiasi sottodominio |
Dead host *.example.com | NPM UI | Intercetta richieste a host non configurati |
| Default Site > Custom HTML | NPM UI | Pagina 404 personalizzata per HTTP |
http.conf | File custom | Catch-all HTTPS con pagina 404 |
server_dead.conf | File custom | Redirect HTTP > HTTPS |
Step 1: Wildcard DNS
Nel pannello del tuo provider DNS crea un record A di tipo wildcard:
*.example.com A IP_DEL_SERVER
Questo fa si che qualsiasi sottodominio — esistente o inventato — raggiunga il tuo server. I record specifici che hai gia configurato (blog, git, ecc.) hanno la precedenza sul wildcard, quindi i servizi attivi non vengono toccati.
Vale la pena verificare la propagazione DNS prima di procedere con i passi successivi, altrimenti i test di raggiungibilita richiesti da Let’s Encrypt potrebbero fallire.
Step 2: Certificato wildcard Let’s Encrypt
Un certificato per un singolo sottodominio non basta: serve un certificato wildcard che copra *.example.com, cosi nginx può presentarlo per qualsiasi sottodominio in entrata.
In NPM vai su SSL Certificates > Add SSL Certificate > Let’s Encrypt:
- Domain Names:
*.example.com - Use a DNS Challenge: abilitato (obbligatorio per i wildcard)
- Seleziona il tuo provider DNS e inserisci le credenziali API
La DNS challenge è necessaria perchè Let’s Encrypt non puo verificare un wildcard via HTTP. NPM supporta diversi provider (OVH, Cloudflare, Hetzner, ecc.) tramite i plugin certbot-dns. Una volta emesso, NPM gestisce il rinnovo automaticamente.
Per chi usa OVH, le credenziali API si generano a questo indirizzo che pre-compila automaticamente i permessi necessari per la DNS challenge (lettura, creazione e cancellazione record TXT sulla zona DNS):
https://api.ovh.com/createToken/index.cgi?GET=/domain/zone*&PUT=/domain/zone*&POST=/domain/zone*&DELETE=/domain/zone*
Imposta la validita su Unlimited per evitare che il rinnovo automatico del certificato fallisca alla scadenza del token.
Se ricevi un errore JSON durante la creazione, prova a ripetere: in alcuni casi si tratta di un timeout transitorio durante la comunicazione con il backend certbot.
Step 3: Dead Host in NPM
In NPM vai su Hosts > 404 Hosts > Add 404 Host:
- Domain Names:
*.example.com - SSL Certificate: seleziona il certificato wildcard creato allo step precedente
Il Dead Host è il meccanismo di NPM per rispondere con 404 a host specifici. Con il wildcard, intercetterà tutte le richieste a sottodomini che non hanno un proxy host dedicato. Questo permette anche al certificato wildcard di essere usato per il TLS handshake, risolvendo il problema degli errori HTTPS.
Step 4: Default Site > Custom HTML
In NPM vai su Settings > Default Site e seleziona Custom Page. Incolla qui il tuo HTML personalizzato per la pagina 404.
NPM salva questo contenuto in /data/nginx/default_www/index.html nel volume del container. Questa sara la pagina servita per le richieste HTTP agli host non configurati.
Ecco il codice della pagina che uso sul mio server: stile terminal dark, zero dipendenze esterne, bilingue IT/EN, con hostname e path popolati dinamicamente via JavaScript:
Alcune note su questa pagina. Per il contenuto della pagina, l’unico vincolo tecnico è che l’HTML non deve contenere singoli apici (') perche NPM li usa come delimitatori nella configurazione nginx generata. Usa sempre il doppio apice negli attributi HTML e in JavaScript. L’hostname e il path vengono letti da window.location via JavaScript e iniettati nel DOM a runtime: il visitatore vede esattamente il sottodominio che ha tentato di raggiungere, senza bisogno di configurazioni lato server. Il meta tag noindex, nofollow istruisce i crawler a non indicizzare la pagina. Zero dipendenze esterne: niente CDN, niente Google Fonts, tutto self-contained.
Step 5: File custom — http.conf
Questo è il pezzo piu interessante della soluzione. NPM include automaticamente i file presenti in /data/nginx/custom/ in punti precisi della configurazione nginx, senza che tu debba modificare nulla di auto-generato.
In particolare, http.conf viene incluso alla fine del blocco HTTP principale, il che ti permette di aggiungere un server block personalizzato per HTTPS.
Crea il file /data/nginx/custom/http.conf:
Sostituisci npm-XX con l’ID del tuo certificato wildcard. Il modo più semplice per trovarlo è in NPM sotto SSL Certificates: clicca sui tre puntini accanto al certificato wildcard e l’ID è visibile direttamente ad esempio npm-46, quindi il path sarà /etc/letsencrypt/live/npm-46/.
Questo server block usa default_server sulla porta 443, quindi cattura qualsiasi richiesta HTTPS il cui server_name non corrisponde a nessun altro host configurato. Presenta il certificato wildcard (TLS handshake riuscito, nessun errore browser), poi serve la tua pagina personalizzata con status 404.
Step 6: File custom — server_dead.conf
NPM include automaticamente server_dead.conf alla fine di ogni server block del tipo Dead Host. Lo usiamo per aggiungere il redirect HTTP > HTTPS:
Crea il file /data/nginx/custom/server_dead.conf:
# Redirect HTTP to HTTPS for dead hosts
if ($scheme = "http") {
return 301 https://$host$request_uri;
}
La direttiva if a livello server viene eseguita nella fase rewrite di nginx, prima del return 404 che NPM inserisce nel blocco location /. Il redirect ha quindi la precedenza: una richiesta HTTP al Dead Host viene reindirizzata a HTTPS, dove interviene il server block di http.conf.
Step 7: Reload
Dopo aver creato i due file, verifica la configurazione nginx e ricarica:
docker exec nome-container nginx -t
docker exec nome-container nginx -s reload
Se il test di configurazione restituisce errori, controlla che npm-XX in http.conf corrisponda all’ID reale del certificato.
Come funziona il flusso completo
Richiesta HTTP a un sottodominio inesistente:
- Il wildcard DNS instrada la richiesta al server
- NPM non trova un proxy host corrispondente
- Il Dead Host
*.example.comintercetta la richiesta server_dead.confesegue redirect 301 a HTTPS- La richiesta riparte in HTTPS (vedi sotto)
Richiesta HTTPS a un sottodominio inesistente:
- Il wildcard DNS instrada la richiesta al server
- Il server block in
http.conf(porta 443,default_server) intercetta la richiesta - Il certificato wildcard garantisce un TLS handshake valido
- nginx serve la pagina personalizzata da
/data/nginx/default_www/index.htmlcon status 404
Vuoi vederla in azione?
Prova tu stesso: apri http://pincopallino.emanuelegori.uno – un sottodominio che non esiste e non esisterà mai. Dovresti vedere esattamente la pagina descritta in questo articolo, servita via HTTPS con certificato wildcard valido e pagina 404 personalizzata.

Troubleshooting
HTTP mostra la pagina di default di NPM invece della 404 personalizzata: verifica che il Dead Host *.example.com sia attivo e che server_dead.conf esista nel percorso corretto.
HTTPS restituisce errore di connessione: verifica che http.conf esista, che npm-XX sia corretto e che il certificato wildcard sia valido. Controlla i log con docker exec nome-container cat /data/logs/default-host_error.log.
Errore nginx “duplicate default server”: non aggiungere listen 80 default_server in http.conf. Il default per HTTP e gia gestito internamente da NPM e un secondo default_server sulla stessa porta causa un conflitto.
Trovare l’ID del certificato: in NPM vai su SSL Certificates, clicca sui tre puntini accanto al certificato wildcard — l’ID numerico è visibile direttamente (es. npm-46).
Perchè questa soluzione mi piace
Il punto di forza è che si appoggia interamente ai meccanismi ufficiali di estensione di NPM: la directory /data/nginx/custom/ è documentata e progettata per contenere configurazioni personalizzate che sopravvivono agli aggiornamenti del container. Non si tocca nessun file auto-generato da NPM, non si hackerano template, non si eseguono script post-aggiornamento.
Il risultato: due file, quattro clic nella UI, zero manutenzione.
Riferimenti
NGINX Proxy Manager
- Documentazione ufficiale advanced config (la pagina che cita
server_dead.conf,http.confecc.):https://nginxproxymanager.com/advanced-config/ - GitHub discussion sul Custom 404 page:
https://github.com/NginxProxyManager/nginx-proxy-manager/discussions/1234
Let’s Encrypt / Certbot
- Documentazione ufficiale plugin certbot-dns-ovh:
https://certbot-dns-ovh.readthedocs.io/








