Vorwort
Die meisten Anleitungen für einen Apache2 mit PHP lassen den User PHP als Apache-Modul installieren. Das hat der Vorteil, dass es extrem einfach zu installieren ist und keinen weiteren Konfigurationsaufwand braucht. Für Webserver, auf denen nur eine Site läuft, ist das oft auch ausreichend. Sobald aber mehr als eine Site betrieben wird, muss man sich darüber Gedanken machen, dass PHP immer unter dem Webserver-Nutzer (per default www-data) läuft. Eine Implikation daraus ist, dass wenn eine Site erfolgreich gehackt wird, der Angreifer auch Zugriff auf die andere Site hat, und auch auf alle Inhalte, die der Nutzer www-data lesen darf. Das ist sicherheitstechnisch bedenklich. Nun gibt es verschiedene Möglichkeiten dieses Problem zu lösen. Eines wäre PHP via SuExec und FastCGI anzusprechen. Das ist hier im Hetzner-Wiki erklärt. Eine andere Lösung ist PHP FPM (FastCGI Process Manager) zu nutzen. Das soll hier erklärt werden.
Installation
Ein funktionierender Apache wird vorausgesetzt. Fall noch nicht installiert braucht man noch das FastCGI-Modul. Außerdem brauchen wir auch PHP-FPM.
$ apt-get install libapache2-mod-fcgid php5-fpm
$ a2enmod proxy_fcgid
$ apache2ctl configtest && apache2ctl graceful
Damit ist das FastCGI-Proxy-Modul installiert und aktiviert. PHP-FPM sollte fehlende Abhängigkeiten ebenfalls mit installiert haben.
Konfiguration
PHP-FPM Konfiguration
Die grundsätzliche Konfiguration von PHP-FPM wird in /etc/php5/fpm/php-fpm.ini gemacht. Dort kann man reinschauen, muss aber meist nichts ändern. Wichtig sind die sogenannten Pool-Konfigurationen. Diese liegen unter /etc/php5/fpm/pool.d. Eine Default-Konfiguration liegt schon dort (www.conf), allerdings sollte diese nur zu Referenzzwecken verwendet werden. Um diesen Pool zu deaktivieren kann man ihn einfach umbenennen:
mv /etc/php5/fpm/pool.d/www.conf /etc/php5/fpm/pool.d/www.conf_inactive
Eine neue Pool-Konfiguration hingegen könnte beispielsweise so aussehen:
[cloud]
user = www-user-cloud
group = www-user-cloud
listen = /var/run/php5-fpm-cloud.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 256
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
;pm.status_path = /status
;ping.path = /ping
access.log = "/var/www/sites/cloud.misterunknown.de/logs/php-$pool.access.log"
access.log = "/var/www/sites/cloud.misterunknown.de/logs/php-$pool.access.log"
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
slowlog = "/var/www/sites/cloud.misterunknown.de/logs/php-$pool.slow.log"
security.limit_extensions = .php .php3 .php4 .php5
php_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[max_execution_time] = 600
php_admin_value[error_reporting] = E_ALL
php_admin_value[open_basedir] = "/var/www/sites/cloud.misterunknown.de:/var/www/sites/cloud.misterunknown.de/tmp:/data/owncloud:/dev/urandom"
In der ersten Zeile, in eckigen Klammern, steht der Name des Pools. Anschließend folgt die Konfiguration für User und Gruppe. Der Wert „listen“ beschreibt, an welcher Stelle der Pool lauscht. Das kann, wie hier, ein Socket sein; das kann aber auch ein Netzwerk-Port sein. WICHTIG: Der Apache kann erst seit Version 2.4.10 Proxy für Unix-Sockets sein. Die genaue Syntax kann in der Beispieldatei (www.conf) nachlesen. Anschließend folgen einige Optionen zum ProcessManager (pm.*), und einige Einstellungen zum Logging. Die Einstellung security.limit_extensions ist ein wichtiges Sicherheitsfeature, welches aktiviert werden sollte.
Wer von SuExec/FastCGI umsteigt fragt sich vielleicht, ob man einzelnen Pools separate php.ini-Dateien mitgeben kann. Das kann man zwar nicht, allerdings lassen sich PHP-Optionen direkt in der Pool-Konfiguration setzen. Mehr zur Konfiguration von PHP und dem Unterschied zwischen flag und value kann man hier nachlesen.
Hinweis: In jedem Fall sollte man die Option open_basedir sinnvoll setzen. Das verhindert, dass PHP außerhalb des definierten Bereiches Dateien lesen kann, auch wenn es die reinen Benutzerrechte zulassen würden.
Wenn man seine Pools fertig konfiguriert hat, kann man den FPM neu starten:
/etc/init.d/php5-fpm restart
Um zu kontrollieren, ob auch alles korrekt ist, sollte man noch nachschauen, dass die entsprechenden Sockets existieren:
$ ls -l /var/run | grep php5-fpm
srw-rw---- 1 www-data www-data 0 Okt 20 16:07 php5-fpm-cloud.sock
-rw-r--r-- 1 root root 4 Okt 20 16:07 php5-fpm.pid
Man sieht sowohl das PID-File als auch den Socket: Alles ok!
Apache2 Konfiguration
Im Apache muss man natürlich noch den PHP-FPM referenzieren. Das ist recht simpel, und geht so:
<VirtualHost *:80>
…
DocumentRoot /var/www/sites/cloud.misterunknown.de/htdocs
<Directory /var/www/sites/cloud.misterunknown.de/htdocs>
Require all granted
AllowOverride All
</Directory>
<FilesMatch "\.php$" >
SetHandler "proxy:unix:/var/run/php5-fpm-cloud.sock|fcgi://localhost"
</FilesMatch>
…
</VirtualHost>
Der markierte Bereich ist wichtig. Hat man keine Unix-Sockets, sondern Netzwerk-Sockets sieht das ganze so aus:
<VirtualHost *:80>
…
DocumentRoot /var/www/sites/cloud.misterunknown.de/htdocs
<Directory /var/www/sites/cloud.misterunknown.de/htdocs>
Require all granted
AllowOverride All
</Directory>
<FilesMatch "\.php$" >
SetHandler "proxy:fcgi://127.0.0.1:9000"
</FilesMatch>
…
</VirtualHost>
Anschließend noch den Apache2 neu laden, und das Setup sollte funktionieren:
$ apache2ctl configtest && apache2ctl graceful
Fazit
Der Konfigurationaufwand für PHP via FPM hält sich in Grenzen, aber der Sicherheitsgewinn ist enorm, gerade wenn man auch Webseiten betreibt, die von anderen Usern gepflegt werden.