This is an old revision of the document!
Table of Contents
Ottimizzazione di Apache con PHP
Anzitutto si verificare quale MPM (Multi-Processing Module) è in uso:
apachectl -V | grep 'Server MPM' Server MPM: prefork
I moduli disponibili installati dal pacchetto apache2 sono event, prefork e worker.
Per una configurazione Apache + PHP-FPM, il modulo MPM event è la scelta preferita. Offre migliori prestazioni, scalabilità ed efficienza gestendo le connessioni in modo asincrono, mentre PHP-FPM elabora separatamente gli script PHP.
Modulo MPM prefork e mod-php
Con una installazione standard Debian di Apache e PHP è probabile che sia in uso il modulo prefork. Questo modulo prevede che ogni richiesta HTTP venga eseguita da un singolo thread e questo garantisce che funzionino correttamente anche le librerie non-thread-safe, come è il PHP. Un processo centrale gestisce il pool di istanze Apache che vengono attivate.
Questa modalità in generale è attivata semplicemente installando il pacchetto apache2-bin e il relativo libapache2-mod-php nella opportuna versione. Ad esempio in Debian 12 Bookworm abbiamo libapache2-mod-php8.2. In questo caso il PHP è semplicemente un modulo di Apache che viene abilitato tramite due link:
- /etc/apache2/mods-enabled/php8.2.conf
- /etc/apache2/mods-enabled/php8.2.load
In questo modo il modulo PHP viene caricato in ogni istanza del processo Apache, anche se il server deve fornire pagine statiche senza PHP. Inoltre il fatto che PHP sia not thread safe, costringe ad usare il MPM prefork, che è la configurazione più lenta possibile.
La configurazione del modulo è in /etc/apache2/mods-available/mpm_prefork.conf. Ad esempio è possibile aumentare il numero massimo di workers attivabili, ecc:
StartServers 5 MinSpareServers 5 MaxSpareServers 10 MaxRequestWorkers 150 MaxConnectionsPerChild 0
Modulo MPM event, proxy_fcgi e php-fpm
Secono le raccomandazioni più recenti tuttavia il modo più efficiente per eseguire Apache + PHP è tramite il modulo Apache proxy_fcgi che si interfaccia con il motore PHP tramite php-fpm (PHP FastCGI Process Manager).
I pacchetti Debian 12 da installare sono:
- libapache2-mod-fcgid
- php-fpm che dipende dalla specifica versione php8.2-fpm
Il modulo fcgid dovrebbe essere attivato automaticamente durante l'installazione del pacchetto, verificare che esistano i link simbolici:
- /etc/apache2/mods-enabled/fcgid.conf
- /etc/apache2/mods-enabled/fcgid.load
Il modulo proxy_fcgi invece va attivato manualmente:
a2enmod proxy_fcgi
È necessario anche attivare la configurazione per collegare Apache al motore php-fpm, eseguendo il comando:
a2enconf php8.2-fpm
In questo modo viene creato il link /etc/apache2/conf-enabled/php8.2-fpm.conf. Questo file indica ad Apache come gestire i file PHP, cioè interagendo con l'engine PHP tramite il socket /run/php/php8.2-fpm.sock.
Per sicurezza verifichiamo che il modulo legacy mod-php sia disabilitato e disinstalliamo definitivamente il pacchetto:
a2dismod php8.2 dpkg --purge libapache2-mod-php8.2
In questa configurazione l'engine PHP è in effetti un servizio che può essere gestito con i comandi standard systemctl (start, stop, status, ecc.):
systemctl status php8.2-fpm
Infine si cambia il modulo MPM da prefork a event:
a2dismod mpm_prefork a2enmod mpm_event systemctl restart apache2.service
Controllare che le pagine PHP siano correttamente interpretate e che il modulo MPM event sia effettivamente in uso:
apachectl -V | grep 'Server MPM' Server MPM: event
Altri moduli Apache consigliati
- setenvif
- actions
- alias
- headers
Configurazione
Le impostazioni PHP che nell'installazione legacy mpm-prefork e mod-php andavano nel file /etc/php/8.2/apache2/php.ini
vanno impostate in /etc/php/8.2/fpm/php.ini. Per renderle effettive è sufficiente riavviare il servizio PHP:
systemctl reload php8.2-fpm.service
Con la configurazione legacy mpm-prefork e mod-php le direttive per la configurazione dell'engine PHP potevano essere incluse nella configurazione di apache tramite php_value
, php_flag
, php_admin_value
e php_admin_flag
(vedere How to change configuration settings). Ad esempio in un VirtualHost potevamo avere:
<VirtualHost *:80> ... php_admin_flag display_errors On </VirtualHost>
Con la configurazione proxy_fcgi e php-fpm predefinita abbiamo una sola istanza dell'engine PHP che viene configurata dal file /etc/php/8.2/fpm/pool.d/www.conf. In questo file viene configurato il socket di comunicazione Apache - PHP e si possono mettere le varie opzioni, ad esempio:
listen = /run/php/php8.2-fpm.sock ... php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com php_flag[display_errors] = on
Questa configurazione vale per tutti gli eventuali VirtualHost esistenti, poiché il file /etc/apache2/conf-enabled/php8.2-fpm.conf indica genericamente quale socket utilizzare:
<FilesMatch ".+\.ph(?:ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost" </FilesMatch>
Configurazione differenziata per VirtualHost
Se si desidera avere una configurazione PHP differente in base al VirtualHost è necessario provvedere come segue:
- Creare un file ad esempio /etc/php/8.2/fpm/pool.d/www.domain.tld.conf cona la configurazione PHP ad-hoc per il VirtualHost. Deve usare un socket diverso da quelli esistenti (vedi esempio sotto).
- Riavviare il servizio php8.2-fpm.service.
- Nella configurazione del VirtualHost dichiarare che l'handler PHP è il socket creato sopra.
Questo è un esempio di file /etc/php/8.2/fpm/pool.d/www.domain.tld.conf, per creare una nuova istanza del php-fpm:
[www.domain.tld] user = www-data group = www-data listen = /run/php/php-fpm.www.domain.tld.sock listen.owner = www-data listen.group = www-data pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3
Queste sono le righe da aggiungere nella sezione VirtualHost:
<IfModule proxy_fcgi_module> <FilesMatch ".+\.ph(?:ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php-fpm.www.domain.tld.sock|fcgi://localhost" </FilesMatch> </IfModule>
Segregazione VirtualHost
Su un host che ospita più di un VirtualHost potrebbe essere desiderabile isolare il PHP di un VirtualHost dai file degli altri VirtualHost ed anche dai file dell'intero sistema. Dal punto di vista della sicurezza ad esempio non è bene che da PHP sia possibile fare l'fopen()
di file tipo /etc/passwd oppure del file wp-config.php del Wordpress installato su un altro VirtualHost.
Essenzialmente si può agire con un chroot per processo PHP oppure con la direttiva open_basedir del PHP.
Il primo metodo è quello formalmente più corretto e sicuro, in quanto il processo PHP sarebbe effettivamente in esecuzione in un ambiente chroot e sarebbe il sistema operativo stesso a impedirgli di accedere fuori dalla propria DocumentRoot. Tuttavia questo metodo comporta notevoli problemi perché ad esempio impedisce l'accesso alle librerie PHP o JavaScript installate a livello di sistema.
Il metodo che utilizza la direttiva open_basedir invece è più flessibile, ma affida la sicurezza all'assenza di bug nell'implementazione del linguaggio PHP.
Metodo chroot
Supponiamo che la DocumentRoot di un sito sia /var/www/html/site.example.org/www/, queste sono le direttive da aggiungere al file di configurazione /root/pool.d.include/site.example.org.conf:
[site.example.org] user = site-example-org group = site-example-org ; This PHP-FPM instance is chrooted. chroot = /var/www/html/site.example.org chdir = /www ; Values set here with php_admin_value belong to this pool only. ; doc_root: (without chroot would be empty or the Apache DocumentRoot). php_admin_value[doc_root] = /www ; cgi.fix_pathinfo: this is mandatory to let the chroot works. ; See https://www.php.net/manual/en/ini.core.php#ini.cgi.fix-pathinfo php_admin_value[cgi.fix_pathinfo] = 0
In questo modo si ha un processo php-fpm che gira con le credenziali di user e group indicate e che è chrooted nella directory indicata.
Purtroppo questa soluzione impedisce il corretto funzionamento delle librerie PHP, ad esempio il pacchetto php-mail installa i propri file in /usr/share/php/ ed è impossibile eseguire il comando require_once “Mail.php”;
.
Metodo open_basedir
Con la direttiva PHP open_basedir si pone un limite a tutte le funzioni PHP che aprono file dal filesystem (ad esempio include
o fopen()
). Il manuale PHP tuttavia avverte: Caution open_basedir is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed.
Ecco quindi come configurare l'istanza PHP-FPM di un VirtualHost nel file /etc/php/7.4/fpm/pool.d/site.example.org.conf:
[site.example.org] user = site-example-org group = site-example-org php_admin_value[open_basedir] = /var/www/html/site.example.org:/usr/share/phpmyadmin:/usr/share/doc/phpmyadmin:/etc/phpmyadmin:/var/lib/phpmyadmin:/usr/share/php:/usr/share/javascript:/tmp
Come si vede è necessario avere l'accortezza di includere tutte le directory delle estensioni installate a livello di sistema. Nell'esempio sopra è compreso il pacchetto phpMyAdmin, le varie librerie PHP e JavaScript. Anche la directory /tmp potrebbe essere utilizzata per la creazione di file temporanei, ecc.
Web Resources
- Running PHP with fcgid -
Se si esegue Apache 2.4 è preferibile il metodo php-fpm.