Introduzione
Per comprendere davvero Nginx è utile vederlo all’opera in scenari completi:
- come web server statico
- come reverse proxy layer 7 con più backend Node.js
- come proxy / load balancer layer 4
- come terminatore TLS che espone un sito pubblico con TLS 1.3 e HTTP/2
Questo articolo segue idealmente gli esempi in cui vengono creati i container nodeapp in Docker.
Per i dettagli sulla creazione dell’immagine Node e dei container si può fare riferimento agli
articoli precedenti dedicati a Docker; qui l’attenzione è sulla configurazione di Nginx.
Obiettivo finale:
- partire da un’installazione Nginx “pulita”
- costruire passo passo una configurazione leggibile
- capire come si passa da localhost a un dominio pubblico con certificato Let’s Encrypt
Installazione di Nginx (macOS con Homebrew)
Su macOS è comodo installare Nginx tramite Homebrew:
brew install nginxL’installazione crea:
- il binario
nginx - una configurazione predefinita (tipicamente in
/usr/local/etc/nginx/nginx.confo similare) - un server in ascolto su una porta di default (spesso 8080 o 8081)
Per studiare Nginx conviene non affidarsi alla configurazione di default, ma scriverne una minimale da zero. Prima si può fare un backup del file esistente e poi sovrascriverlo.
Configurazione minima: web server statico
Una configurazione Nginx layer 7 minima richiede:
- il blocco
events(anche vuoto, ma obbligatorio) - il blocco
http(contesto layer 7) - almeno un blocco
servercon una direttivalisten
Esempio di nginx.conf minimale:
events { worker_connections 1024;}
http { server { listen 8080; }}Con questa configurazione Nginx è in ascolto sulla porta 8080, ma non sa ancora da dove servire contenuti statici.
Servire file statici con root
Per servire contenuti statici (HTML, CSS, JavaScript, immagini) occorre indicare una directory
radice tramite la direttiva root all’interno del blocco server (o di un blocco location).
Supponiamo di avere:
/Users/<utente>/Documents/nginx-course/ index.htmlAllora si può scrivere:
http { server { listen 8080; root /Users/<utente>/Documents/nginx-course; }}Visitando http://localhost:8080/ Nginx, non trovando altre regole, servirà il file
index.html nella directory indicata.
Più “siti” con blocchi location
All’interno di un server si possono definire percorsi diversi con blocchi location.
Esempio di struttura:
/Users/<utente>/Documents/nginx-course/ site1/index.html site2/index.htmlConfigurazione:
http { server { listen 8080;
# Root generale del server root /Users/<utente>/Documents/nginx-course;
# / → index.html nella root location / { # usa la root di server }
# /site1 → /Users/.../site1/index.html location /site1 { # root resta quella del server }
# /site2 → /Users/.../site2/index.html location /site2 { # root resta quella del server } }}Con questa configurazione:
http://localhost:8080/→index.htmlnella roothttp://localhost:8080/site1→site1/index.htmlhttp://localhost:8080/site2→site2/index.html
Statico da directory esterna con location dedicato
A volte le risorse (es. immagini) risiedono in una directory fuori dalla root principale.
Esempio:
/Users/<utente>/Pictures/nginx-images/ postgres.pngSi può creare un location dedicato:
http { server { listen 8080; root /Users/<utente>/Documents/nginx-course;
location / { # root principale }
location /images { root /Users/<utente>/Pictures/nginx-images; } }}Richieste a:
/images/postgres.png→/Users/<utente>/Pictures/nginx-images/postgres.png
Attenzione: il percorso effettivo è dato da root + parte di path dopo il prefisso location.
Regex e return per bloccare estensioni
Con location è possibile usare espressioni regolari per intercettare pattern specifici, ad
esempio per bloccare il download di tutti i file .jpg o .jpeg.
Esempio:
http { server { listen 8080; root /Users/<utente>/Documents/nginx-course;
# Tutte le richieste che terminano con .jpg o .jpeg location ~ \.(jpe?g)$ { return 403; } }}In questo modo:
GET /foto.jpg→ 403 ForbiddenGET /foto.png→ servito normalmente (se esiste)
Le regex nei blocchi location permettono controlli sofisticati sui path senza modificare il
codice dell’applicazione.
Reverse proxy layer 7 con più backend Node.js
Per usare Nginx come reverse proxy layer 7 verso più backend Node.js si usano:
- il contesto
http - il blocco
upstreamper definire il gruppo di server - uno o più blocchi
serverconproxy_pass
Scenario di esempio
Si considerano quattro applicazioni Node.js esposte sull’host (ad esempio tramite Docker) con le seguenti porte:
localhost:2222localhost:3333localhost:4444localhost:5555
Ogni app espone:
/→ homepage che identifica l’istanza (es. “I am app 2222”)/app1→ sezione app1/app2→ sezione app2/admin→ area amministrativa
upstream e round-robin
Per bilanciare il traffico in round-robin tra tutti i backend:
http { upstream all_backend { server 127.0.0.1:2222; server 127.0.0.1:3333; server 127.0.0.1:4444; server 127.0.0.1:5555; }
server { listen 80;
location / { proxy_pass http://all_backend; } }}Ogni richiesta a http://localhost/ viene inoltrata a uno dei server in all_backend secondo
round-robin (1 → 2 → 3 → 4 → di nuovo 1 → ...).
ip_hash e sticky session
Se si usa l’algoritmo ip_hash, Nginx applica un hashing sull’IP del client per scegliere
sempre lo stesso backend per quel client.
upstream all_backend { ip_hash; server 127.0.0.1:2222; server 127.0.0.1:3333; server 127.0.0.1:4444; server 127.0.0.1:5555;}In questo caso ogni client viene “stickato” a un server. Questo approccio è talvolta usato per applicazioni stateful in memoria, anche se l’architettura moderna tende a privilegiare servizi stateless.
Due upstream distinti per /app1 e /app2
È possibile dividere le app in gruppi diversi e instradare in base al path:
http { upstream app1_backend { server 127.0.0.1:2222; server 127.0.0.1:3333; }
upstream app2_backend { server 127.0.0.1:4444; server 127.0.0.1:5555; }
server { listen 80;
location / { proxy_pass http://all_backend; }
location /app1 { proxy_pass http://app1_backend; }
location /app2 { proxy_pass http://app2_backend; } }}In questo modo:
/continua a bilanciare su tutti i backend/app1bilancia solo tra2222e3333/app2bilancia solo tra4444e5555
Proteggere /admin dal traffico pubblico
Per evitare che la sezione admin sia raggiungibile dalla porta pubblica (es. 80) ma resti
accessibile solo tramite le porte interne (2222, 3333, …), si può bloccare il path a livello
di Nginx:
server { listen 80;
location / { proxy_pass http://all_backend; }
location /admin { return 403; }}In questo scenario:
http://localhost/admin→ 403 Forbiddenhttp://localhost:2222/admin(o porte interne) → ancora accessibile, ad esempio solo dalla rete interna
Nginx come proxy / load balancer layer 4 (stream)
Finora Nginx ha lavorato in layer 7 (contesto http), leggendo le richieste HTTP e
instradando in base al path. In alcuni casi serve invece un proxy layer 4 (TCP puro), che
non interpreta HTTP ma si limita a inoltrare connessioni.
Per questo Nginx offre il contesto stream.
Differenza concettuale
-
In layer 7 (HTTP):
- Nginx termina le connessioni TCP dal client
- apre a sua volta connessioni verso i backend
- legge e interpreta la richiesta HTTP (es. path, header)
- può decidere il backend in base alla URI
-
In layer 4 (stream):
- Nginx non interpreta il protocollo applicativo
- riceve una connessione TCP dal client
- ne apre una verso un backend
- inoltra byte da una parte all’altra (TCP forwarding)
Il vantaggio del layer 4 è che qualsiasi protocollo sopra TCP (HTTP, WebSocket, SMTP, protocolli custom) viene trattato allo stesso modo.
Esempio di configurazione stream
Configurazione come proxy TCP con bilanciamento round-robin tra i quattro backend:
stream { upstream all_backend { server 127.0.0.1:2222; server 127.0.0.1:3333; server 127.0.0.1:4444; server 127.0.0.1:5555; }
server { listen 80; proxy_pass all_backend; }}Nel contesto stream:
- non esistono
location - non si può usare
proxy_pass http://...con path - la scelta del backend avviene per connessione, non per singola richiesta HTTP
Se un browser mantiene la stessa connessione TCP (tipico con HTTP/1.1 e keep-alive), tutte le richieste successive viaggeranno verso lo stesso backend finché la connessione rimane aperta.
Questo si vede bene usando strumenti come telnet o nc: ogni nuova connessione TCP viene
inoltrata in round-robin a un backend diverso.
Esporre Nginx su Internet e DNS
Per rendere il server accessibile da Internet in modo controllato occorrono:
- un router o firewall che faccia port forwarding
- un dominio o hostname DNS che punti all’IP pubblico
Port forwarding su router
Configurazione tipica:
- inoltrare la porta 80 pubblica verso la porta 80 della macchina che esegue Nginx
- inoltrare la porta 443 pubblica verso la porta 443 della stessa macchina
In questo modo:
http://<IP_pubblico>→ arriva a Nginx (porta 80)https://<IP_pubblico>→ arriva a Nginx (porta 443)
Questa operazione espone il server su Internet ed è delicata: è essenziale assicurarsi che Nginx sia configurato in modo sicuro.
Hostname DNS
Per evitare di usare direttamente l’IP pubblico si può registrare un hostname tramite un servizio DNS (anche dinamico), ad esempio:
nginx-test.dns.net→ punta all’IP pubblico della connessione
Una volta propagato il record DNS, http://nginx-test.dns.net/ raggiunge lo stesso server
raggiungibile tramite IP pubblico.
TLS con Let’s Encrypt e certbot
Per abilitare HTTPS con un certificato valido si può usare Let’s Encrypt tramite certbot.
Flusso semplificato
- Installare
certbot(ad esempio via Homebrew su macOS). - Arrestare temporaneamente Nginx se si usa la modalità standalone.
- Eseguire
certbotindicando il dominio (es.nginx-test.dns.net). - Certbot contatta Let’s Encrypt, verifica il controllo sul dominio e genera:
- il certificate chain (pubblico)
- la private key
- I file vengono salvati tipicamente in
/etc/letsencrypt/live/<dominio>/. - Nginx viene riconfigurato per usare questi file nelle direttive SSL.
Configurazione SSL in Nginx
Esempio semplificato di server HTTPS:
http { server { listen 443 ssl http2; server_name nginx-test.dns.net;
ssl_certificate /etc/letsencrypt/live/nginx-test.dns.net/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/nginx-test.dns.net/privkey.pem;
location / { proxy_pass http://all_backend; } }}Punti chiave:
listen 443 ssl http2:443: porta standard HTTPSssl: attiva il TLShttp2: abilita il protocollo HTTP/2
ssl_certificate: catena di certificati (incluso il certificato del sito)ssl_certificate_key: chiave privata corrispondente
Se la configurazione è corretta, visitando https://nginx-test.dns.net/ il browser mostrerà
una connessione sicura con certificato valido emesso da Let’s Encrypt.
Abilitare esplicitamente TLS 1.3 e HTTP/2
Di default molte installazioni Nginx abilitano TLS 1.2 e, a seconda della versione, possono non attivare TLS 1.3.
Per forzare l’uso di TLS 1.3 si può aggiungere:
server { listen 443 ssl http2;
ssl_protocols TLSv1.3; # ssl_ciphers può essere personalizzato per forzare solo suite moderne}Con ssl_protocols TLSv1.3;:
- i client che supportano solo TLS 1.2 non riusciranno a connettersi
- la connessione utilizzerà sempre TLS 1.3, con:
- handshake più rapido (meno round-trip)
- ciphersuite moderne (es. basate su Diffie-Hellman effimero)
Per HTTP/2 è sufficiente mantenere http2 nel listen:
listen 443 ssl http2;I browser moderni negozieranno automaticamente HTTP/2 tramite ALPN quando possibile.
Riepilogo
In questo articolo Nginx è stato visto in più ruoli:
- Web server statico:
root,location, regex e codici di ritorno - Reverse proxy layer 7:
upstream,proxy_pass, round-robin,ip_hash, path-based routing e blocco selettivo di/admin - Proxy / load balancer layer 4: contesto
streame forwarding TCP trasparente - Terminazione TLS: integrazione con Let’s Encrypt, configurazione di certificato e chiave, abilitazione di TLS 1.3 e HTTP/2
Capire questi aspetti permette di leggere e progettare configurazioni Nginx con maggiore consapevolezza, scegliendo di volta in volta:
- se lavorare a livello di HTTP (layer 7) o TCP (layer 4)
- come distribuire il traffico tra più backend
- come esporre in sicurezza il servizio verso Internet.