Unterschiede zwischen den Revisionen 14 und 15
Revision 14 vom 2021-01-29 11:55:59
Größe: 22512
Autor: anonym
Kommentar: Varnish-Doku
Revision 15 vom 2021-01-31 02:06:34
Größe: 23222
Autor: anonym
Kommentar: skript aktualisiert
Gelöschter Text ist auf diese Art markiert. Hinzugefügter Text ist auf diese Art markiert.
Zeile 133: Zeile 133:
set -eu
Zeile 134: Zeile 136:
CHROOT_DIR="$BASE_DIR/*"
COPY_SOURCES="\
        /etc/hosts \
    
/etc/resolv.conf \
        /etc/ssl/certs \
    
/lib/x86_64-linux-gnu/libnss_dns.so.2 \
    
/usr/lib/ssl/openssl.cnf \
    
/usr/share/zoneinfo"
CHROOT_DIRS=$(find "$BASE_DIR" -mindepth 1 -maxdepth 1 -type d)
COPY_SOURCES="
    /etc/hosts
/etc/resolv.conf
    /etc/ssl/certs
/lib/x86_64-linux-gnu/libnss_dns.so.2
/usr/lib/ssl/openssl.cnf
/usr/share/zoneinfo"
Zeile 143: Zeile 145:
MKNOD_DIRS="\
        dev \
        logs \
        sessions \
        tmp"
MKNOD_DIRS="
    dev
    logs
    sessions
    tmp"

create_mounts() {
    local chroot_dir="$1"
    shift
    local mount_dir
    for mount_dir in "$@"; do
        if [ -d "$mount_dir" ]; then
            # $mount_dir ist ein Pfad zu einem Verzeichnis
            mkdir -p "$chroot_dir/$mount_dir"
            mount --bind -o ro "$mount_dir" "$chroot_dir/$mount_dir"
        else
            # $mount_dir ist ein Pfad zu einer Datei
            mkdir -p "$chroot_dir/$(dirname "$mount_dir")"
            touch "$chroot_dir/$mount_dir"
            mount --bind -o ro "$mount_dir" "$chroot_dir/$mount_dir"
        fi
    done
}

remove_chroot() {
    local chroot_dir="$1"
    shift
    if [ -d "$chroot_dir/tmp" ]; then
        echo "Loese $chroot_dir auf..."
        for f in "$@"; do
            umount "$chroot_dir/$f" || continue
            if [ -d "$chroot_dir/$f" ]; then
                # Leeren Ordner loeschen
                rmdir "$chroot_dir/$f"
            elif [ -f "$chroot_dir/$f" ]; then
                # Datei loeschen
                rm "${chroot_dir}${f}"
            fi
        done
    fi
}
Zeile 153: Zeile 191:
        ACTION=-help         ACTION=help
Zeile 160: Zeile 198:
        CHROOT_SITE_DIR="$BASE_DIR/$DOMAIN"
Zeile 161: Zeile 201:
             mkdir -p "$BASE_DIR"/"$DOMAIN"/"$i"
                done
            mkdir -p "$CHROOT_SITE_DIR/$i"
        done
Zeile 164: Zeile 204:
     cp -urpL --parents "$i" "$BASE_DIR"/"$DOMAIN"             cp -urpL --parents "$i" "$CHROOT_SITE_DIR"
Zeile 166: Zeile 206:
        mknod -m 666 "$BASE_DIR"/"$DOMAIN"/dev/null c 1 3
        mknod -m 444 "$BASE_DIR"/"$DOMAIN"/dev/random c 1 8
        mknod -m 444 "$BASE_DIR"/"$DOMAIN"/dev/urandom c 1 9
        mknod -m 666 "$BASE_DIR"/"$DOMAIN"/dev/zero c 1 5
        mknod -m 666 "$CHROOT_SITE_DIR/dev/null" c 1 3
        mknod -m 444 "$CHROOT_SITE_DIR/dev/random" c 1 8
        mknod -m 444 "$CHROOT_SITE_DIR/dev/urandom" c 1 9
        mknod -m 666 "$CHROOT_SITE_DIR/dev/zero" c 1 5
        mkdir -p "$CHROOT_SITE_DIR/tmp"
        create_mounts "$CHROOT_SITE_DIR" "$MOUNTS"
Zeile 171: Zeile 213:
    restart|force-reload|start)
        $0 stop 2>/dev/null

        for chrootdir in $CHROOT_DIR; do
            if [ -d "${chrootdir}/tmp" ]; then
                chmod 777 "${chrootdir}/tmp"
                chmod +t "${chrootdir}/tmp"

                echo "Setting up ${chrootdir}..."
                for f in $MOUNTS; do
                    if [ -d "$f" ]; then
                        mkdir -p "${chrootdir}${f}"
                        mount --bind -o ro "${f}" "${chrootdir}${f}"
                    else
                        mkdir -p "${chrootdir}$(dirname "${f}")"
                        touch "${chrootdir}${f}"
                        mount --bind -o ro "${f}" "${chrootdir}${f}"
                    fi
                done
                cp -urpL --parents $COPY_SOURCES "${chrootdir}"

    start)
        for chroot_dir in $CHROOT_DIRS; do
            # Nur in Ordnern mit eigenem /tmp Verzeichnis als Markierung einen Chroot aufsetzen
            if [ -d "$chroot_dir/tmp" ]; then
                # Berechtigungen von /tmp korrigieren
                chmod 777 "$chroot_dir/tmp"
                chmod +t "$chroot_dir/tmp"

                echo "Setting up ${chroot_dir} ..."
                create_mounts "$chroot_dir" $MOUNTS
         for i in $COPY_SOURCES; do
             cp -urpL --parents "$i" "$chroot_dir"
         done
Zeile 196: Zeile 232:
        for chrootdir in $CHROOT_DIR; do
            if [ -d "${chrootdir}/tmp" ]; then
                echo "Destructing ${chrootdir}..."
                for f in $MOUNTS; do
                    umount "${chrootdir}${f}"
                    if [ -d "${chrootdir}${f}" ] && [ ! $(ls -A "${chrootdir}${f}") ]; then
                        rmdir "${chrootdir}${f}"
                    elif [ -f "${chrootdir}${f}" ]; then
                        rm "${chrootdir}${f}"
                    fi
                done
            fi
        for chroot_dir in $CHROOT_DIRS; do
            remove_chroot "$chroot_dir" "$MOUNTS"
Zeile 211: Zeile 237:
    restart)
 "$0" stop 2>/dev/null
 "$0" start
    ;;

    remove)
        DOMAIN="$1"
        CHROOT_SITE_DIR="$BASE_DIR/$DOMAIN"
        remove_chroot "$CHROOT_SITE_DIR" "$MOUNTS"
    ;;
Zeile 212: Zeile 249:
        echo "Usage: $N {start|stop|restart|force-reload|init}" >&2         echo "Usage:  $(basename "$0") { start | stop | restart | init | remove }"
       echo

Einige Technik-Kollektive bieten Wordpress-Hosting auf Basis von Wordpress-Multisite an. Beispiele dafür sind die tollen Angebote von blackblogs.org und noblogs.org. In der Regel ist die Funktionalität von Wordpress beschränkt, so dass sich nur eine Auswahl von Themes und Plugins installieren lassen. Ein anderer Ansatz ist der Betrieb einzener Wordpress-Seiten in einem abgesichertem Hosting.

Diese Seite beschreibt die Einrichtung eines solchen Hostings. Entgegen anderen Anleitungen verwenden wir Apache, um mittels Macros die Seiteneinrichtung zu vereinfachen. Zur Absicherung verwenden wir ein chroot, basierend auf PHP-FPM.

Einführung

Dieser Anleitung basiert auf anderen sehr gut Howtos. Für ein grundlegendes Verständnis solltest du diese auch lesen:

Variablen, Versionen und Pfade

Zur Veranschaulichung werden die nachfolgenden Variablen und Werte in der Anleitung genutzt:

Beispiel-Domain

example.org

Benutzerkonto

katja

PHP-Version

7.3

Quota-Partition (ext4)

/data

Skript-Verzeichnis

/usr/local/bin

Wordpress-Speicherort

/data/wordpress

IP des Reverse-Proxys

192.168.0.1

Bitte beachte, dass sich einzelnen Pfadangaben teilweise in mehreren Konfigurationsdateien benutzt werden. Bei Änderungen eines Pfades musst du also alle Vorkommen ändern.

Benutzerkonto zur PHP-Ausführung

  • Ein Systemnutzer mit eingeschränkten Rechten ist für die Auführung des PHP-Prozesses zuständig. Die Anmeldung per Passwort1 und das Anlegen eines Home-Verzeichnissen werden deaktiviert:

    adduser --disabled-login --disabled-password --no-create-home --gecos katja
  • Im späteren Verlauf wird das Wordpress-Verzeichnis diesem neuen Benutzerkonto übergeben.

Quotas einrichten

  • Disk-Quotas sorgen dafür, dass eine einzelne Wordpress-Instanz nicht den gesamten Speicherplatz auf einer Partition aufbrauchen kann. Läuft Wordpress in einem KVM-Gast muss ein Kernel installiert werden2:

    apt install linux-image-amd64 quota
  • Anschließend werden der ext4-Partition in /etc/fstab die notwendigen Parameter übergeben:

    /dev/vdb /data auto noatime,nodiratime,usrjquota=aquota.user,jqfmt=vfsv1  0 0
  • Die Partition mit den neuen Werten remounten:

    mount -vo remount /data
  • Den Quota-Index erstellen und Quotas aktivieren3:

    quotacheck -cum
    
    quotaon -v /data
  • Nun können für den neuen Nutzer die Quota-Regeln erstellt werden:

    edquota katja
  • Weitere Hinweise dazu im Arch-Wiki

Apache: Seitenkonfiguration mit Macros

  • Durch Apache-Macros ist es möglich, nur eine Seitenkonfiguration für alle Wordpress-Instanzen zu erstellen.
  • Die Seitenkonfiguration /etc/apache/sites-available/wordpress.conf basiert auf dem später näher erläuterten Verzeichnisaufbau. Die Konfiguration verhindert durch Rewriting die Installation von eigenen Themes und Modulen über das Dashboard4. Zudem wird eine optionale htaccess-Datei eingebunden. Damit lassen sich einzelnen Wordpress-Seiten per htacess zusätzlich schützen:

    <Macro WPSite $domain $pool>
    <VirtualHost *>
            ServerName $domain
            SetEnv HTTPS on
            DocumentRoot /data/wordpress/$domain/htdocs
            ErrorLog /var/log/apache2/$domain.error
            IncludeOptional /data/wordpress/_htaccess.d/$domain.conf
            Include /etc/apache2/conf-available/wordpress-cache.conf
    
            # Installation von eigenen Themes und Plugins verhindern        
            <IfModule mod_rewrite.c>
                    RewriteEngine On
                    RewriteCond %{REQUEST_URI} ^/wp-admin/update.php
                    RewriteCond %{QUERY_STRING} action=upload-(plugin|theme)
                    RewriteRule (.*) /wp-admin/plugin-install.php [QSD,R=302,L]
            </IfModule>
            <Directory /data/wordpress/$domain/htdocs>
                    Require all granted
                    Options SymLinksIfOwnerMatch MultiViews IncludesNoExec
                    AllowOverride AuthConfig FileInfo Indexes Limit Options
    
                    # https://wordpress.org/support/article/hardening-wordpress/#securing-wp-includes
                    <IfModule mod_rewrite.c>
                            RewriteEngine On
                            RewriteBase /
                            RewriteRule ^wp-admin/includes/ - [F,L]
                            RewriteRule !^wp-includes/ - [S=3]
                            RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
                            RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
                            RewriteRule ^wp-includes/theme-compat/ - [F,L]
                    </IfModule>
            </Directory>
            <IfModule proxy_fcgi_module>
                    <FilesMatch ".+\.ph(ar|p|tml)$">
                            SetHandler "proxy:unix:/run/php/php-fpm-$pool.sock|fcgi://$domain"
                    </FilesMatch>
                    <FilesMatch ".+\.phps$">
                            Require all denied
                    </FilesMatch>
                    <FilesMatch "^\.ph(ar|p|ps|tml)$">
                            Require all denied
                    </FilesMatch>
                    <Files xmlrpc.php>
                            Require all denied
                    </Files>
                    <Files wp-config.php>
                            Require all denied
                    </Files>
            </IfModule>
    </VirtualHost>
    </Macro>
    # Includieren der Variablen
    Include /etc/apache2/conf-available/wordpress-sites.conf
    
    UndefMacro WPSite
  • Alle Variablen werden in /etc/apache2/conf-available/wordpress-sites.conf definiert. Die pool-Variable bezeichnet den Namen des PHP-FPM-Pools:

    # Muster: Use WPSite $domain $pool
    
    Use WPSite example.org katja
  • Anschließend werden alle benötigten Module und die Seite aktiviert:

    a2enmod macros
    
    a2ensite wordpress.conf
    
    systecmtl reload apache2

Chroot-Verzeichnis-Struktur

  • Oberste Ebene eines jeden chroots ist das jeweilige Domain-Verzeichnis. Für die Beispieldomain ist es data/wordpress/example.org.

  • Das tmp-Verzeichnis hat eine doppelte Funktion: Es markiert ein chroot-Verzeichnis. Das ist für das später vorgestellte chroot-setup-Skript wichtig. Zudem werden dort temporäre Daten gespeichert.

  • Wordpress selbst liegt unter htdocs. Daraus ergibt sich folgende Struktur:

    ├── data/wordpress/example.org
    │   ├── htdocs/
    │   │   └── index.php
    │   ├── logs/
    |   ├── sessions/
    |   └── tmp/
  • htdocs gehört dem oben erstellten Benutzerkonto katja:katja. Alle anderen Verzeichnisse root:root.

Chroot-Setup-Skript

  • Das Skript /usr/local/bin/php-fpm-chroot-setup nimmt die Ersteinrichtung des chroots und der Verzeichnisstruktur vor. Es kopiert zudem die notwendigen Daten in das chroot.

  • Im Wesentlichen handelt es sich um Daten für die Namensauflösung, das Überprüfen von Zertifikaten und den mysql-Socket:

    set -eu
    
    BASE_DIR="/data/wordpress"
    CHROOT_DIRS=$(find "$BASE_DIR" -mindepth 1 -maxdepth 1 -type d)
    COPY_SOURCES="
        /etc/hosts
        /etc/resolv.conf
        /etc/ssl/certs
        /lib/x86_64-linux-gnu/libnss_dns.so.2
        /usr/lib/ssl/openssl.cnf
        /usr/share/zoneinfo"
    MOUNTS="/var/run/mysqld"
    MKNOD_DIRS="
        dev
        logs
        sessions
        tmp"
    
    create_mounts() {
        local chroot_dir="$1"
        shift
        local mount_dir
        for mount_dir in "$@"; do
            if [ -d "$mount_dir" ]; then
                # $mount_dir ist ein Pfad zu einem Verzeichnis
                mkdir -p "$chroot_dir/$mount_dir"
                mount --bind -o ro "$mount_dir" "$chroot_dir/$mount_dir"
            else
                # $mount_dir ist ein Pfad zu einer Datei
                mkdir -p "$chroot_dir/$(dirname "$mount_dir")"
                touch "$chroot_dir/$mount_dir"
                mount --bind -o ro "$mount_dir" "$chroot_dir/$mount_dir"
            fi
        done
    }
    
    remove_chroot() {
        local chroot_dir="$1"
        shift
        if [ -d "$chroot_dir/tmp" ]; then
            echo "Loese $chroot_dir auf..."
            for f in "$@"; do
                umount "$chroot_dir/$f" || continue
                if [ -d "$chroot_dir/$f" ]; then
                    # Leeren Ordner loeschen
                    rmdir "$chroot_dir/$f"
                elif [ -f "$chroot_dir/$f" ]; then
                    # Datei loeschen
                    rm "${chroot_dir}${f}"
                fi
            done
        fi
    }
    
    if [ $# -ge 1 ]; then
            ACTION=$1
            shift
    else
            ACTION=help
    fi
    
    case "$ACTION" in
        init)
            [ $# -ne 1 ] && echo "action 'init' requires one parameter: domain - e.g. 'example.org'" && exit 1
            DOMAIN="$1"
            CHROOT_SITE_DIR="$BASE_DIR/$DOMAIN"
    
            for i in $MKNOD_DIRS; do
                mkdir -p "$CHROOT_SITE_DIR/$i"
            done
            for i in $COPY_SOURCES; do
                cp -urpL --parents "$i" "$CHROOT_SITE_DIR"
            done
            mknod -m 666 "$CHROOT_SITE_DIR/dev/null" c 1 3
            mknod -m 444 "$CHROOT_SITE_DIR/dev/random" c 1 8
            mknod -m 444 "$CHROOT_SITE_DIR/dev/urandom" c 1 9
            mknod -m 666 "$CHROOT_SITE_DIR/dev/zero" c 1 5
            mkdir -p "$CHROOT_SITE_DIR/tmp"
            create_mounts "$CHROOT_SITE_DIR" "$MOUNTS"
            ;;
    
        start)
            for chroot_dir in $CHROOT_DIRS; do
                # Nur in Ordnern mit eigenem /tmp Verzeichnis als Markierung einen Chroot aufsetzen
                if [ -d "$chroot_dir/tmp" ]; then
                    # Berechtigungen von /tmp korrigieren
                    chmod 777 "$chroot_dir/tmp"
                    chmod +t "$chroot_dir/tmp"
    
                    echo "Setting up ${chroot_dir} ..."
                    create_mounts "$chroot_dir" $MOUNTS
                    for i in $COPY_SOURCES; do
                        cp -urpL --parents "$i" "$chroot_dir"
                    done
                fi
            done
        ;;
    
        stop)
            for chroot_dir in $CHROOT_DIRS; do
                remove_chroot "$chroot_dir" "$MOUNTS"
            done
        ;;
    
        restart)
            "$0" stop 2>/dev/null
            "$0" start
        ;;
    
        remove)
            DOMAIN="$1"
            CHROOT_SITE_DIR="$BASE_DIR/$DOMAIN"
            remove_chroot "$CHROOT_SITE_DIR" "$MOUNTS"
        ;;
    
        help|--help)
            echo "Usage:  $(basename "$0")  { start | stop | restart | init | remove }"
            echo
            exit 1
        ;;
        *)
            "$0" help >&2
            exit 1
        ;;
    esac
  • Nähere Erläuterungen zu dem Skript findest du hier.

  • Zudem ist hier erklärt, weshalb die Verwendung von NSCD zur Namensauflösung keine gute Idee ist. Als Alternative kopiert das Skript /etc/resolv.conf und libnss_dns.so.2.

Initieren beim Start

  • Einige Daten (im Beispiel der mysql-Socket) werden durch das Skript per bind-mount in das chroot gemountet. Diese müssen bei jedem Start wieder hergestellt werden. Dies übernimmt der systemd-Service /etc/systemd/system/php-fpm-chroot.service:

    [Unit]
    Description=Set up PHP-FPM chroot mounts
    After=network.target php7.3-fpm.service mysqld.service
    
    [Service]
    Type=forking
    ExecStart=/usr/local/bin/php-fpm-chroot-setup start
    ExecStop=/usr/local/bin/php-fpm-chroot-setup stop
    RemainAfterExit=yes
    
    [Install]
    WantedBy=multi-user.target
  • Den Service aktivieren und den Deamon neu laden:

    systemctl enable php-fpm-chroot.service
    
    systemctl daemon-reload

Konfiguration des PHP-FPM-Pools mit Chroot

  • Die Konfigurarion des PHP-FPM-Pools basiert auf den Pfadangaben, die in der Apachekonfiguration und in der Verzeichnisstruktur verwenden werden.

  • Damit das PHP-FPM-Socket gefunden wird:

    php_admin_value[doc_root] = /htdocs
    php_admin_value[cgi.fix_pathinfo] = 0
  • Sicherheitseinstellugnen für opcache:

    php_admin_value[opcache.validate_permission] = 1
    php_admin_value[opcache.validate_root] = 1
  • Angabe des Cert-Pfades, damit die Validierung klappt:

    php_admin_value[openssl.capath] = /etc/ssl/certs
  • Alles in allem die vollständige FPM-Konfiguration:

    [katja]
    prefix = /data/wordpress/example.org
    user = $pool
    group = www-data
    listen = /run/php/php-fpm-$pool.sock
    listen.owner =  $pool
    listen.group = www-data
    listen.mode = 0660
    listen.allowed_clients = 127.0.0.1
    pm = ondemand
    pm.max_children = 5
    pm.start_servers = 2
    pm.process_idle_timeout = 10s;
    pm.max_requests = 100
    chroot = $prefix
    chdir = /
    security.limit_extensions = .php .php3 .php4 .php5
    php_admin_value[doc_root] = /htdocs
    php_admin_value[cgi.fix_pathinfo] = 0
    php_admin_value[opcache.validate_permission] = 1
    php_admin_value[opcache.validate_root] = 1
    php_admin_value[session.save_path] = /sessions
    php_admin_value[openssl.capath] = /etc/ssl/certs
    php_admin_value[disable_functions] = mail,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_exec,passthru,system,proc_get_status,proc_close,proc_nice,proc_terminate,proc_open,curl_ini,parse_ini_file,show_source,dl,symlink,system_exec,exec,shell_exec,phpinfo

Verwaltung mit wpcli

  • Wpcli ist ein Werkzeug für die Kommandozeile, um Wordpress-Seiten zu verwalten.

  • Die Installation geht recht einfach.

  • Zu beachten ist, dass wpcli stehts als der angelegte Wordpress-Nutzer ausgeführt wird (und nicht als root).

Seitengeschwindigkeit optimieren

Mittels Caching sollen Seitenaufrufe beschleunigt werden.

Webserver-Caching

  • Wir weisen den Browser an, Mediendateien, css und javascript zu cachen. Dafür wirf in /etc/apache2/conf-available/wordpress-cache.conf ein globaler Cache definiert:

    <IfModule mod_expires.c>
      ExpiresActive on
    
      # whitelist expires rules
      ExpiresDefault "access 1 month"
    
      # Favicon (cannot be renamed)
      ExpiresByType image/x-icon "access plus 1 week"
    
      # Media: images, video, audio
      ExpiresByType image/gif "access plus 1 month"
      ExpiresByType image/png "access plus 1 month"
      ExpiresByType image/jpg "access plus 1 month"
      ExpiresByType image/jpeg "access plus 1 month"
      ExpiresByType video/ogg "access plus 1 month"
      ExpiresByType audio/ogg "access plus 1 month"
      ExpiresByType video/mp4 "access plus 1 month"
      ExpiresByType video/webm "access plus 1 month"
    
      # Webfonts
      ExpiresByType application/x-font-ttf "access plus 1 year"
      ExpiresByType font/opentype "access plus 1 year"
      ExpiresByType application/x-font-woff "access plus 1 year"
      ExpiresByType image/svg+xml "access plus 1 year"
    
      # CSS and JavaScript
      ExpiresByType text/css "access plus 1 month"
      ExpiresByType text/javascript "access plus 1 month"
      ExpiresByType application/javascript "access plus 1 month"
    
      <IfModule mod_headers.c>
        Header append Cache-Control "public"
      </IfModule>
    </IfModule>
  • Nun noch das entsprechende Modul neu laden und den Webserver neu laden:

    a2enmod macros expires
    
    systemctl reload apache2

Redis-Caching

  • Redis ist ein persistenter Objekt-Cache zur Zwischenspeicherung von Daten
  • In der wp-config.php muss der Cache_Salt angegeben werden. Das ist wichtig, damit bei der Verwendung von nur einer Redis-Datenbank mit mehreren Wordpress-Seiten die Inhalte des Caches korrekt zugeordnet werden können:

    wp config set WP_CACHE_KEY_SALT example.org_
  • Die Nutzung setzt ein Plugin voraus, bspw. WP-Redis:

    wp plugin install wp-redis
    
    wp plugin acticate wp-redis
  • Anschließend lässt sich die Redis-Nutzung per wpcli aktivieren:

    wp redis enable
    
    wp redis info
  • Wird Redis per TCP-Verbindung angesprochen, muss nichts weiter konfiguriert werden. Soll die Verbindung über das Socket hergestellt werden, müssen die Serverangaben in wp-config.php ergänzt werden:

    $redis_server = array(
      'host'     => '/run/redis/redis-server.sock',
      'port'     => 0,
    );
  • Zudem muss der Wordpress-Nutzer zur Redis-Gruppe hinzugefügt werden:

    usermod -a -G redis katja

Varnish-Caching

Alternativ zu Redis kann Varnish als Caching-Lösung eingesetzt werden. Dadurch lassen sich die Caching-Einstellungen ziemlich detailliert anpassen. Die nachfolgenden Punkte orientieren sich an den Blog-Artikeln Varnish Virtual Hosts. The Right Way sowie Purge and proxies, a love-hate affair. In der Wordpress-Dokumentation ist ein umfangreiches Konfigurationsbeispiel für Varnish enthalten. Dort werden unnötige Weise viele Sachen gedopplet, die Varnish durch seine Built-in vcl_recv sowieso macht5.

  • Installation über die Paketquellen:

    apt install varnish
  • Varnish lauscht anschließend auf Port 6081 auf Verbindungen.

  • Da die freie Version keine SSL-Terminierung unterstützt, muss Varnish hinter einem Reverse-Proxy laufen
  • Die Konfiguration in /etc/varnish/default.vcl sieht so aus:

    vcl 4.0;
    
    acl local {
        "localhost";
        "192.168.0.2";
        "192.168.0.1";
    }   
    
    import std;
    
    backend default { 
        .host = "127.0.0.1";
        .port = "80";
    }   
    
    sub vcl_recv {
    }
    
    sub vcl_backend_response {
    }
    
    sub vcl_deliver {
        unset resp.http.server;
        unset resp.http.via;
        unset resp.http.x-powered-by;
        unset resp.http.x-runtime;
        unset resp.http.x-varnish;
    }   
    
    include "vhosts.vcl";
  • Hier werden folgende Sachen konfiguriert:
    • Die ACL-Regel local besagt, dass der Cache nur von berechtigten Adresse gelöscht werden kann. 192.168.0.1 ist in diesem Fall der Reverse-Proxy.

    • Das Modul std wird import - es wird für die ACL-Regel benötigt.

    • Unter sub vcl_deliver wir die Auslieferung verschiedener Header unterdrückt.

    • Die Konfiguration von einzelnen Seiten erfolgt über die Einbindung der Datei /etc/varnish/hosts.vcl.

  • In /etc/varnish/hosts.vcl werden wiederum lediglich weitere Seiten inkludiert:

    include "sites.d/example.org.vcl";
  • Im anzulegenden Verzeichnis /etc/varnish/sites.d befinden sich dann die spezielle Seitenkonfiguration example.org.vcl:

    sub vcl_recv {
        if (req.http.host == "example.org") {
            set req.backend_hint = default;
        }
        include "sites.d/wordpress.vcl";
    }
    
    sub vcl_backend_response {
        if (beresp.ttl == 120s) {
            set beresp.ttl = 1h;
        }
    }
  • Hier werden folgende Sachen festgelegt:
    • Inkludieren der allgemeinen Wordpress-Konfiguration
    • Konfigurieren der Haltbarkeit der gecachten Sachen
  • Letzendlich sieht die sites.d/wordpress.vcl so aus:

    if (req.http.Upgrade ~ "(?i)websocket") {
        return (pipe);
    }
    
    if (req.url ~ "wp-admin|wp-login|login") {
        return (pass);
    }
    
    set req.http.cookie = regsuball(req.http.cookie, "wp-settings-\d+=[^;]+(; )?", "");
    set req.http.cookie = regsuball(req.http.cookie, "wp-settings-time-\d+=[^;]+(; )?", "");
    set req.http.cookie = regsuball(req.http.cookie, "wordpress_test_cookie=[^;]+(; )?", "");
        if (req.http.cookie == "") {
            unset req.http.cookie;
        }
    
    if (req.method == "PURGE") {
        if (std.ip(req.http.x-real-ip, "0.0.0.0") ~ local) {
            return (purge);
        } else {
            return (synth(403));
        }
    }
  • Damit die ACL-Regel funktioniert und nur berechtigte IP-Adressen den Cache löschen können, muss der Reverse-Proxy den Header X-Real-IP setzen.

  • Zum automatischen Löschen der Caches bei Änderungen an der Seite kann das Plugin Proxy Cache Purge verwendet werden.

Abschließende Hinweise

  • In der obigen Apachekonfiguration wird mittels rewrite die Installation von eigenen Themes und Plugins unterbunden. Ergänzend dazu kann die Bearbeitung vorhandener Themes und Plugins im Dashboard verhindert werden:

    # wp-config.php
    ...
    define('DISALLOW_FILE_EDIT', true)
  • Ein paar Abweichungen von den gängigen Anleitungen zur Einrichtung von PHP-FPM seien noch erwähnt:
    • Das Mounten oder Kopieren von sendmail in das chroot für Mailfunktionalität: Das funktioniert so nicht. Sendmail hat weitere Abhängigkeiten und braucht Zugriff auf verschiedene Verzeichnisse. Als Aternative wird mini_sendmail vorgeschlagen. Mittels der oben dargestellten PHP-FPM-Konfiguration wird mail() eh deaktiviert. Sendmail ist also in diesem Setup nicht notwendig. Der Mailversand aus Wordpress erfolgt mittels eines SMTP-Plugins.

    • Das Mounten oder Kopieren von /etc/ssl/certs in das chroot: Die Zertikate alleine reichen für die volle Funktionalität nicht aus. PHP benötigt zudem Zugriff auf /usr/lib/ssl/openssl.cnf.


Hinweise und Links

CategorySystemausfall

Fussnoten

  1. Die Anmeldung per ssh-Schlüssel ist weiterhin möglich (1)

  2. Damit die Quota-Kernelmodule verfügbar sind (2)

  3. Falls quotaon den Fehler Devive or ressource busy anzeigt, Quota deaktivieren und erneut aktivieren (3)

  4. Die Installation über das Wordpress Theme- und Pluginverzeichnis ist weiterhin möglich (4)

  5. Siehe The Varnish Book, Kapitel 7.8 "Built-in vcl_recv" (5)


Creative Commons Lizenzvertrag
This page is licensed under a Creative Commons Attribution-ShareAlike 2.5 License.