Installare LAMP su Ubuntu 24.04 con PHP-FPM
Configura Apache, MariaDB con unix_socket, PHP 8.3 tramite PHP-FPM, virtual host basato sul nome e HTTPS gratuito con Certbot su Ubuntu 24.04.
Cosa stai configurando
Uno stack LAMP è composto da quattro elementi in esecuzione su un unico server Ubuntu 24.04: Linux alla base, Apache che gestisce le richieste HTTP, MariaDB che contiene i dati e PHP 8.3 che esegue il codice. Al termine avrai un virtual host basato sul nome che pubblica una directory applicativa reale, un database con un utente dedicato dotato dei privilegi minimi necessari, PHP collegato ad Apache tramite PHP-FPM e un certificato gratuito di Let's Encrypt.
L'installazione richiede quattro comandi apt. Quasi tutto ciò che trovi in questa guida riguarda il collegamento tra i componenti e il piccolo insieme di errori che può fare in modo che uno stack appena configurato mostri una pagina vuota, consegni il codice sorgente al browser come download oppure rifiuti l'accesso al database appena installato. Ognuno di questi problemi presenta segnali riconoscibili. Di seguito viene indicato con il testo esatto che visualizzerai.
Prerequisiti e aspetti critici da conoscere
Presupponi un VPS KVM Ubuntu 24.04 appena installato, con un utente sudo o root e un indirizzo IPv4 pubblico. Uno stack minimo funziona con 1 GB di RAM; assegnagli 2 GB prima di installare un'applicazione reale basata su database, perché i buffer predefiniti di MariaDB e alcuni processi PHP-FPM consumano rapidamente il primo gigabyte.
Prima che Certbot possa funzionare alla fine, devono essere vere due condizioni. Configura tutto ora. Ti serve un nome di dominio con un record A che punti all'indirizzo IP pubblico del VPS. Let's Encrypt esegue la convalida tramite HTTP per quel nome e non può mai rilasciare un certificato per un semplice indirizzo IP. Inoltre, le porte 80 e 443 devono essere raggiungibili da Internet. Con molti provider, questo significa aprirle nel firewall di rete del pannello di controllo e anche in ufw sul server. Le modifiche DNS possono richiedere fino a un'ora per propagarsi. Configura quindi prima il record A, così sarà attivo quando ti servirà.
Passaggio 1 - Installare Apache e verificare la pagina predefinita
sudo apt update
sudo apt install -y apache2apt avvia e abilita il servizio automaticamente. Verificarlo:
systemctl status apache2È necessario visualizzare una riga con active (running). Aprire quindi http://YOUR_SERVER_IP/ in un browser. La Apache2 Ubuntu Default Page con il grande banner "It works!" indica il risultato corretto: dimostra che Apache sta fornendo il contenuto e non rappresenta un errore. La pagina si trova in /var/www/html/index.html ed è fornita dall'host virtuale predefinito incluso 000-default.conf. In seguito verranno disabilitati entrambi; per ora la loro presenza è esattamente ciò che deve essere verificato.
Se la pagina non viene caricata, ma systemctl indica che il processo è in esecuzione, la causa è un firewall. Questo sarà il passaggio successivo.
Passaggio 2 - Aprire il firewall per HTTP e HTTPS
Il pacchetto apache2 registra tre profili applicativi ufw. Elencali:
sudo ufw app listVedrai Apache, Apache Full e Apache Secure. Apache riguarda solo la porta 80, Apache Secure solo la porta 443 e Apache Full entrambe. È quello da usare, perché aggiungerai TLS alla fine.
sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enableConsenti OpenSSH prima di eseguire ufw enable. ufw nega per impostazione predefinita tutto il traffico in ingresso. Se lo abiliti senza una regola SSH, interrompe la tua connessione non appena diventa attivo. Mantieni la sessione corrente, ma non puoi più riconnetterti. Verifica con sudo ufw status. OpenSSH, Apache Full e i relativi equivalenti v6 devono risultare tutti impostati su ALLOW.
Passaggio 3 - Installare MariaDB e proteggerlo
sudo apt install -y mariadb-server
systemctl status mariadbUbuntu 24.04 include MariaDB 10.11, una versione con supporto a lungo termine, quindi non è necessario usare un repository esterno. Dopo aver avviato il servizio, applicare le misure di sicurezza:
sudo mysql_secure_installationLeggere le richieste invece di premere sempre Invio. Quando viene richiesta la password root corrente, premere Invio: non è ancora stata impostata. Alla richiesta "Passare all'autenticazione unix_socket?", la risposta non cambia nulla, perché questa autenticazione è già abilitata in questo pacchetto; premere quindi n. Rispondere n a "Cambiare la password root?" per il motivo spiegato nel paragrafo successivo, quindi rispondere Y alle richieste restanti: rimuovere gli utenti anonimi, impedire l'accesso remoto a root, eliminare il database di test e ricaricare le tabelle dei privilegi.
Questa è la parte che crea più confusione. In MariaDB su Ubuntu, l'account del database root usa l'autenticazione unix_socket, non una password. Il database si fida quindi dell'utente del sistema operativo con cui è già stata eseguita l'autenticazione. Di conseguenza, da una shell root funziona quanto segue:
sudo mysql...e viene visualizzato un prompt MariaDB [(none)]> senza richiedere alcuna password. Lo stesso comando eseguito da un utente senza privilegi viene rifiutato. Questo è esattamente lo scopo: l'accesso a root del database è associato a sudo nel sistema e non esiste alcuna password da sottrarre, ottenere con il phishing o indovinare con un attacco brute-force. È più sicuro di una password, quindi non modificarlo. La regola che ne deriva è la seguente: non configurare mai un'applicazione per usare l'account root. Creare un utente dedicato per ogni applicazione (Passaggio 7), perché un'applicazione che si connette tramite TCP usando un nome utente e una password non può usare l'autenticazione socket. Inoltre, ogni applicazione deve essere limitata al proprio database.
Passaggio 4 - Installare PHP 8.3 con PHP-FPM
La versione predefinita di PHP in Ubuntu 24.04 è la 8.3. Installare il gestore dei processi FPM e le estensioni necessarie per un'applicazione tipica:
sudo apt install -y php8.3-fpm php8.3-mysql php8.3-cli \
php8.3-curl php8.3-xml php8.3-mbstring php8.3-zipNotare cosa non è incluso nell'elenco: libapache2-mod-php. Questo pacchetto obsoleto incorpora un interprete PHP in ogni processo Apache. È semplice, ma ogni worker contiene una copia di PHP, indipendentemente dal fatto che stia servendo uno script o un'immagine statica. Inoltre, i due componenti condividono il ciclo di vita e questa configurazione funziona solo con l'MPM prefork di Apache, il meno efficiente. PHP-FPM, invece, esegue PHP in un proprio pool di processi, con cui Apache comunica tramite un socket. Apache può quindi usare l'MPM event basato su thread per i file statici e inoltrare solo le richieste PHP. Il pool può essere configurato indipendentemente dal server web e la stessa identica configurazione FPM funziona in seguito anche se si mette nginx davanti ad Apache. È l'impostazione predefinita attuale per un motivo valido.
Apache raggiunge FPM tramite il modulo proxy_fcgi. Abilitarlo, abilitare la configurazione installata dal pacchetto FPM e riavviare:
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2a2enconf php8.3-fpm attiva /etc/apache2/conf-available/php8.3-fpm.conf, che contiene la regola per inoltrare i file PHP al socket FPM. La regola identifica qualsiasi file .php e lo inoltra al socket in /run/php/php8.3-fpm.sock:
<FilesMatch ".+\.ph(ar|p|tml)$">
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>Non modificare questo file: viene fornito già configurato correttamente. Tuttavia, conoscere il percorso del socket consente di diagnosticare in seguito gli errori "PHP viene scaricato invece di essere eseguito" e "Primary script unknown". Entrambi dipendono dal fatto che Apache e FPM non concordano su questo socket o sul file associato.
Passaggio 5 - Un host virtuale basato sul nome per l'app
L'hosting virtuale basato sul nome consente a un solo IP di servire molti siti; Apache seleziona il sito in base all'header Host: della richiesta. Crea una directory per l'app, ben separata da /var/www/html:
sudo mkdir -p /var/www/testapp
sudo chown -R www-data:www-data /var/www/testapp
sudo chmod -R 755 /var/www/testappLa proprietà è importante. Su Ubuntu, Apache e PHP-FPM vengono eseguiti come utente www-data, quindi i file che il server web deve leggere e le directory in cui l'app deve scrivere, come una cartella per gli upload, devono appartenere a www-data. Se modifichi i file anche con il tuo utente di accesso, un modello comune consiste nel mantenere la proprietà dei file e aggiungere il tuo utente al gruppo www-data; per un deploy semplice, www-data:www-data è la scelta più prevedibile.
Crea l'host virtuale in /etc/apache2/sites-available/testapp.conf:
<VirtualHost *:80>
ServerName app.example.com
DocumentRoot /var/www/testapp
<Directory /var/www/testapp>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/testapp-error.log
CustomLog ${APACHE_LOG_DIR}/testapp-access.log combined
</VirtualHost>Imposta ServerName sul tuo dominio reale. Options -Indexes impedisce ad Apache di elencare la directory quando non è presente alcun file index; in caso contrario, i visitatori possono sfogliare l'albero dei sorgenti. AllowOverride All consente il funzionamento di un file .htaccess, previsto dalla maggior parte delle applicazioni PHP per gli URL leggibili; impostalo su None per ottenere un piccolo miglioramento delle prestazioni se l'app non ne ha bisogno. Abilita questo sito, disabilita quello predefinito, verifica la configurazione e ricaricala:
sudo a2ensite testapp
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2apache2ctl configtest dovrebbe stampare Syntax OK. La riga a2dissite 000-default viene spesso dimenticata; per questo in seguito può comparire ancora la pagina predefinita, come descritto nella sezione sui problemi.
Passaggio 6 - Verificare che PHP venga eseguito, quindi eliminare la verifica
Crea un file PHP di una riga nella directory root dell'applicazione:
echo "<?php phpinfo();" | sudo tee /var/www/testapp/info.phpVisita http://app.example.com/info.php. Il risultato corretto è la lunga tabella PHP Version 8.3.x viola e grigia che elenca i moduli caricati, con la riga Server API che riporta FPM/FastCGI. Quest'ultima riga conferma che le richieste passano attraverso PHP-FPM e non mod_php.
Ora eliminalo immediatamente:
sudo rm /var/www/testapp/info.phpphpinfo() espone la versione esatta di PHP, ogni estensione caricata, i percorsi dei file e i dettagli dell'ambiente. Queste informazioni possono aiutare chi esegue scansioni del server alla ricerca di una versione con una vulnerabilità nota. È un test, non una funzionalità. Eliminalo non appena hai visualizzato la pagina. Se invece della tabella il browser propone di scaricare info.php, PHP non è configurato per Apache. Passa alla sezione relativa ai problemi prima di procedere.
Passaggio 7 - Creare il database dell'applicazione e un utente con privilegi minimi
Aprire il database come root autenticato tramite socket:
sudo mysqlCreare quindi un database e un utente associato esclusivamente a quel database:
CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;Qui vengono adottate tre scelte deliberate. utf8mb4 è UTF-8 reale a quattro byte; il vecchio alias utf8 tronca senza avviso le emoji e alcuni caratteri CJK, quindi usare sempre utf8mb4. Il grant riguarda appdb.*, non *.*: questo utente può accedere al proprio database e a nient'altro. In questo modo, una vulnerabilità di SQL injection nell'applicazione non può leggere le tabelle degli altri siti. Inoltre, 'appuser'@'localhost' limita l'account alle connessioni provenienti dallo stesso server.
Eseguire il test con questo utente:
mysql -u appuser -p appdbIl comando richiede la password e mostra un prompt MariaDB [appdb]>. Notare l'assenza del flag -h. Lasciarlo omesso: il client si connette tramite il socket Unix locale, che MariaDB considera esattamente localhost. È importante conoscere un dettaglio: per MySQL e MariaDB, localhost indica il socket Unix e 127.0.0.1 indica una connessione TCP. In una configurazione standard di MariaDB su Ubuntu 24.04, il server risolve comunque una connessione TCP da 127.0.0.1 in localhost. Pertanto, entrambi corrispondono all'account. Tuttavia, sui server con skip-name-resolve abilitato, una configurazione comune per migliorare le prestazioni e lo standard in molte immagini container, i due valori vengono trattati come host diversi. In questo caso, un'applicazione che si connette a 127.0.0.1 viene rifiutata con ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES) anche se la password è corretta.
Configurare quindi l'applicazione con host localhost, utente appuser e database appdb, mai root. Sia mysqli di PHP sia PDO usano il socket Unix quando l'host è la stringa letterale localhost, in corrispondenza dell'account appena creato. Se un framework richiede un host TCP numerico, creare l'utente in base al metodo di connessione effettivo: 'appuser'@'127.0.0.1' oppure @'%', associato a una regola firewall, solo se il database deve essere raggiungibile da un altro computer.
Passaggio 8 - Aggiungere HTTPS con Certbot
Servire un modulo di login tramite HTTP non cifrato invia le password in chiaro e ogni browser moderno segnala la pagina come "Non sicura". Certbot risolve il problema con un singolo comando. Installalo insieme al plugin per Apache:
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apacheCertbot utilizza qui due plugin. L'autenticatore apache dimostra che controlli il dominio servendo brevemente un file di verifica tramite l'istanza di Apache in esecuzione, mentre l'installer apache riscrive il tuo virtual host per aggiungere il blocco 443, lo punta al nuovo certificato e reindirizza tutto il traffico HTTP verso HTTPS per impostazione predefinita. Certbot non pone più la domanda sul reindirizzamento dalla versione 1.4.0 (maggio 2020), quindi passa --no-redirect se hai necessità di continuare a servire traffico HTTP non cifrato. Poiché hai impostato un ServerName reale nel Passaggio 5, Certbot rileva automaticamente il dominio. I certificati emessi oggi hanno una validità di 90 giorni (Let's Encrypt ridurrà la durata predefinita a 64 giorni il 10 febbraio 2027 e a 45 giorni il 16 febbraio 2028) e il pacchetto installa un timer systemd che si occupa del rinnovo; verifica il timer con sudo certbot renew --dry-run, che dovrebbe terminare con Congratulations, all simulated renewals succeeded.
Per la procedura completa sulla verifica, sul timer di rinnovo e sui requisiti DNS e firewall, consulta la guida di approfondimento su emissione di certificati TLS gratuiti Let's Encrypt con Certbot su Apache.
Backup, aggiornamenti e hardening
Esegui il backup dei due elementi che contengono il tuo stato: i database e la web root. Un dump logico notturno è l'approccio più semplice e affidabile, sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz', da copiare poi fuori dal server. Racchiudere l'intera pipeline in sudo sh -c è fondamentale: senza di esso, la shell esegue il redirect > /root/... con i privilegi del tuo utente e fallisce con Permission denied, poiché solo mysqldump ha ereditato sudo. --single-transaction fornisce uno snapshot coerente delle tabelle InnoDB senza bloccarle. Abbinalo a un tar di /var/www e /etc/apache2/sites-available per poter ricostruire l'intero stack su una nuova VPS partendo da quei file.
Gli aggiornamenti sono una normale operazione di sudo apt update && sudo apt upgrade. L'aspetto critico è l'aggiornamento della versione di PHP durante un avanzamento di release. Ubuntu 26.04 LTS distribuisce PHP 8.5 come predefinito e i pacchetti php8.3-* installati qui non esistono in quella release; di conseguenza, dopo l'aggiornamento, eseguirai php8.5-fpm, il socket diventerà /run/php/php8.5-fpm.sock e la configurazione di Apache punterà ancora al socket 8.3. Abilita la nuova configurazione (sudo a2enconf php8.5-fpm) e disabilita la vecchia, altrimenti il sito inizierà a restituire Primary script unknown dopo un aggiornamento altrimenti di routine. Poiché le release di PHP avanzano più rapidamente di una distribuzione LTS, controlla le note di rilascio correnti di PHP invece di bloccare una versione patch.
Due passaggi di hardening meritano di essere eseguiti fin dal primo giorno. Primo, configura Fail2Ban per monitorare SSH sul server: una VPS pubblica riceve tentativi di accesso automatizzati nel giro di pochi minuti e una piccola jail trasforma migliaia di tentativi in una manciata prima di un ban. Secondo, se preferisci gestire virtual host Apache, database MariaDB e utenti tramite un browser anziché modificare i file manualmente, il pannello di controllo web Webmin si integra perfettamente su questo stack e gestisce gli stessi file di configurazione che hai appena scritto. Nessuno dei due sostituisce la comprensione dei singoli componenti, ma entrambi riducono l'attrito nella gestione quotidiana.
Modalità di errore e messaggi associati
La pagina predefinita non scompare. Hai modificato il tuo virtual host, ricaricato, ma il browser mostra ancora la "Apache2 Ubuntu Default Page" con il banner "It works!". Apache serve il primo virtual host corrispondente; quando nessun ServerName soddisfa la richiesta, viene scelto quello che appare per primo in ordine alfabetico, poiché 000-default.conf precede testapp.conf. Il nome host della richiesta non corrisponde al tuo ServerName, oppure non hai eseguito sudo a2dissite 000-default. Disabilita la configurazione predefinita con sudo systemctl reload apache2 e verifica con apache2ctl -S, che stampa la mappa dei vhost e mostra quale file gestisce la pagina predefinita. Svuota anche la cache del browser: una risposta 200 memorizzata in cache dalla vecchia pagina persiste senza problemi.
Un file .php viene scaricato invece di essere eseguito. Apri info.php e il browser scarica il file contenente il codice sorgente <?php grezzo, o lo mostra come testo semplice, invece di eseguirlo. Apache sta servendo il file come risorsa statica perché il gestore PHP non è collegato, hai saltato sudo a2enmod proxy_fcgi o sudo a2enconf php8.3-fpm, oppure non hai riavviato Apache in seguito. Eseguili tutti e tre (Passaggio 4) e ricarica. Conferma che il modulo sia caricato con apache2ctl -M | grep fcgi, che dovrebbe elencare proxy_fcgi_module. Si tratta di una fuga di codice sorgente, non di un bug estetico; risolvi il problema prima di pubblicare dati reali sul server.
ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Hai eseguito mysql -u root o mariadb -u root senza sudo. L'account root utilizza l'autenticazione unix_socket, quindi accetta la connessione solo se il tuo utente del sistema operativo è effettivamente root. La soluzione è sudo mysql, senza -u root e senza password. Questo messaggio è il comportamento previsto dell'autenticazione tramite socket quando funziona correttamente, non indica un'installazione danneggiata.
ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' dall'applicazione, pur usando la password corretta. L'account esiste come 'appuser'@'localhost', ma la tua applicazione si connette via TCP a 127.0.0.1 su un server dove la risoluzione del nome host è disabilitata (skip-name-resolve). MariaDB considera i due come host differenti: localhost è il socket Unix, 127.0.0.1 è il TCP. Punta l'applicazione all'host localhost affinché utilizzi il socket e corrisponda all'account, oppure crea un secondo account 'appuser'@'127.0.0.1' se il framework comunica esclusivamente via TCP.
AH01071: Got error 'Primary script unknown' in /var/log/apache2/testapp-error.log, con il browser che mostra File not found.. Apache ha inoltrato la richiesta a PHP-FPM, ma FPM non è riuscito a trovare lo script nel percorso indicato da Apache. Due cause comuni: il socket FPM nella tua configurazione punta a una versione di PHP non installata (il socket 8.3 è ancora configurato dopo un aggiornamento di release che ti ha portato a PHP 8.5), oppure il file non è presente perché DocumentRoot e la directory reale non coincidono. Verifica che il socket esista con ls -l /run/php/, conferma che DocumentRoot corrisponda alla posizione reale del file e riavvia sia php8.3-fpm (o la versione di PHP-FPM effettivamente installata) che apache2.
AH00558: apache2: Could not reliably determine the server's fully qualified domain name a ogni riavvio. Si tratta di un avviso innocuo, non di un errore: Apache ti informa che non è impostato alcun ServerName globale. Silenzialo scrivendo ServerName your.domain in /etc/apache2/conf-available/servername.conf ed eseguendo sudo a2enconf servername.
(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80 all'avvio di Apache. Un altro server web sta già occupando la porta 80, spesso un processo nginx residuo da un esperimento precedente. Individualo con sudo ss -ltnp | grep :80, quindi arresta e disabilita l'altro servizio prima di avviare Apache.
FAQ
mod_php o PHP-FPM: quale devo usare?
Usa PHP-FPM. mod_php incorpora un interprete in ogni processo Apache e impone l'MPM lento prefork, quindi Apache utilizza risorse aggiuntive per PHP anche quando serve un'immagine statica. PHP-FPM esegue PHP in un pool separato, configurabile in modo indipendente, che Apache raggiunge tramite un socket. Funziona con l'MPM event, più veloce e basato sui thread, e consente di passare in seguito a nginx senza modifiche sostanziali. È la scelta predefinita moderna; mod_php è appropriato solo per un'applicazione legacy che dipende da un comportamento specifico all'interno del processo.
Perché il browser scarica il file PHP invece di eseguirlo?
Apache tratta il file .php come un download statico perché non gli è associato alcun gestore PHP. Su Ubuntu 24.04 con FPM, significa che manca uno tra sudo a2enmod proxy_fcgi, sudo a2enconf php8.3-fpm o il riavvio successivo di Apache. Eseguili tutti e tre e ricarica la configurazione, quindi verifica con apache2ctl -M | grep fcgi che proxy_fcgi_module sia elencato. Finché non risolvi il problema, il server espone il codice sorgente: trattalo come un problema urgente.
Perché l'accesso root è negato in MariaDB anche con la password corretta?
Perché non esiste alcuna password: MariaDB su Ubuntu autentica l'account root tramite unix_socket, associandolo all'utente root del sistema operativo. mysql -u root eseguito da una shell normale restituisce ERROR 1698 (28000): Access denied for user 'root'@'localhost' per progettazione. Usa invece sudo mysql e crea un utente separato con autenticazione tramite password per ogni applicazione, invece di riutilizzare root.
Come posso aggiungere HTTPS al mio sito LAMP?
Installa certbot e python3-certbot-apache, configura il record A del dominio in modo che punti al server, quindi esegui sudo certbot --apache. L'autenticatore Apache dimostra il controllo del dominio tramite l'istanza Apache in esecuzione. L'installer modifica la configurazione dell'host virtuale per la porta 443 e configura il rinnovo automatico. La guida completa a Certbot e Apache descrive la challenge, il timer di rinnovo e i problemi più comuni.