Das perfekte PHP (+ WordPress) Setup?!

In diesem Artikel mache ich einige Vorschläge, wie man ein richtig gutes PHP-Setup für WordPress, aber auch für alle anderen PHP-Anwendungen baut. Die Konfiguration soll folgende Kern-Features aufweisen:

  • Reines Nginx-Hosting mit PHP FPM hinter
  • Strikt voneinander getrennte PHP-Webspaces
  • SSH-Login pro Website, ebenfalls strikt voneinander getrennt
  • Sichere Dateirechte mit zwei Systemnutzern pro Webspace, so dass man sauber steuern kann, auf welche Dateien es nur Lese- und auf welche es Lese- und Schreibrechte gibt
  • Nginx FCGI Caching mit sowohl kontextabhängigem als auch komplettem Purging

Bei der Entwicklung meiner neuen PHP-Umgebung wollte ich diesmal nun ein Setup haben, was ein sauberes Jailing beinhaltet. Dabei stellte ich dann fest, dass es nicht besonders viele Anleitungen für diese Aufgabe gibt – was alleine schon daran liegt, dass die wenigsten Menschen sichere Dateirechte nutzen. Aber auch bei Jails wird es schnell dünn. Also beschreibe ich mein Setup mal – vielleicht hilft es dem einen oder anderen ja.

Zielgruppe sind Menschen, die sich auf der Shell schon so richtig wohlfühlen und aus ihrem Server gerne mehr rausholen wollen – vor allem mehr Sicherheit. Menschen ohne tiefgreifendere Technik-Interesse sollten dagegen zu einem Hoster ihres Vertrauens gehen, der macht das alles für sie.

Die verwendeten Dienste

Eine PHP-Anwendung braucht typischerweise:

  • den HTTPd, also den Service, welchen der Nutzer sieht. Wir nehmen hier aufgrund der Geschwindigkeit und aufgrund des hervorragenden integrierten Cachings Nginx
  • den PHPd, also den Service, der PHP Dateien ausführt. Wir nehmen hier PHP-FPM, da es uns alles mitbringt, was wir für Jails brauchen
  • den mySQLd, also den Service, der die Datenbank bereitstellt. Wir nehmen hier MariaDB.

Auf einem Ubuntu 18.04 wären das also folgende Pakete:

apt install nginx libnginx-mod-http-cache-purge php7.2-fpm php7.2-cli mariadb-server

Sehr schön ist im Übrigen, dass es seit Ubuntu 18.04 ein einzelnes Paket für das Nginx Cache Purging gibt. In Ubuntu 16.04 musste man noch nginx-extras als das Nginx mit nahezu allen Plugins installieren, um den Purger mit zu bekommen, was Geschwindigkeitsreduzierung und Angriffsfläche bedeutete.

Die PHP-Jail

Ziel der PHP Jail ist es, dass PHP Scripte innerhalb einer kontrollierten, minimal gehaltenen Umgebung bleiben und keine Möglichkeit haben, an Daten vom Hauptsystem oder von anderen Webspaces zu bekommen.

Um die PHP-Jail einzurichten, brauchen wir einen eigenen Systemnutzer:

adduser mywebsite-php

Zu diesem erstellen wir dann einen PHP-FPM-Pool in /etc/php/7.2/fpm/pool.d/mywebsite.conf

[mywebsite]

user = mywebsite-php
group = mywebsite-php

listen = /var/run/php-mywebsite.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6

chroot = /var/jail/mywebsite
chdir = /

php_value[session.save_path] = /sessions

Bis Zeile 15 ist das eine recht normale Konfiguration, eben ein Pool, welcher unter dem Systemnutzer mywebsite-php läuft. Zeile 17 sagt, wo die Jail aufgebaut werden soll, Zeile 18 sagt aus, dass der PHP-User auf / in der Jail eingesperrt werden soll.

Wichtig ist auch noch Zeile 20: der Session Store von PHP ist /var/lib/php/sessions/ , was außerhalb der Jail ist und damit in Zukunft unerreichbar ist. Also schafft man sich mit /sessions einen eigenen Session Store innerhalb der Jail.

Eine extrem wichtige Ergänzung ist auch noch an der /etc/php/7.2/fpm/php.ini vorzunehmen:

opcache.validate_permission=1
opcache.validate_root=1

Dies ist nicht Standard-Einstellung, woraufhin PHP den Object-Cache nur Pfad-basiert macht. Da die Pfade innerhalb der Jail aber nicht den Basisordner mit dabei haben (/var/jail/mywebsite/httpdocs/index.php ist innerhalb der Jail eben nur noch /httpdocs/index.php), kann man von einer Jail den Object Cache der anderen Jail lesen – und sogar überschreiben.

Das passiert auch ziemlich schnell aus Versehen, da ein WordPress-Dateilayout in jeder Jail eben ziemlich identisch aussieht, worauf vermeintlich Zufällig 301er auf andere Jails = andere Websites auf dem Server geschehen. Bringt kein Spaß und sollte man durch die beiden Zeilen oben unbedingt vermeiden.

Die SSH-Jail

Um SSH-Zugriff auf einen Space, aber nicht auf den gesamten Rest des Servers zu geben, muss auch das SSH Login gejailed werden. OpenSSH bringt dazu alles nötige mit.

Hierzu brauchen wir zunächst eine Gruppe, in der alle User für die SSH Jail hineinkommen. Außerdem brauchen wir natürlich den Nutzer, mit dem sich eingeloggt wird, welcher dann auch Teil der Jail-Gruppe wird:

adduser mywebsite --home /var/jail/mywebsite/home
addgroup ssh-jail
adduser mywebsite ssh-jail

Nun ergänzen wir die SSH Konfiguration in /etc/ssh/sshd_config:

Match Group ssh-jail
   X11Forwarding no
   AllowTcpForwarding no
   ChrootDirectory /var/jail/%u/
   PasswordAuthentication yes

In Zeile 4 befindet sich der Grund, warum wir weiter oben in der PHP-Konfiguration auf /var/jail/mywebsite zeigen: das ist der Systemnutzer, mit dem wir uns via SSH einloggen.

Zeile 5 ist noch eine weitere interessante Möglichkeit: meine Server haben aus Sicherheitsgründen grundlegend kein SSH-Passwort-Login aktiviert. Für die Jails (und nur für diese) können wir das aber erlauben, da dort viel weniger Schaden gemacht werden kann. SSH erlaubt dies glücklicherweise auch nur Gruppen-bezogen wie in unserem Beispiel.

Die Jail selbst

Sowohl SSH als auch PHP verweisen nun auf den Order /var/jail/mywebsite, also muss dort die Jail selbst aufgebaut werden. In der Jail brauchen wir alle Binaries und Libraries, die wir via SSH nutzen wollen, aber eben auch nicht mehr, um kein unnötiges Sicherheitsrisiko einzugehen. Außerdem brauchen wir ein paar Basics wie virtuelle Gerätedateien, um die Jail überhaupt funktionsfähig zu machen.

Virtuelle Gerätedateien

Zunächst die virtuellen Gerätedateien :

mkdir /var/jail/mywebsite/dev
mknod -m 666 /var/jail/mywebsite/dev/null c 1 3
mknod -m 666 /var/jail/mywebsite/dev/tty c 5 0
mknod -m 444 /var/jail/mywebsite/dev/random c 1 8
mknod -m 444 /var/jail/mywebsite/dev/urandom c 1 9
mknod -m 666 /var/jail/mywebsite/dev/zero c 1 5

Im Netz findet man zeitweise Tutorials, in denen stattdessen virtuelle Gerätedateien in die Jail hineinmountet. Ich kann allerdings nicht erkennen, welchen Sinn das ergeben soll, es spammt einem nur grundlos die Mount-Auflistung zu.

Konfigurationsdateien

Auch einige Konfigurationsdateien möchte man in der Jail haben, um diese zu nutzen. Schließlich möchte man php-cli, vim oder git mit den Linux-typischen Setups nutzen, und auch der Zertifikat-Root für SSL-Verbindungen aus der Jail heraus sollte zur Verfügung stehen. Eine (sicher unvollständige) Liste an Dateien und Ordnern, welche man in die Jail kopieren sollte:

/bin/which
/lib/terminfo/x
/etc/php/7.2/cli
/etc/php/7.2/mods-available
/usr/share/zoneinfo
/etc/hosts
/etc/resolv.conf
/etc/ssl/certs
/usr/share/ca-certificates
/lib/x86_64-linux-gnu/libnss_dns.so.2
/lib/x86_64-linux-gnu/libnss_files.so.2
/lib/x86_64-linux-gnu/libnss_compat.so.2
/usr/share/git-core/templates
/usr/lib/git-core/

Ebenfalls mit in der Liste sind einige Libraries, die es für die DNS-Auflösung innerhalb der Jail braucht (z.B. Zeile 10-12), sowie Anwendungen, welche keine dynamisch gelinkten Libraries hat (z.B. which).

Zu der DNS-Auflösung gibt es hin und wieder im Netz den Tip, doch einfach nscd zu installieren und in die Jail hineinzumounten. Das ist aus gleich zwei Gründen eine eher unglückliche Idee:

  • nscd ist ein Cache-System, welches nicht nur Hostnames, sondern auch die /etc/passwd cached. Das Problem ist nur, dass der Home-Ordner innerhalb der Jail ein ganz anderer ist als außerhalb. Wenn man also nscd nutzt, glaubt ein in der Jail genutzter SSH-Client, dass der Home-Ordner gleich /var/jail/mywebsite/home ist, und sucht dort nach seinem Profil wie z.B. den Private Key. Innerhalb der Jail ist der Home-Ordner aber nur /home, woraufhin der SSH-Client sein Profil nicht findet und nicht sauber funktioniert.
  • über den nscd kommt man nicht nur an Daten vom eigenen Jail-User, sondern auch an die Daten aller anderen User auf dem Server. Das ist schlicht ein Sicherheitsproblem.

Dynamisch gelinkte Anwendungen inkl. ihrer Libraries

Der Löwenanteil der zu kopierenden Dateien besteht jedoch aus verschiedenen Anwendungen und den dazugehörigen Shared Libraries. Hier eine (sicherlich ebenfalls unvollständige) Liste an Anwendungen:

/bin/bash
/bin/cat
/bin/cp
/bin/grep
/bin/less
/bin/ls
/bin/mkdir
/bin/mv
/bin/nano
/bin/rm
/bin/rmdir
/bin/sh
/bin/tar
/usr/bin/curl
/usr/bin/dircolors
/usr/bin/env
/usr/bin/find
/usr/bin/git
/usr/bin/git-receive-pack
/usr/bin/git-shell
/usr/bin/git-upload-archive
/usr/bin/git-upload-pack
/usr/bin/groups
/usr/bin/host
/usr/bin/id
/usr/bin/md5sum
/usr/bin/mysqldump
/usr/bin/patch
/usr/bin/php7.2
/usr/bin/rsync
/usr/bin/scp
/usr/bin/ssh
/usr/bin/tput
/usr/bin/unzip
/usr/bin/tail
/usr/bin/vim.basic
/usr/bin/wc
/usr/bin/wget
/usr/bin/whoami
/usr/lib/git-core/git
/usr/lib/git-core/git-credential-cache
/usr/lib/git-core/git-credential-cache--daemon
/usr/lib/git-core/git-credential-store
/usr/lib/git-core/git-daemon
/usr/lib/git-core/git-fast-import
/usr/lib/git-core/git-http-backend
/usr/lib/git-core/git-http-fetch
/usr/lib/git-core/git-http-push
/usr/lib/git-core/git-imap-send
/usr/lib/git-core/git-remote-http
/usr/lib/git-core/git-remote-testsvn
/usr/lib/git-core/git-shell
/usr/lib/git-core/git-sh-i18n--envsubst
/usr/lib/git-core/git-show-index
/usr/lib/git-core/git-upload-pack
/sbin/unix_chkpwd

Diese Dateien können wir nun nicht einfach so kopieren, da wir zunächst herausfinden müssen, auf welchen Shared Libraries diese basieren. Dies machen wir mit dem Befehl ldd:

ldd /bin/bash
        linux-vdso.so.1 (0x00007fff429fe000)
        libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007f14a0510000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f14a030c000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f149ff1b000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f14a0a54000)

Wir müssen also nicht nur die Binärdatei, sondern auch alle durch ldd aufgelisteten Shared Libraries in die Jail kopieren. Wie man sich vorstellen kann, ist das per Hand eine längliche Operation, und man sollte man diesen Vorgang automatisieren.

E-Mails in der Jail senden

Das Programm sendmail lässt sich durch die tiefe Integration im System nicht ohne weiteres in die Jail kopieren, so dass man in der Jail nicht via PHP-mail() E-Mails senden kann. Dazu gibt es nun zwei Lösungen:

  • Man nutzt auf jeder einzelnen Installation einen SMTP-Client, also im Fall von WordPress z.B. das Plugin wp-mail-smtp
  • Man compiliert und nutzt mini_sendmail

Mini-Sendmail ist ein kleines C++-Programm, was nichts anders macht als E-Mails via SMTP zu versenden. In Form der statischen Verlinkung funktionierte bei mir aber zumindest die Erkennung des System-Nutzers nicht, so dass ich mini_sendmail mit Shared Libraries compiliert habe. Nebeneffekt: es wird dann viel kleiner.

home und httpdocs in der Jail

Nahezu alle Dateien in der Jail gehören root und sind daher für den Nutzer nicht veränderbar. Lediglich zwei Orte gehören dem Nutzer: das Root-Directory vom Webserver und Home.

Der Home-Ordner ist alleine deshalb wichtig, weil der Nutzer dort alle Dateien und Konfigurationen ablegen kann, um seinen Space an sich anzupassen. Eigene VIM-Config? Eigener Private SSH Key? Ein Ort, der ohne SSH Zugriff nicht erreichbar ist? Das ist der Job von /home

Den HTTP-Root habe ich unter /httpdocs hingelegt. Das ist eigentlich ziemlich egal, sollte man aber bei der Nginx-Config mit beachten.

/etc/passwd in der Jail

Damit in der Jail die verwendeten System-Nutzer bekannt sind, wird eine eigene kleine /etc/passwd sowie eine eigene /etc/group in der Jail benötigt. Diese besteht aus den Nutzern root, www-data, mywebsite und mywebsite-php:

root:x:0:0:root:/home:/bin/bash
www-data:x:33:33:www-data:/home:/usr/sbin/nologin
mywebsite:x:5000:5000:"",,,:/home:/bin/bash
mywebsite-php:x:5001:5001:"",,,:/home:/bin/bash

UID und GID der beiden Website-bezogenen Nutzer müssen natürlich entsprechend der real verwendeten UID / GID angepasst werden.

Jail aktivieren: mysql Socket mounten

Hat man all die obrigen Punkte ausgeführt, so hat man bereits eine vollständige Jail. Ein SSH Login dürfte bereits möglich sein, und auch PHP sollte schon funktionieren. Lediglich die Datenbank-Verbindungen schlagen fehl, da PHP sich per default via Socket verbindet.

Dieses Problem kann man lösen, indem man den Socket einfach bei jedem Bootvorgang in die Jail hineinmountet:

mount --bind /var/run/mysqld /var/jail/mywebsite/var/run/mysqld
mount -o remount,ro,bind /var/jail/mywebsite/var/run/mysqld

Der zweite Befehl ist ebenfalls extrem wichtig, er verhindert, dass der Socket in der Jail plötzlich verschwindet, wenn der System-MySQL-Socket z.B. durch ein Update kurz ausfällt.

Der Webserver Nginx

Bislang ist nichts von außen abrufbar, weil der Webserver selbst fehlt. Hierfür passen wir zunächst die /etc/nginx/nginx.conf an:

http {
        [...]
        ##
        # File Cache #
        ##
        open_file_cache max=2000 inactive=20s;
        open_file_cache_valid 60s;
        open_file_cache_min_uses 5;
        #open_file_cache_errors off;

        ##
        # FastCGI Cache #
        ##

        fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=FCGICACHE:256m max_size=512m inactive=60m;
        fastcgi_cache_key "$scheme$request_method$host$request_uri";
        fastcgi_cache_use_stale error timeout invalid_header http_500;

        log_format cache '***$time_local '
                     '$upstream_cache_status '
                     'Cache-Control: $upstream_http_cache_control '
                     'Expires: $upstream_http_expires '
                     '"$request" ($status) '
                     '"$http_user_agent" ';
        [...]
}

Hier werden File- und FCGI-Caching schon einmal vorbereitet. Der wirklich spannende Teil geschieht aber nun im vHost /etc/nginx/sites-available/mywebsite:

fastcgi_cache_path /var/jail/mywebsite/cache/ use_temp_path=off levels=1:2 keys_zone=mywebsite:256m max_size=512m inactive=60m;

server {
    server_name www.mywebsite.de;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ssl on;
    ssl_certificate /etc/letsencrypt/live/www.mywebsite.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.mywebsite.de/privkey.pem;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 5m;
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_dhparam /etc/ssl/private/pfs-4096.pem;
    ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED";
    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
    resolver 8.8.8.8 8.8.4.4;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/www.mywebsite.de/fullchain.pem;
    access_log /var/jail/mywebsite/logs/front-access.log;
    error_log /var/jail/mywebsite/logs/front-error.log;

    root /var/jail/mywebsite/httpdocs;

    location ~ /\.git {
        deny all;
    }
    location ~ /\.htaccess {
        deny all;
    }

    set $skip_cache 0;

    if ($request_method = POST) {
            set $skip_cache 1;
    }

    if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {
        set $skip_cache 1;
    }

    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
        set $skip_cache 1;
    }

    location ~ /purge(/.*) {
        allow 127.0.0.1;
        allow ::1;
        allow [...];
        deny all;
        fastcgi_cache_purge mywebsite "$scheme$request_method$host$1";
    }

    location /purge-all {
        allow 127.0.0.1;
        allow ::1;
        allow [...];
        deny all;
        root /usr/lib/nginx/purger;
        fastcgi_pass unix:/var/run/php-default.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME "/usr/lib/nginx/purger/purge.php";
        fastcgi_param SCRIPT_NAME "purge.php";
        fastcgi_param PURGE_PATH "/var/jail/mywebsite/cache";
    }

    location / {
        try_files $uri $uri/ /index.php?q=$uri&$args;
    }

    if (!-e $request_filename) {
        rewrite /wp-admin$ $scheme://$host$uri/ permanent;
    }

    location = /robots.txt {
        rewrite ^ /index.php;
        access_log off;
        log_not_found off;
    }

    location ~ \.php$ {
        try_files $uri = 404;
        fastcgi_index index.php;
        fastcgi_pass unix:/var/run/php-mywebsite.sock;
        include fastcgi_params;

        fastcgi_param HTTPS on;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_param SCRIPT_FILENAME /httpdocs/$fastcgi_script_name;
        fastcgi_param SCRIPT_NAME $fastcgi_script_name;

        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;

        fastcgi_read_timeout 12000;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;
        fastcgi_cache mywebsite;
        fastcgi_cache_valid 60m;
        add_header X-Cache-Status $upstream_cache_status;

        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
    }
}

Einige Elemente aus der Nginx-Konfiguration:

  • Zeile 1: unser eigener Cache nur für diese Website innerhalb der Jail
  • Zeile 7ff: allerlei SSL-Einstellungen
  • Zeile 33ff: die Logik, ob der Cache übergangen wird oder nicht.
  • Zeile 47ff: die URLs, mit dessen Aufruf der Cach einzelner URLs geleert werden kann. Dies sollte nur von Anfragen des Servers selbst = dem WordPress aus funktionieren, so dass alle Anfragen von außen abgelehnt werden. In die [..] müssen also alle IPs des Servers eingetragen werden.
  • Zeile 55ff: die URL, mit der der gesamte Cache geleert wird. Dies läuft über ein kleines Extra-PHP-File in /usr/lib/nginx/purger/purge.php, welches unter dem Systemnutzer www-data = dem Nginx-Systemnutzer ausgeführt wird und daher auch die Berechtigung hat, alle Cache-Dateien zu löschen. Das Plugin Nginx-Helper versucht dagegen, direkt die Cache-Dateien zu löschen, was nicht funktioniert, da PHP als der User mywebsite ausgeführt wird, die Cache-Dateien aber dem Nginx-User www-data gehören
  • Zeile 68ff: schöne URLs
  • Zeile 82: die Weiterleitung aller PHP-Requests auf den Socket. Mit dabei ist die Abfrage, ob eine URL aus dem Cache geholt werden soll, was auch in den Header geschrieben wird (großartig zur Fehlerbehebung!)

Das Script in /usr/lib/nginx/purger/purge.php ist ziemlich simpel: da der HTTP-Header PURGE_PATH ja via nginx vHost File hardgecoded ist, braucht es keine Validierung:

<?php
function unlinkRecursive($dir) {
    if ( ! is_dir( $dir ) ) {
        return;
    }

    if ( ! $dh = opendir( $dir ) ) {
        return;
    }

    while ( false !== ($obj = readdir( $dh )) ) {
        if ( $obj == '.' || $obj == '..' ) {
            continue;
        }
        if (is_dir($dir . '/' . $obj)) {
            unlinkRecursive( $dir . '/' . $obj );
            rmdir($dir . '/' . $obj);
        }
        else {
            unlink( $dir . '/' . $obj );
        }
    }
    closedir( $dh );
}

if (array_key_exists('PURGE_PATH', $_SERVER)) {
    unlinkRecursive($_SERVER['PURGE_PATH']);
}

Einsatz von WordPress

Die Konfiguration ist eine universelle PHP-Konfiguration. Da ich aber diverse WordPress betreibe, gehe ich darauf noch einmal speziell ein.

Durch die beiden Systemnutzer ist die Konfiguration zu 100 % für die von mir beschriebenen sicheren Dateirechte mit zwei Systemnutzern einsetzbar. Das Login erfolgt in diesem Fall dann via SSH. Um stattdessen das Standard-FTP-Abfrage-System beim Update zu nutzen, kann man natürlich auch noch zusätzlich unter dem SSH-User einen FTPd einrichten.

Als Plugin, welche URL wann gepurged werden muss, eignet sich der Nginx Helper. Lediglich der „purge all“ Button funktioniert aus dem oben beschriebenen Rechte-Problem nicht. Diesen kann man aber leicht mit einer neuen Funktion belegen:

<?php
add_action('init', array($this, 'init'));
add_action('rest_api_init', array($this, 'rest_api_init'));
add_action('activated_plugin', array($this, 'update_server'));
add_action('deactivated_plugin', array($this, 'update_server'));
add_action('after_switch_theme', array($this, 'update_server'));
add_action('admin_bar_menu', function ($wp_admin_bar) {
    if (is_admin()) {
$wp_admin_bar->remove_node('nginx-helper-purge-all');
    }
}, 9999);
add_action('admin_bar_menu', function ($wp_admin_bar) {
    if ( ! current_user_can( 'manage_options' ) ) {
return;
    }
    if ( ! is_admin() )
return;

    $purge_url  = add_query_arg(
array(
    'dashboard-client-action' => 'purge'
)
    );

    $nonced_url = wp_nonce_url( $purge_url, 'dashboard-client-action' );

    $wp_admin_bar->add_menu(
array(
    'id'    => 'dashboard-client-action',
    'title' => __( 'Purge Cache', 'nginx-helper' ),
    'href'  => $nonced_url,
    'meta'  => array( 'title' => __( 'Purge Cache', 'nginx-helper' ) ),
)
    );
}, 200);
add_action('admin_bar_init', function() {
    $action = filter_input( INPUT_GET, 'dashboard-client-action', FILTER_SANITIZE_STRING );
    if ( empty( $action ) ) {
return;
    }
    if ($action !== 'purge') {
return;
    }
    check_admin_referer( 'dashboard-client-action' );
    @file_get_contents(get_site_url(null, '/purge-all'));
    $redirect_url = add_query_arg(array(
'nginx_helper_action' => 'done',
'nginx_helper_urls' => 'all'
    ), remove_query_arg('dashboard-client-action'));
    wp_redirect( esc_url_raw( $redirect_url ) );
});

Zusammenfassung

Die Anleitung beschreibt ein Setup, welches Sicherheit und Geschwindigkeit optimal zusammenbringt. Natürlich ist das Setup nichts für Anfänger, und über kurz oder lang wird man das in seine Scripte einbauen, da ein manuelles Setup extrem aufwändig wäre.

Trotzdem hoffe ich, dass es dem einen oder anderen Menschen weiter hilft und Gedankenanstöße bringt. Und vielleicht habe ich ja auch etwas übersehen, unschön gemacht oder sonst wie versemmelt? Schreibt es mir gerne!

7 Antworten zu “Das perfekte PHP (+ WordPress) Setup?!”

  1. Sehr charmante Anleitung, viele Punkte übernehme ich exakt so. Den SSH-Teil habe ich bei mir sogar komplett ignoriert, danke für die Inspiration!

    Gibt es einen Grund, warum du bei fastcgi_cache_path die Verwendung des temporären Ordners nicht explizit deaktivierst? Außerdem hast du innerhalb von http einen FastCGI-Cache für die Zone FCGICACHE angelegt – soll das als Fallback fungieren? Ich auch nicht nur explizit den Zugriff auf .htaccess verhindern, sondern generell alles, was mit „Punkt“ beginnt. Es gibt von kthx.at ein ziemlich praktisches Script, um die Jails mit allen Mountpunkten per Knopfdruck anzulegen, ich hab das noch etwas angepasst, siehe hier: https://www.nickyreinert.de/mehrere-virtuelle-server-mit-nginx-und-php-fpm-fur-wordpress-teil-1-3/

    • Danke 🙂

      Zum temporären Ordner: well, das gabs noch nicht, als ich damals damit angefangen habe. Hast recht, das könnte man deaktivieren.

      Die Zusatz-zone hat traditionelle Gründe und ist eine Art Fallback / Ding, was Systemanwendungen brauchen. Wirklich nötig ist die nicht, da hast du schon recht.

      Ich hab meins aber natürlich auch gescripted. Ist ein kleiner Python-basierter vHost-Manager, der dann gleich auch alles andere anlegt, also z.B. mein mu-plugin in WordPress kopiert, Flask in supervisord einrichtet, … etc – was man halt so braucht. Und schön nur via CLI bedienbar, so dass die Angriffsfläche denkbar gering ist.

      PS: du solltest mal dein Flash of Unstyled Content in deinem Blog wegmachen. CSS ganz aus dem Header wegschieben sieht nicht sooooo hübsch aus. 🙂 Aber ansonsten schöne Tutorial-Variante. Hätts die vor nem Jahr gegeben, hätt ich meines vielleicht nicht geschrieben.

      • Vor ein paar Jahren habe ich mir ja selber noch mit Apache und php-mod einen abgebrochen. 😉
        Danke für den Hinweis, wenn ich mal Zeit habe, fixe ich das.

  2. O.O Echt heftig, wie viel Mehrwert du in kostenlos verfügbare Beiträge steckst! Da sind ja Bücher zu dem Thema weniger detail- und aufschlussreich ^^‘ Kaum ein Blogger hat Lust auf die technischen Hintergründe, obwohl sie so wichtig sind. Vielen Dank für den hervorragenden Ratgeber!

  3. Hallo,
    wie aktiviert man denn „fastcgi_cache_purge“?
    Wenn ich dieses aktiviere, erscheint bei mir eine Fehlermeldung, die besagt, das etwas nicht gefunden werden kann. Ich nutze Nginx auf einem FreeBSD Jail. Leider bringt mich die Pkg Suche auch nicht weiter.
    Danke 🙂

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.