SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-02

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 apache2

apt 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 list

Vedrai 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 enable

Consenti 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 mariadb

Ubuntu 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_installation

Leggere 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-zip

Notare 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 apache2

a2enconf 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/testapp

La 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 apache2

apache2ctl 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.php

Visita 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.php

phpinfo() 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 mysql

Creare 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 appdb

Il 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 accesso tramite HTTP non crittografato invia le password in chiaro e tutti i browser moderni contrassegnano la pagina come "Non sicura". Certbot risolve il problema con un solo comando. Installarlo con il plugin Apache:

sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache

Certbot usa due plugin in questo caso. L'autenticatore apache dimostra che si controlla il dominio servendo temporaneamente un file di challenge tramite Apache in esecuzione. L'installer apache modifica quindi la configurazione del virtual host per aggiungere il blocco 443, lo associa al nuovo certificato e reindirizza per impostazione predefinita tutto il traffico HTTP a HTTPS. A partire da Certbot 2.0 non viene posta alcuna domanda sul reindirizzamento; passare --no-redirect se è necessario continuare a servire HTTP non crittografato. Poiché nel passaggio 5 è stato impostato un ServerName reale, Certbot rileva automaticamente il dominio. I certificati durano 90 giorni e il pacchetto installa un timer systemd che li rinnova; verificare il timer con sudo certbot renew --dry-run, il cui output dovrebbe terminare con Congratulations, all simulated renewals succeeded.

Per la procedura completa relativa alla challenge, al timer di rinnovo e ai requisiti DNS e del firewall, consultare la guida complementare su come emettere certificati TLS gratuiti Let's Encrypt con Certbot su Apache.

Backup, aggiornamenti e messa in sicurezza

Eseguire il backup dei due elementi che contengono lo stato del sistema: i database e la web root. Un dump logico notturno è l'approccio affidabile più semplice, sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz', quindi copiarlo fuori dal server. Racchiudere l'intera pipeline in sudo sh -c è importante: senza questa operazione, la shell esegue il redirect > /root/... con l'utente corrente e restituisce Permission denied, perché solo mysqldump ha ereditato sudo. --single-transaction crea uno snapshot coerente delle tabelle InnoDB senza bloccarle. Abbinarlo a un tar di /var/www e /etc/apache2/sites-available consente di ricostruire l'intero stack su un nuovo VPS a partire da questi file.

Gli aggiornamenti sono una normale sudo apt update && sudo apt upgrade. L'unico aggiornamento problematico è un incremento della versione di PHP: quando una futura versione di Ubuntu imposterà PHP 8.4 come versione predefinita, apt potrebbe installare php8.4-fpm insieme alla 8.3, il socket diventerebbe /run/php/php8.4-fpm.sock e la configurazione di Apache continuerebbe a puntare al socket della 8.3. Abilitare la nuova configurazione (sudo a2enconf php8.4-fpm) e disabilitare quella precedente; in caso contrario, il sito inizierebbe a restituire Primary script unknown dopo un aggiornamento altrimenti ordinario. Poiché le release di PHP avanzano più rapidamente di una distribuzione LTS, consultare le note di rilascio della versione corrente di PHP invece di fissare una versione di patch.

Il primo giorno è opportuno applicare due misure di hardening. Per prima cosa, configurare Fail2Ban per monitorare SSH sul server: un VPS pubblico riceve tentativi di accesso automatizzati entro pochi minuti e un jail riduce migliaia di tentativi a pochi prima di applicare un ban. In secondo luogo, se si preferisce gestire gli host virtuali di Apache, i database MariaDB e gli utenti tramite un browser invece di modificare manualmente i file, il pannello di controllo Webmin basato sul web opera sopra questo stesso stack e gestisce gli stessi file di configurazione appena creati. Nessuno dei due strumenti sostituisce la comprensione dei singoli componenti, ma entrambi riducono le attività ripetitive quotidiane.

Modalità di errore, con le stringhe visualizzate

La pagina predefinita non scompare. Hai modificato il virtual host, hai ricaricato la configurazione, ma il browser continua a mostrare "Apache2 Ubuntu Default Page" e il relativo banner "It works!". Apache serve il primo virtual host corrispondente. Quando nessun ServerName corrisponde alla richiesta, prevale la configurazione con il primo nome in ordine alfabetico: 000-default.conf viene prima di testapp.conf. Il nome host della richiesta non corrisponde al tuo ServerName, oppure non hai mai eseguito sudo a2dissite 000-default. Disabilita quello predefinito con sudo systemctl reload apache2 e verifica con apache2ctl -S. Questo comando stampa la mappa dei virtual host e mostra quale configurazione gestisce quello predefinito. Svuota anche la cache del browser: una risposta 200 memorizzata dalla vecchia pagina può continuare a essere visualizzata.

Un file .php viene scaricato invece di essere eseguito. Apri info.php e il browser scarica un file contenente il codice sorgente <?php non elaborato, oppure lo mostra come testo normale, invece di eseguirlo. Apache serve il file come risorsa statica perché il gestore PHP non è associato, hai saltato sudo a2enmod proxy_fcgi o sudo a2enconf php8.3-fpm oppure non hai riavviato Apache. Esegui tutti e tre i comandi (Passaggio 4) e ricarica la pagina. Verifica 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 problema estetico. Risolvilo prima di mettere dati o applicazioni 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 usa l'autenticazione unix_socket. Pertanto accetta la connessione solo quando l'utente del sistema operativo è effettivamente root. La soluzione è sudo mysql, senza -u root e senza password. Questo messaggio indica il comportamento previsto dell'autenticazione tramite socket. Non indica un'installazione danneggiata.

ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' dall'applicazione, con la password corretta. L'account esiste come 'appuser'@'localhost', ma l'applicazione si connette tramite TCP a 127.0.0.1 su un server in cui la risoluzione dei nomi host è disabilitata (skip-name-resolve). MariaDB considera quindi i due host diversi: localhost è il socket Unix, mentre 127.0.0.1 è TCP. Configura l'applicazione con l'host localhost, in modo che utilizzi il socket e corrisponda all'account, oppure crea un secondo account 'appuser'@'127.0.0.1' se il framework supporta solo TCP.

AH01071: Got error 'Primary script unknown' in /var/log/apache2/testapp-error.log, mentre il browser mostra File not found.. Apache ha inoltrato la richiesta a PHP-FPM, ma FPM non ha trovato lo script nel percorso ricevuto da Apache. Le cause più comuni sono due: il socket FPM nella configurazione punta a una versione di PHP non installata, ad esempio un socket php8.4 dopo un aggiornamento mentre è in esecuzione solo 8.3; oppure il file non esiste realmente perché DocumentRoot e la directory effettiva non corrispondono. Verifica che il socket esista con ls -l /run/php/, conferma che DocumentRoot corrisponda alla posizione del file e riavvia sia php8.3-fpm sia apache2.

AH00558: apache2: Could not reliably determine the server's fully qualified domain name a ogni riavvio. È un avviso innocuo, non un errore. Apache segnala che non è impostato alcun ServerName globale. Disattiva l'avviso 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à usando la porta 80, spesso un processo nginx rimasto 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.

#lamp#apache#mariadb#php-fpm#ubuntu