Das der nginx-Webserver schneller und performanter als z.B. der Apache ist, hatte ich in meinem letzten Blogbeitrag zur Installation und Konfiguration von nginx bereits geschrieben. Wenn WordPress oder PHP auf dem Apache mit mod_php generell zu langsam werden, macht auch hier die Überlegung Sinn, auf einen anderen Webserver umzusteigen. In Sachen Performance und Performance-Optimierung nicht nur für WordPress und PHP ist nginx aktuell die erste Wahl.
Bei den meisten Installationen unter Apache läuft PHP als Modul. Das heisst, für das Ausführen von PHP muß kein neuer Prozess gestartet werden, sondern die Verarbeitung bzw. Parsen der PHP-Dateien übernimmt das beim Start des Apachen geladene PHP-Modul. Für nginx gibt es solch ein Modul nicht, weshalb wir auf die CGI-Version von PHP zurückgreifen müssen. Dies ist nicht weiter tragisch, da die Performance – eine halbwegs schnelle CPU vorausgesetzt – aus Erfahrung nicht darunter leidet. Doch wie konfiguriert man nginx, um ihn für das Ausliefern von dynamischen PHP-Scripts vorzubereiten?
Zuallererst benötigen wir nun die CGI-Version von PHP. Falls sie noch nicht installiert ist, können wir sie entweder selbst kompilieren oder verwenden von der Linux-Distribution bestehende Pakete. Für Ubuntu ist die Installation hier mit einer Zeile erledigt:
# apt-get install php5-cgi
Sofern man für WordPress zum Beispiel noch mehr Module wie MySQL oder GD benötigt, könnte die Zeile auch so aussehen:
# apt-get install php5-cgi php5-dev php5-gd php5-mysql
Damit wir PHP mit nginx nutzen können, benötigen wir sogenannte Sockets unter denen das PHP auf Anfragen von nginx wartet, wahlweisse als TCP- oder UNIX-Domain-Socket. Da ein Socket ständig auf neue Verbindungen wartet, muß ein neuer Prozess erzeugt werden, der diese Arbeit übernimmt. Dieser Prozess erzeugt weitere Unterprozesse und leitet die PHP-Anfragen dann an diese weiter.
Es gibt nun mehrere Möglichkeiten die PHP-Prozesse zu starten. Die Aufgabe ist hier, die Prozesse als Daemon und unter einem anderen User laufen zu lassen, sowie die Standard- und Fehler-Ausgaben umzuleiten.
Zwei Möglichkeiten möchte ich hier aufzeigen: Zum einen kann man die Prozesse ganz normal per Startscript starten, auf Ubuntu zum Beispiel mit dem start-stop-daemon. Zum anderen gibt es Tools wie spawn-fcgi, mit dem ich jedoch nicht so gute Erfahrungen gemacht habe, da es bei mir öfters, teilweisse sogar mit Segfaults, unter Last abstürzte. Da ich aber keine Zeit hatte, dem Grund auf die Spur zu gehen und PHP mit dem start-stop-daemon bei mir ziemlich gut läuft, möchte ich euch spawn-fcgi der Vollständigkeithalber nicht vorenthalten. Sicher auch hilfreich für andere Linux-Distributionen.
Die in PHP mitgelieferte CGI-Version php5-cgi erledigt im FastCGI-Modus die Socketbindung und das Erzeugen von neuen Prozessen selbstständig und sehr zuverlässig. Zum Starten benötigen wir ein Startscript, welches wir unter /etc/init.d/fastcgi anlegen:
# vi /etc/init.d/fastcgi
Inhalt:
#!/bin/bash
BIND=127.0.0.1:8088
USER=www
PHP_FCGI_CHILDREN=15
PHP_FCGI_MAX_REQUESTS=1000
PHP_CGI=/usr/bin/php5-cgi
PHP_CGI_NAME=`basename $PHP_CGI`
PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS $PHP_CGI -b $BIND"
RETVAL=0
start() {
echo -n "Starting PHP FastCGI: "
start-stop-daemon --quiet --start --background --chuid "$USER" --exec /usr/bin/env -- $PHP_CGI_ARGS
RETVAL=$?
echo "$PHP_CGI_NAME."
}
stop() {
echo -n "Stopping PHP FastCGI: "
killall -q -w -u $USER $PHP_CGI
RETVAL=$?
echo "$PHP_CGI_NAME."
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: php-fastcgi {start|stop|restart}"
exit 1
;;
esac
exit $RETVAL
Die ersten fünf Variablen werden zur Konfiguration benötigt und bedeuten folgendes:
BIND: IP-Adresse und Port, unter denen auf Verbindungen gewartet wird
USER: Benutzer, unter dem die Prozesse laufen
PHP_FCGI_CHILDREN: Anzahl der zu forkenden bzw. erstellenden Prozesse
PHP_FCGI_MAX_REQUESTS: Anzahl von Requests, nach denen der Prozess stirbt (neue werden automatisch erstellt)
PHP_CGI: Pfad zu php5-cgi
Evtl. muß nun noch der Pfad zu php5-cgi und der Benutzer angepasst werden und wir können das Script nach einem chmod ausführen:
# chmod 700 /etc/init.d/fastcgi
# /etc/init.d/fastcgi start
Mit dem Befehl „ps ax“ sollten wir nun in der Prozessliste unsere PHP-Prozesse laufen sehen und gehen jetzt zur nginx-Konfiguration über.
spawn-fcgi macht eigentlich auch nichts anderes als das Startscript unter Ubuntu. Es erstellt einen TCP/Unix-Domain-Socket und die gewünschte Anzahl von Prozessen unter einem bestimmten Benutzer und leitet die Ausgaben ensprechend um. Zur Installation benötigen wir natürlich die aktuellen Sourcen von spawn-fcgi, deren Link wir auf der News-Seite des Projektes erhalten. Download, Entpacken und ins Verzeichnis wechseln:
# wget http://www.lighttpd.net/download/spawn-fcgi-1.6.3.tar.gz
# tar -xvzf spawn-fcgi-1.6.3.tar.gz
# cd spawn-fcgi-1.6.3
Nun folgt das bekannte Zweiergespann zum Kompilieren. spawn-fcgi besitzt keine Install-Routine, weshalb wir dies selbst vornehmen müssen:
# ./configure
# make
# cp ./src/spawn-fcgi /usr/bin/spawn-fcgi
Nun können wir das Tool das erste mal starten.
/usr/bin/spawn-fcgi -a 127.0.0.1 -p 8088 -F 3 -u www -f /usr/bin/php5-cgi
Die Bedeutung der Optionen:
-a IP-Adresse, an der auf Verbindungen gewartet wird -p Port, an dem auf Verbindungen gewartet wird -w User unter dem die Prozesse laufen sollen -f Pfad zum PHP-CGI -F Anzahl der Prozesse, die geforkt werden sollen
Wenn wir bereits einen fertigen nginx am Laufen haben, sind nur wenige Zeilen in der Konfiguration zu ergänzen. Ansonsten lies bitte zuerst meine Anleitung zur Installation und Konfiguration des nginx.
Innerhalb des Vhosts, also innerhalb von server {} fügen wir eine neue Location-Direktive für PHP-Files hinzu:
location ~ .*\.php$ {
root /var/www/meinedomain.de/htdocs;
fastcgi_pass 127.0.0.1:8088;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Hier darauf achten, das die Angaben in fastcgi_pass mit denen der vorhin konfigurierten übereinstimmen. include fastcgi_params inkludiert eine Datei, die bei der nginx-Installation enthalten ist, möchte Sie hier jedoch abbilden für den Fall das…
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
Damit nginx auch beim Aufruf der Domain ohne Dateiname in der URL die Startseite als PHP ausführt, müssen wir zwei Parts in der bestehenden Konfiguration unter server {} anpassen bzw neu einsetzen:
Als erstes machen wir die Datei index.php zur ersten Wahl beim Aufruf ohne Dateiname:
index index.php index.html index.htm;
Alles andere was nicht gefunden wird, leiten wir ebenfalls auf index.php weiter:
# this sends all non-existing file or directory requests to index.php
if (!-e $request_filename) {
rewrite . /index.php last;
}
Das komplett Konfigfile könnte nun so aussehen:
server {
# IP-Adresse und Port, an denen für Web-Zugriffe gelauscht wird
listen 1.2.3.4:80;
# Domains, auf die dieser Server hören soll
server_name www.meinedomain.de meinedomain.de;
# Hauptverzeichnis für Dokumente
root /var/www/meinedomain.de/htdocs;
# Ort des Access-Logfiles
access_log /var/www/meinedomain.de/logs/access_log vhosts;
# Definition des Index-Files (Startseite, für Anfragen ohne Dateiname)
index index.php index.html index.htm;;
# Wenn Zugriff auf andere als Standarddomain,
# auf diese per 301 permanent weiterleiten
if ($host != 'www.meinedomain.de' ) {
rewrite ^/(.*)$ http://www.meinedomain.de/$1 permanent;
}
# Für Bilder und einige andere Dateitypen Access-Log ausschalten und
# Expire-Zeit auf 7 Tage erhöhen (sendet den entspr. Expire-Header)
location ~* ^.+\.(js|css|jpg|jpeg|gif|png|pdf|zip|rar)$ {
access_log off;
expires 7d;
}
location / {
# Nützlich für SSL und evtl. später hinzukommende Scriptsprachen
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
# Wenn die angeforderete statische Datei existiert, diese ausliefern
# ohne die anderen Regeln weiter unten zu beachten
if (-f $request_filename) {
break;
}
# this sends all non-existing file or directory requests to index.php
if (!-e $request_filename) {
rewrite . /index.php last;
}
}
location ~ .*.php$ {
root /var/www/meinedomain.de/htdocs;
fastcgi_pass 127.0.0.1:8088;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Jetzt heißt es den nginx mit /etc/init.d/nginx restart
neu starten und PHP ist einsatzbereit.
WordPress kann nun ganz normal installiert und verwendet werden.
Insofern man ein Cache-Modul wie WP-Super-Cache nutzt, sollte man noch die entsprechenden RewriteRules für die statischen HTML-Dateien setzen. Für WP-Super-Cache beispielsweisse einfach folgende Zeilen innerhalb location / {} und vor der Einstellung für die .php-Dateien kopieren:
if (-f $document_root/cache$fastcgi_script_name) {
rewrite (.*) /cache$1 break;
}
if (-f $document_root/cache/$fastcgi_script_name/index.html) {
rewrite (.*) /cache/$1/index.html break;
}
if (-f $document_root/cache$fastcgi_script_name.html) {
rewrite (.*) /cache$1.html break;
}
Geschafft! Jetzt haben wir nginx mit PHP laufen und können uns über die bessere Performance freuen. Wenn du noch weitere Beispiele für Konfigurationen, auch für andere Sprachen und Einsatzmöglichkeiten suchst, empfiehlt sich ein Blick auf die Configuration-Seite von nginx.
Sicher folgen noch einige Artikel mehr über den nginx, um meine Begeisterung kund zu tun ;-) Solltest Du also Fragen, Wünsche, Vorschläge oder sonstiges haben, ab damit in die Kommentare, ich freue mich!
Credits:
Original-Foto Strap-On Video Rocket unter Creative Commons von jurvetson auf Flickr. Danke!
AdPoint GmbH
ein praktisches Programm, das du hier präsentierst. Wird es aktuell noch geupdated?
Viele Grüße, Felix von der AdPoint GmbH
Marcus
danke für die Anleitung.
Allerdings sind hier einige Performance-Fallen mit if-Abfragen, die auf der offiziellen Seite von nginx erklärt werden.
http://wiki.nginx.org/Pitfalls
Devko
RobSe
Nico
mit Joomla habe ich noch keine Erfahrungen gemacht, könnte mir aber vorstellen, das es auch mit evtl. Anpassung der Rewrite-Rules ohne Probleme funktioniert. Im Nginx-Wiki gibt es direkt auch eine Beispiel-Konfiguration:
http://wiki.nginx.org/Joomlahttp://wiki.nginx.org/Joomla">http://wiki.nginx.org/Joomla>
Der Performance-Zuwachs kommt u.a. auf die Grösse des Traffics, also auf die Anzahl der Anfragen an. Wenn Joomla in der Lage ist, statische HTML-Files zu erzeugen, die der Nginx dann ausliefern kann, wäre dies nochmals ein Pluspunkt.
Viele Grüsse,
Nico
Bernd
Grüße,
Bernd
Was denkst du?