Tutorial: Server Setup mit nginx, HHVM und WordPress

Speed up all PHP things!

Wer PHP-basierte Webseiten ausliefern möchte, hat mittlerweile eine Menge Möglichkeiten, viele werden den Wechsel von Apache zu nginx in Kombination mit PHP-FPM bereits hinter sich haben, welcher bereits einen ansehnlichen Performance-Vorteil gebracht hat.

Durch den Einsatz von HHVM und dessen JIT-Compiler (vs. konventionellem PHP Interpreter) lässt sich im Zusammenspiel mit nginx ein weiterer großer Schritt in Sachen Performance machen. Ein netter Nebeneffekt ist die Tatsache, dass sich auch starkfrequentierte Anwendungen auf vergleichsweise günstiger Hardware betreiben lassen.

Wie man solch einen Server zusammenbaut, gibt es im nachfolgendem Tutorial, kurz und bündig samt beispielhafter WordPress Instanz. Das ganze passiert bei DigitalOcean auf einer Ubuntu 14.04 LTS Maschine.

Vorbereitung

Nach dem Hochfahren einer neuen Maschine sorgt man erst einmal routinemäßig dafür, dass das System und Abhängigkeiten auf dem neusten Stand sind.

1$ apt-get update && apt-get upgrade
2$ apt-get dist-upgrade
3$ apt-get autoremove

Jetzt legt man einen neuen Benutzer an und setzten das Passwort um die späteren Aktionen nicht mit dem Root-Benutzer auszuführen zu müssen. Alternativ zum Passwort kann hier auch mit SSH Keys gearbeitet werden.

1$ adduser demo

Anschließend erteilen wir dem erstellten Benutzer sudo-Rechte.

1$ visudo
1# User privilege specification
2root ALL=(ALL:ALL) ALL
3demo ALL=(ALL:ALL) ALL

Aus Sicherheitsgründen ist es in diesem Zuge ratsam das Anmelden des Root-Benutzers zu verbieten und den SSH Port zu ändern. Finden und ersetzen.

1$ nano /etc/ssh/sshd_config
1Port 1234 PermitRootLogin no
1$ service ssh restart

Ein guter Zeitpunkt das Verzeichnis anzulegen, aus dem später die Webseite ausgeliefert werden soll und der Ordnerstruktur die korrekten Besitzrechte zu erteilen.

1$ mkdir -p /var/www/demo.local
2$ chown -R demo /var/www

Nun kann man sich abmelden und mit dem neuem Benutzer erneut via SSH einloggen.

1ssh -p 1234
2demo@domain.local

nginx

Die Installation von nginx folgt jetzt. Da HHVM Änderungen an der Konfiguration von nginx automatisch vornehmen kann, wird HHVM erst anschließend installiert. Es wird empfohlen den Mainline-Branch (heißt development, etwas verwirrend) zu nutzen…

In NGINX nomenclature, “stable” means that no new features are added (the feature set is stable). Only major bugfixes are committed to that version. In general, you should deploy the NGINX mainline branch at all times.

…dafür fügt man entsprechendes Repository dem Paketmanager hinzu.

1$ sudo apt-add-repository ppa:nginx/development
2$ sudo apt-get update
3$ sudo apt-get install nginx
4$ sudo service nginx start

HHVM

Kommen wir endlich zu HHVM. Im HVVM GitHub Repository finden sich zahlreiche Prebuilt Packages, auch für Ubuntu 14.04, so brauchen wir es nicht selbst kompilieren und sind ready to roll, nachdem die Paketquelle hinzugefügt wurde.

1$ wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | sudo apt-key add - echo deb http://dl.hhvm.com/ubuntu trusty main | sudo tee /etc/apt/sources.list.d/hhvm.list
2$ sudo apt-get update
3$ sudo apt-get install hhvm

Um HHVM mit nginx nutzen zu können muss die mitgelieferte FastCGI Installation ausgeführt werden, welche auch die angesprochenen Änderungen an nginx vornimmt und die Schnittstelle zu nginx darstellt.

1$ sudo /usr/share/hhvm/install_fastcgi.sh

Man schaltet Server zwar nicht so oft ab, aber um HHVM nicht manuell nach jedem Systemstart ausführen zu müssen, werfen wir HHVM in den Autostart von Ubuntu.

1$ sudo update-rc.d hhvm defaults

Datenbank: MariaDB

Als Datenbankserver kommt MariaDB zum Einsatz, für die korrekte Version ist es auch hier nötig die Repository manuell hinzuzufügen.

1$ sudo apt-get install software-properties-common
2$ sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
3$ sudo add-apt-repository 'deb http://ams2.mirrors.digitalocean.com/mariadb/repo/10.1/ubuntu trusty main'
4$ sudo apt-get update
5$ sudo apt-get install mariadb-server

Nachdem MariaDB installiert ist, wird noch für etwas mehr Sicherheit gesorgt. Beim Ausführen des nächsten Befehls werden wird man aufgefordert das MySQL Root Passwort zu ändern, welches zuvor gesetzt wurde, diese wird mit Nein beantwortet, die restlichen Abfragen werden mit Ja abgearbeitet.

1$ mysql_secure_installation

Fertig? Dann ist alles bereit eine neue Datenbank anzulegen, hierfür begibt man sich in die SQL Shell und tut genau dies. Vergebenes Passwort für den Root-Benutzer des Datenbankservers bereit halten.

1$ mysql -u root -p
1MariaDB [(none)] CREATE DATABASE demo; MariaDB [(none)] exit

WordPress & Konfiguration

Zu guter Letzt brauchen man natürlich noch WordPress an sich, die Installation ist relativ einfach und mit wenigen Anweisungen ausgeführt, ohne einen SFTP Client zu starten.

1$ cd /var/www/demo.local
2$ wget http://de.wordpress.org/latest-de_DE.tar.gz
3$ tar -xvzf latest-de_DE.tar.gz
4$ mv wordpress/* .
5$ rm -rf wordpress
6$ rm -rf latest-de_DE.tar.gz

Die WordPress Installation wird wie gewohnt im Browser fertiggestellt.

Nun müssen noch ein paar Anpassungen an der Konfiguration von nginx vornehmen, damit die Sachen so ausgeliefert werden, wie sie sollen.

1$ nano /etc/nginx/nginx.conf

Die folgenden Zeilen sind standardmäßig auskommentiert, die Option server_tokens versteckt bei Fehlermeldungen des Servers die verwendete nginx-Version (Sicherheit und so) und da wir die gzip Kompression unbedingt verwenden möchten, ändern wir dies ebenfalls. Sofern Grafiken im SVG Format eingebunden werden sollen, fügt man zusätzlich image/svg+xml den gzip_types hinzu.

1server_tokens off; gzip_vary on; gzip_proxied any; gzip_comp_level 6;
2gzip_buffers 16 8k;
3gzip_http_version 1.1;
4gzip_types … image/svg+xml;

Im letzten Schritt der Konfiguration von nginx geht es an den Server-Block, welcher dafür verantwortlich ist, HTTP Anfragen entgegenzunehmen und letztendlich die Antwort zur gesendeten Anfrage an den Client zu senden. nginx liest hierzu Konfigurationsdateien ein die dem Schema /etc/nginx/conf.d/*.conf und /etc/nginx/sites-enabled/* entsprechen, dies ist in der nginx.conf einstellbar. Der Einfachheit halber wird der bereits vorgegebenen Server-Block bearbeitet.

1$ nano /etc/nginx/sites-enabled/default

Auch hier finden sich wieder zahlreich auskommentierte Anweisungen, die nicht benötigen werden, der Übersicht halber kann man nicht verwendete Server-Blöcke löschen. Am Ende sollte die Konfiguration wie folgt aussehen.

1server {
2 listen 80;
3 server_name demo.local;
4 root /var/www/demo.local;
5
6 index index.php index.html;
7
8 include hhvm.conf;
9
10 location / {
11 try_files $uri $uri/ /index.php?$args;
12 }
13
14 location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff2|woff|ttf|svg|otf)$ {
15 expires 30d;
16 add_header Pragma public;
17 add_header Cache-Control "public";
18 }
19
20}

Static durch Caching

Bonuspunkte gibt es für das Caching von dynamischen Anfragen, insbesondere bei WordPress lässt sich hier die Response Time verringern, in dem bei bereits im Cache liegende Seiten direkt an den Client gesendet werden, statt die Anfrage erst über HHVM und Datenbank zu schicken.

Die Auswahl an Möglichkeiten für die Umsetzung sind mehr als zahlreich, neben dem FastCGI-Cache von nginx gibt es – natürlich – diverse WordPress Plugins.

Mit Cachify lässt sich relativ einfach das gewünschte Ziel erreichen, nach der Installation und Wahl der Caching-Methode ergänzt man den zuvor genannten Server-Block um entsprechende Anweisungen und ist damit eigentlich schon fertig.

Ich bin derzeit noch am Experimentieren welche Konfiguration die beste Performance bringt, doch dabei geht es um die letzten Millisekunden. Grundsätzlich kann ich als solide Basis das Cachify Plugin von Sergej empfehlen, welches im Gegensatz zu anderen Plugin-Lösungen ohne großen Overhead daherkommt und sich auf das Wichtigste konzentriert, dies kann man leider nicht von vielen Plugins behaupten. An dieser Stelle noch mal ein Dank an Sergej für die großartige Arbeit in der WordPress Community.

Firewall

Als abschließende Übung machen wir den Server noch etwas sicherer, in dem wir die bei Ubuntu mitgelieferte Firewall aktivieren.

1$ sudo ufw allow 1234
2$ sudo ufw allow 80
3$ sudo ufw allow 443
4$ sudo ufw enable

Der Server antwortet nun nur auf Anfragen auf korrespondierenden Ports: dem benutzerdefiniertem SSH Port (1234), HTTP (80) und HTTPS (443).

Fertig.