Servire un'applicazione Laravel con nginx
Laravel è uno dei framework PHP più diffusi e apprezzati, noto per la sua sintassi elegante e per l'ampio ecosistema di strumenti che mette a disposizione degli sviluppatori. Per portare un'applicazione Laravel in produzione, è necessario un web server in grado di gestire le richieste HTTP, servire i file statici e passare le richieste PHP a un processo interprete. Nginx, grazie alle sue prestazioni elevate, alla gestione efficiente della concorrenza e alla flessibilità nella configurazione, rappresenta una scelta eccellente per questo ruolo.
In questa guida vedremo come installare e configurare nginx su un server Linux (Ubuntu/Debian),
come installare PHP-FPM per eseguire il codice PHP, come preparare il progetto Laravel e come
scrivere un blocco server nginx corretto e sicuro. Tratteremo anche i permessi sul
filesystem, la gestione delle variabili d'ambiente, la configurazione HTTPS con Let's Encrypt e
alcune ottimizzazioni consigliate per l'ambiente di produzione.
Prerequisiti
Prima di iniziare, assicurarsi di disporre di:
- Un server con Ubuntu 22.04 o Debian 12 (o versioni successive compatibili).
- Accesso root o un utente con privilegi
sudo. - Un nome di dominio puntato all'indirizzo IP del server (necessario per il certificato TLS).
- Composer installato a livello globale.
- Node.js e npm, se il progetto utilizza asset compilati con Vite o webpack.
Installazione di nginx
Aggiornare l'indice dei pacchetti e installare nginx tramite il gestore di pacchetti di sistema.
# Aggiornamento dell'indice dei pacchetti
sudo apt update
# Installazione di nginx
sudo apt install -y nginx
# Abilitazione e avvio del servizio
sudo systemctl enable nginx
sudo systemctl start nginx
Verificare che nginx sia in esecuzione controllando il suo stato:
# Verifica dello stato del servizio
sudo systemctl status nginx
Se tutto è corretto, l'output mostrerà active (running). Aprire un browser e navigare
verso l'indirizzo IP del server: si dovrebbe vedere la pagina di benvenuto predefinita di nginx.
Installazione di PHP-FPM
Nginx non esegue PHP nativamente: delega questa operazione a PHP-FPM (FastCGI Process Manager), un gestore di processi PHP con cui comunica tramite un socket Unix o TCP. Laravel 11 richiede almeno PHP 8.2; in questa guida utilizzeremo PHP 8.3.
# Aggiunta del repository Ondrej Surý per PHP aggiornato
sudo apt install -y software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update
# Installazione di PHP 8.3 e delle estensioni richieste da Laravel
sudo apt install -y \
php8.3-fpm \
php8.3-cli \
php8.3-mbstring \
php8.3-xml \
php8.3-bcmath \
php8.3-curl \
php8.3-zip \
php8.3-intl \
php8.3-mysql \
php8.3-redis
Al termine, abilitare e avviare il servizio PHP-FPM:
# Abilitazione e avvio di PHP-FPM
sudo systemctl enable php8.3-fpm
sudo systemctl start php8.3-fpm
PHP-FPM, per impostazione predefinita, è in ascolto sul socket Unix
/run/php/php8.3-fpm.sock. Questo path verrà utilizzato nella configurazione
di nginx per passare le richieste PHP al processo corretto.
Preparazione del progetto Laravel
Posizionare il codice dell'applicazione in una directory accessibile al web server. La convenzione
più comune è /var/www. In questo esempio il dominio si chiama
example.com; sostituire questo valore con il proprio dominio reale.
# Creazione della directory del progetto
sudo mkdir -p /var/www/example.com
# Clonazione del repository (oppure upload del codice tramite rsync/scp)
sudo git clone https://github.com/your-org/your-app.git /var/www/example.com
# Spostamento nella directory del progetto
cd /var/www/example.com
# Installazione delle dipendenze PHP tramite Composer
sudo composer install --no-dev --optimize-autoloader
# Copia del file delle variabili d'ambiente
sudo cp .env.example .env
# Generazione della chiave dell'applicazione
sudo php artisan key:generate
Configurazione del file .env
Aprire il file .env e configurare almeno le seguenti variabili prima di procedere:
# Modalità dell'applicazione (mai 'true' in produzione)
APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com
# Configurazione del database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=app_database
DB_USERNAME=app_user
DB_PASSWORD=strong_password_here
# Driver per cache e sessioni (redis consigliato in produzione)
CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
Permessi sul filesystem
Laravel scrive nei file di log, nella cache e nelle sessioni all'interno delle directory
storage e bootstrap/cache. Queste directory devono essere scrivibili
dall'utente con cui gira PHP-FPM, che su Ubuntu/Debian è www-data.
# Assegnazione della proprietà all'utente www-data
sudo chown -R www-data:www-data /var/www/example.com
# Permessi standard per i file
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
# Permessi standard per le directory
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
# Permessi di scrittura per storage e bootstrap/cache
sudo chmod -R 775 /var/www/example.com/storage
sudo chmod -R 775 /var/www/example.com/bootstrap/cache
Ottimizzazione per la produzione
Prima di configurare il web server, eseguire i comandi di ottimizzazione di Laravel che compilano i file di configurazione, di routing e le view in cache, migliorando sensibilmente le prestazioni in produzione:
# Cache della configurazione
sudo php artisan config:cache
# Cache delle route
sudo php artisan route:cache
# Cache delle view Blade
sudo php artisan view:cache
# Ottimizzazione dell'autoloader di Composer (già eseguita con --optimize-autoloader)
sudo php artisan optimize
Configurazione di nginx per Laravel
Il cuore della configurazione risiede nel blocco server di nginx. Laravel richiede
che tutte le richieste verso path inesistenti sul disco vengano reindirizzate a
index.php, che funge da front controller. Questo è ottenuto con la direttiva
try_files.
Creare un nuovo file di configurazione nella directory sites-available:
# Creazione del file di configurazione del virtual host
sudo nano /etc/nginx/sites-available/example.com
Incollare il contenuto seguente, adattando il nome del dominio e il path del progetto:
# Blocco server per il redirect da HTTP a HTTPS
server {
listen 80;
listen [::]:80;
# Nome del dominio servito
server_name example.com www.example.com;
# Reindirizzamento permanente verso HTTPS
return 301 https://$host$request_uri;
}
# Blocco server principale su HTTPS
server {
listen 443 ssl;
listen [::]:443 ssl;
# Nome del dominio servito
server_name example.com www.example.com;
# Percorso alla document root (deve puntare alla cartella public di Laravel)
root /var/www/example.com/public;
# File di indice predefinito
index index.php;
# Certificati TLS gestiti da Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Parametri TLS consigliati (generati da Certbot o da ssl-config.mozilla.org)
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Dimensione massima del corpo della richiesta (utile per upload di file)
client_max_body_size 64M;
# Charset predefinito
charset utf-8;
# Gestione delle richieste: prima cerca il file, poi la directory,
# altrimenti passa al front controller di Laravel
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Blocco della location per i file PHP: passa la richiesta a PHP-FPM
location ~ \.php$ {
# Inclusione dei parametri FastCGI predefiniti di nginx
include fastcgi_params;
# Socket Unix di PHP-FPM
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
# Script da eseguire (document root + URI)
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
# Correzione della variabile DOCUMENT_ROOT
fastcgi_param DOCUMENT_ROOT $realpath_root;
# Timeout per richieste lente (es. import di grandi dataset)
fastcgi_read_timeout 300;
}
# Negazione dell'accesso ai file .htaccess (non usati da nginx,
# ma è buona norma bloccarli esplicitamente)
location ~ /\.ht {
deny all;
}
# Negazione dell'accesso alla directory .git e ad altri file sensibili
location ~ /\.(git|env|svn) {
deny all;
}
# Cache lato client per i file statici con hash nel nome (generati da Vite)
location ~* \.(css|js|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|ico|webp)$ {
expires max;
add_header Cache-Control "public, immutable";
access_log off;
}
# Log degli accessi e degli errori specifici per questo virtual host
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
}
Attivazione del virtual host
Nginx utilizza il meccanismo sites-available / sites-enabled: i file
di configurazione si creano in sites-available e vengono attivati creando un
collegamento simbolico in sites-enabled.
# Creazione del collegamento simbolico per abilitare il virtual host
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
# Verifica della sintassi della configurazione
sudo nginx -t
# Ricarica della configurazione senza interrompere il servizio
sudo systemctl reload nginx
Il comando nginx -t è fondamentale: in caso di errori di sintassi, mostra il file
e il numero di riga coinvolti, permettendo di correggere il problema prima di applicare la
configurazione.
Ottenere un certificato TLS con Let's Encrypt
Per servire il sito in HTTPS è necessario un certificato TLS. Let's Encrypt fornisce certificati gratuiti tramite il client Certbot, che è in grado di integrarsi direttamente con nginx e di aggiornare automaticamente la configurazione.
# Installazione di Certbot e del plugin per nginx
sudo apt install -y certbot python3-certbot-nginx
# Ottenimento del certificato e modifica automatica della configurazione nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot chiederà un indirizzo email per le notifiche di rinnovo e, dopo la verifica del dominio, installerà il certificato e aggiornerà il file di configurazione di nginx. Il rinnovo automatico è gestito da un timer di systemd installato insieme a Certbot; per verificarne il funzionamento:
# Simulazione del rinnovo per verificare che tutto funzioni
sudo certbot renew --dry-run
Se si preferisce mantenere la configurazione nginx scritta manualmente (come mostrata nella
sezione precedente), è possibile ottenere il certificato senza modificare la configurazione
usando il flag --certonly:
# Ottenimento del certificato senza modificare la configurazione nginx
sudo certbot certonly --nginx -d example.com -d www.example.com
Configurazione di PHP-FPM
PHP-FPM gestisce un pool di processi PHP per ogni applicazione. Il file di configurazione del
pool predefinito si trova in /etc/php/8.3/fpm/pool.d/www.conf. Per un'applicazione
Laravel in produzione è consigliabile creare un pool dedicato, con un utente, un gruppo e
un socket propri.
# Creazione di un pool PHP-FPM dedicato all'applicazione
sudo nano /etc/php/8.3/fpm/pool.d/example.conf
; Definizione del pool dedicato
[example]
; Utente e gruppo con cui vengono eseguiti i processi PHP
user = www-data
group = www-data
; Percorso del socket Unix dedicato a questo pool
listen = /run/php/php8.3-example.sock
; Proprietà del socket (nginx gira come www-data, quindi stesse credenziali)
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Gestione dinamica dei processi worker
pm = dynamic
; Numero massimo di processi worker contemporanei
pm.max_children = 20
; Processi worker avviati all'accensione del pool
pm.start_servers = 5
; Numero minimo di worker in attesa di richieste
pm.min_spare_servers = 3
; Numero massimo di worker in attesa di richieste
pm.max_spare_servers = 8
; Numero di richieste gestite da ogni worker prima di essere riavviato
; (utile per prevenire memory leak)
pm.max_requests = 500
; Percorso del log degli errori specifico per questo pool
php_admin_value[error_log] = /var/log/php/example.error.log
php_admin_flag[log_errors] = on
; Limite di memoria per ogni processo PHP
php_admin_value[memory_limit] = 256M
# Creazione della directory per i log PHP
sudo mkdir -p /var/log/php
sudo chown www-data:www-data /var/log/php
# Riavvio di PHP-FPM per applicare il nuovo pool
sudo systemctl restart php8.3-fpm
Aggiornare la direttiva fastcgi_pass nel blocco di nginx per usare il nuovo socket:
# Utilizzo del socket del pool dedicato anziché di quello predefinito
fastcgi_pass unix:/run/php/php8.3-example.sock;
Gestione delle code con Supervisor
Le applicazioni Laravel fanno spesso uso del sistema di code per eseguire task in background
(invio email, elaborazione di file, notifiche). Il comando php artisan queue:work
deve girare continuamente come processo demone; Supervisor è lo strumento più usato per
assicurare che il worker rimanga attivo e venga riavviato automaticamente in caso di crash.
# Installazione di Supervisor
sudo apt install -y supervisor
# Creazione del file di configurazione del worker
sudo nano /etc/supervisor/conf.d/example-worker.conf
; Configurazione del worker per le code di Laravel
[program:example-worker]
; Comando da eseguire (percorso assoluto consigliato)
command=php /var/www/example.com/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
; Directory di lavoro
directory=/var/www/example.com
; Utente con cui eseguire il processo
user=www-data
; Avvio automatico all'avvio di Supervisor
autostart=true
; Riavvio automatico in caso di uscita inattesa
autorestart=true
; Numero di processi worker da avviare in parallelo
numprocs=2
; Prefisso usato per nominare i processi (worker_00, worker_01, ...)
process_name=%(program_name)s_%(process_num)02d
; Percorso dei log
stdout_logfile=/var/log/supervisor/example-worker.log
stderr_logfile=/var/log/supervisor/example-worker.error.log
; Segnale inviato al processo per la terminazione graceful
stopsignal=TERM
; Secondi di attesa prima di considerare il processo avviato
startsecs=1
# Ricarica della configurazione di Supervisor
sudo supervisorctl reread
sudo supervisorctl update
# Avvio del worker
sudo supervisorctl start example-worker:*
# Verifica dello stato
sudo supervisorctl status
Script di deploy
Automatizzare il processo di aggiornamento dell'applicazione riduce il rischio di errori umani e rende il deploy ripetibile. Lo script seguente esegue i passi tipici di un deploy Laravel a zero downtime tramite la strategia del mantenimento della cache attiva.
#!/usr/bin/env bash
# Script di deploy per l'applicazione Laravel
# Utilizzo: sudo bash deploy.sh
set -euo pipefail
# Variabili di configurazione
APP_DIR="/var/www/example.com"
PHP_BIN="/usr/bin/php8.3"
COMPOSER_BIN="/usr/local/bin/composer"
echo "Inizio del deploy..."
# Spostamento nella directory del progetto
cd "$APP_DIR"
# Attivazione della modalità manutenzione
"$PHP_BIN" artisan down --render="errors::503" --retry=60
# Aggiornamento del codice sorgente
git pull origin main
# Installazione delle dipendenze (solo produzione, senza script di sviluppo)
"$COMPOSER_BIN" install --no-dev --optimize-autoloader --no-interaction
# Esecuzione delle migration del database
"$PHP_BIN" artisan migrate --force
# Pulizia e riscrittura delle cache
"$PHP_BIN" artisan config:cache
"$PHP_BIN" artisan route:cache
"$PHP_BIN" artisan view:cache
"$PHP_BIN" artisan event:cache
# Riavvio dei worker delle code tramite Supervisor
supervisorctl restart example-worker:*
# Disattivazione della modalità manutenzione
"$PHP_BIN" artisan up
echo "Deploy completato con successo."
Verifica della configurazione finale
Una volta completati tutti i passi, eseguire una serie di controlli per assicurarsi che l'ambiente sia configurato correttamente.
# Verifica della sintassi di nginx
sudo nginx -t
# Stato di nginx
sudo systemctl status nginx
# Stato di PHP-FPM
sudo systemctl status php8.3-fpm
# Stato dei worker delle code
sudo supervisorctl status
# Test di connettività HTTP/HTTPS (deve restituire 200 o 301)
curl -I http://example.com
curl -I https://example.com
# Verifica del certificato TLS
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null \
| openssl x509 -noout -dates
# Verifica dei permessi su storage
ls -la /var/www/example.com/storage/logs/
Se il comando curl -I https://example.com restituisce HTTP/2 200
e la pagina viene visualizzata correttamente nel browser, la configurazione è completata
con successo.
Considerazioni sulla sicurezza
Una configurazione funzionante è solo il punto di partenza. In produzione è importante adottare ulteriori misure di sicurezza:
-
Firewall: Abilitare
ufwe permettere solo le porte 22, 80 e 443. Bloccare l'accesso diretto al socket di PHP-FPM dall'esterno. -
Header HTTP di sicurezza: Aggiungere al blocco
serverdi nginx gli headerX-Frame-Options,X-Content-Type-Options,Referrer-PolicyeContent-Security-Policy. -
Rate limiting: Usare la direttiva
limit_req_zonedi nginx per limitare il numero di richieste per IP, proteggendo i form di login da attacchi a forza bruta. -
Variabili d'ambiente: Non committare mai il file
.envnel repository. Usare strumenti come Vault, AWS Secrets Manager o le variabili d'ambiente del sistema operativo per gestire i segreti. - Aggiornamenti: Tenere aggiornati nginx, PHP, Laravel e tutte le dipendenze Composer. Monitorare i bollettini di sicurezza di ciascun componente.
Conclusione
Configurare nginx per servire un'applicazione Laravel richiede di coordinare diversi componenti: il web server stesso, PHP-FPM, il sistema di code e gli strumenti di certificazione TLS. Seguendo i passi descritti in questa guida si ottiene un ambiente di produzione robusto, performante e sicuro, in grado di gestire applicazioni Laravel di qualsiasi dimensione.
La chiave per un'infrastruttura stabile nel tempo è l'automazione: uno script di deploy ripetibile, un processo di rinnovo automatico del certificato TLS e un sistema di monitoraggio dei worker delle code permettono di ridurre al minimo le operazioni manuali e il rischio di downtime non pianificati.