f in x
Nginx come reverse proxy: Node.js, PHP-FPM e Python – Guida operativa
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Analisi dei dati e metriche

Nginx come reverse proxy: Node.js, PHP-FPM e Python – Guida operativa

[2026-06-08] Author: Ing. Calogero Bono

Se hai un'applicazione Node.js che impiega 3 secondi a rispondere su localhost:3000, oppure un backend Python che muore sotto carico, o un sito WordPress che va lento perché Apache consuma tutta la RAM… il problema non è quasi mai il codice. È come esponi quella applicazione al web.

Noi, di Meteora Web, vediamo ogni giorno progetti che usano Nginx solo per servire file statici, ma non sfruttano la sua vera potenza: fare da reverse proxy. Un reverse proxy non è un semplice passacarte. Distribuisce il carico, protegge il backend, termina SSL, gestisce la cache e – se configurato bene – può fare la differenza tra un sito che regge 10 visitatori e uno che ne regge 10.000 senza crash.

In questa guida vediamo come configurare Nginx come reverse proxy per tre stack tra i più diffusi: Node.js, PHP-FPM e Python. Zero teoria astratta. Solo comandi, file di configurazione e decisioni concrete da prendere.

Perché un reverse proxy? Non basta un server diretto?

Potresti esporre Node.js sulla porta 3000, Python sulla 5000 e PHP su un server Apache. Ma così:

  • Ogni applicazione gestisce SSL in autonomia (doppio lavoro, possibili errori)
  • Nessuna cache centralizzata per file statici, immagini, CSS
  • Nessun load balancing se hai più istanze dello stesso servizio
  • Esponi direttamente il backend a internet: aumenti la superficie d'attacco

Con Nginx come reverse proxy, un unico punto di ingresso (porta 443) instrada il traffico verso il backend giusto, gestisce SSL, comprime, cachea, limita il rate e filtra richieste dannose. Un solo server che fa da guardiano.

Noi lo usiamo per tutti i nostri progetti – dalla piattaforma proprietaria in Laravel ai siti WordPress dei clienti. È stabile, leggero, e consuma meno risorse di Apache. Non a caso è il server web più usato al mondo.

Configurazione di base: il blocco server per un proxy

Ogni reverse proxy in Nginx si configura con un blocco server e una direttiva location che contiene proxy_pass. Ecco la struttura minima che useremo come base:

server {
    listen 80;
    server_name esempio.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        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;
        proxy_cache_bypass $http_upgrade;
    }
}

Analizziamolo riga per riga:

  • proxy_pass dice a Nginx dove inoltrare le richieste.
  • proxy_http_version 1.1 necessario per connessioni keep-alive con il backend.
  • Gli header Upgrade e Connection sono fondamentali per WebSocket (Node.js).
  • Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto fanno passare le informazioni reali del client al backend. Senza, il backend vede sempre localhost.
  • proxy_cache_bypass evita di servire cache obsoleta per connessioni aggiornate.

Attenzione all'errore comune: se il backend si aspetta l'IP reale del cliente (ad esempio per log o limitazione), saltare gli header X-Forwarded-* produce dati falsi. Lo vediamo spesso in audit: backend che vede solo 127.0.0.1.

Cosa fare subito

Prendi un dominio di test, crea un file /etc/nginx/sites-available/test-proxy con il blocco sopra. Sostituisci proxy_pass con l'indirizzo di un'applicazione ascoltante (es. un semplice server HTTP Python). Ricarica Nginx: sudo nginx -t && sudo systemctl reload nginx. Verifica che le richieste arrivino al backend e che gli header siano corretti (usa curl -I o un tool come nghttp).

Reverse proxy per Node.js (Express, Fastify, Next.js)

Node.js è single-thread e gestisce richieste asincrone. Nginx lo aiuta con:

  • Static file serving: Nginx serve direttamente CSS, JS, immagini senza passare da Node.
  • Compressione: gzip o brotli gestita da Nginx, meno carico per Node.
  • WebSocket: Nginx supporta il proxy di WebSocket se configurato come sopra.
  • Rate limiting: limiti le richieste a livello proxy, non a livello applicativo.

Configurazione tipica per un'app Node.js

server {
    listen 80;
    server_name app.esempio.com;

    # File statici serviti direttamente
    location /static/ {
        root /var/www/app/public;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Tutto il resto va a Node
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;
        proxy_cache_bypass $http_upgrade;

        # Timeout specifici per Node
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Log separati
    access_log /var/log/nginx/node-access.log;
    error_log /var/log/nginx/node-error.log;
}

Errori comuni con Node.js:

  • Buffering di default: Nginx bufferizza la risposta del backend prima di inviarla al client. Per stream o risposte grandi (es. download) può causare ritardi. Disabilitare con proxy_buffering off dentro la location.
  • Timeout troppo bassi: se un endpoint Node impiega più di 60 secondi (es. generazione report), aumenta proxy_read_timeout a 300s.
  • WebSocket senza Upgrade: se dimentichi gli header Upgrade e Connection, le connessioni WebSocket cadono dopo pochi secondi.

Esempio pratico: Node + WebSocket

Se usi Socket.IO, aggiungi una location specifica per il namespace WebSocket:

location /socket.io/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    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;
    proxy_read_timeout 86400s;  # 24 ore per WebSocket persistenti
}

Il timeout di lettura lungo evita che Nginx chiuda la connessione WebSocket dopo 60 secondi.

Reverse proxy per PHP-FPM (WordPress, Laravel, Symfony)

PHP-FPM (FastCGI Process Manager) non parla HTTP direttamente, ma un protocollo FastCGI. Nginx lo gestisce con fastcgi_pass invece di proxy_pass. La differenza è sostanziale: devi passare variabili FastCGI specifiche con fastcgi_param.

Configurazione base per Laravel/WordPress

server {
    listen 80;
    server_name blog.esempio.com;
    root /var/www/blog/public;  # La cartella public del progetto
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;  # o 127.0.0.1:9000
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Timeout adeguati
        fastcgi_read_timeout 300;
        fastcgi_connect_timeout 60;
        fastcgi_send_timeout 60;

        # Buffer ottimizzati
        fastcgi_buffers 16 32k;
        fastcgi_buffer_size 32k;
        fastcgi_busy_buffers_size 64k;
    }

    # Negare accesso a file sensibili
    location ~ /\.ht {
        deny all;
    }
}

Punti critici:

  • try_files $uri $uri/ /index.php?$query_string è il cuore del routing per framework PHP. Senza, Nginx non passa le richieste a index.php per URL like /contatti.
  • SCRIPT_FILENAME deve puntare al file PHP corretto. Errore comune: usare $fastcgi_script_name senza $document_root.
  • Il socket Unix (unix:/var/run/php/php8.2-fpm.sock) è più veloce e sicuro di TCP, ma devi assicurarti che i permessi permettano a Nginx di scrivere.
  • WordPress: a volte richiede di aggiungere regole per il caricamento di file multipart: client_max_body_size 64M; nel blocco server.

Ottimizzazione per PHP-FPM

Se il tuo sito PHP riceve molto traffico, regola i buffer FastCGI. Valori troppo bassi causano scritture su disco (slow). Noi usiamo questi come punto di partenza:

fastcgi_buffers 256 4k;
fastcgi_buffer_size 128k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;

Inoltre, abilita la cache delle pagine FastCGI per siti WordPress con poco contenuto dinamico:

fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=wordpress:10m inactive=60m;

server {
    ...
    location ~ \.php$ {
        fastcgi_cache wordpress;
        fastcgi_cache_valid 200 60m;
        fastcgi_cache_use_stale error timeout invalid_header updating http_500;
        ...
    }
}

Attenzione: la cache FastCGI può causare problemi con cookie di sessione e WooCommerce. Usala solo per pagine pubbliche o con regole di bypass.

Reverse proxy per Python (Gunicorn, uWSGI, Daphne)

Python ha due protocolli comuni: WSGI (Gunicorn, uWSGI) e ASGI (Daphne, Uvicorn). Nginx supporta entrambi: per WSGI usa proxy_pass (HTTP), per uWSGI usa uwsgi_pass. Per ASGI, devi usare proxy_pass con WebSocket support.

Configurazione per Gunicorn (WSGI – Django/Flask/FastAPI)

server {
    listen 80;
    server_name api.esempio.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        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;
        proxy_redirect off;

        # Aumenta timeout per richieste lunghe (es. machine learning)
        proxy_read_timeout 120s;
        proxy_send_timeout 120s;
    }

    # File statici (collezionati da collectstatic)
    location /static/ {
        alias /var/www/api/static/;
        expires 30d;
    }
}

Nota su Gunicorn: Gunicorn va eseguito con un numero di worker adeguato (2-4 per CPU). Se usi Unix socket, il proxy_pass diventa proxy_pass http://unix:/tmp/gunicorn.sock.

Configurazione per uWSGI (più performante)

server {
    listen 80;
    server_name app.esempio.com;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/var/run/uwsgi/app.sock;

        uwsgi_read_timeout 120s;
        uwsgi_send_timeout 120s;
        uwsgi_buffer_size 32k;
        uwsgi_buffers 8 32k;
    }

    location /static/ {
        alias /var/www/app/static/;
    }
}

Il file uwsgi_params è solitamente in /etc/nginx/uwsgi_params e contiene variabili simili a FastCGI.

Configurazione per ASGI (Daphne/Uvicorn) con WebSocket

Se usi Django Channels o FastAPI con WebSocket, il proxy deve supportare sia HTTP che WebSocket. La configurazione è identica a quella di Node.js WebSocket:

location /ws/ {
    proxy_pass http://127.0.0.1:8001;
    proxy_http_version 1.1;
    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;
    proxy_read_timeout 86400s;
    proxy_send_timeout 86400s;
}

Sicurezza del proxy: regole da non dimenticare

Esporre un reverse proxy senza protezioni è come lasciare la porta di casa aperta. Noi, di Meteora Web, in ogni progetto applichiamo almeno queste misure:

  • Limitare l'accesso ai backend solo da localhost: se il backend ascolta su 127.0.0.1, non è raggiungibile dall'esterno. Configura Gunicorn con --bind 127.0.0.1:8000.
  • Rate limiting: limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; e applicare con limit_req zone=api burst=20 nodelay; sulle location.
  • Nascondere la versione di Nginx: server_tokens off; nel blocco http.
  • Bloccare metodi HTTP non necessari: if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE)$ ) { return 405; }
  • Disabilitare proxy_buffering se non serve: evita buffer overflow su backend lenti.
  • Usare HTTPS: termina SSL su Nginx con certificato LetsEncrypt. Mai proxy su HTTP se il backend non è in locale.

In sintesi – Cosa fare adesso

  1. Scegli il tuo stack identifica quale backend stai esponendo (Node, PHP-FPM, Python).
  2. Configura un file server di test in /etc/nginx/sites-available/ con il template corretto.
  3. Testa la configurazione con sudo nginx -t e ricarica.
  4. Verifica gli header con curl -I http://localhost e controlla che X-Real-IP sia il tuo IP pubblico (o quello del client di test).
  5. Aggiungi sicurezza: rate limit, server_tokens off, bind su localhost.
  6. Abilita HTTPS con Certbot (LetsEncrypt).

Se hai bisogno di una mano con configurazioni complesse (load balancing di più istanze, caching avanzato, WebSocket multipli), siamo qui. Noi lavoriamo ogni giorno con Nginx per clienti in tutta Italia – dal piccolo e-commerce al SaaS enterprise. Un reverse proxy ben configurato è il primo passo per un'infrastruttura solida.

Sponsored Protocol

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Co-founder di Meteora Web. Ingegnere informatico, sviluppo ecosistemi digitali ad alte prestazioni. AI, automazione, SEO tecnica e infrastrutture web. Scrivo di tecnologia per rendere complesso… semplice.

[ Read Full Dossier ]

Hai bisogno di applicare questa strategia?

Esegui il protocollo di contatto per iniziare un progetto con noi.

> INIZIA_PROGETTO

Sponsored

> MW_JOURNAL

> READ_ALL()