WordPress

Siehe auch

Verwandte Artikel
Offizielle Dokumentation
Linuxfabrik

WordPress ist ein in PHP geschriebenes CMS, das seine Inhalte in einer MariaDB- oder MySQL-Datenbank ablegt. Auf RHEL läuft es üblicherweise unter Apache mit PHP-FPM, die Site-Dateien liegen unter /var/www/html/<site>.

Installation

WordPress läuft auf Apache mit PHP-FPM und einer MariaDB. Vorausgesetzt werden ein Webserver mit PHP-FPM (siehe Apache httpd und PHP) und eine Datenbank samt Benutzer (siehe MariaDB), hier die Datenbank wordpress mit dem Benutzer wordpress. Installiert wird über die offizielle Kommandozeile WP-CLI.

WP-CLI installieren:

curl \
    --silent \
    --location https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
    --output /usr/local/bin/wp
chmod +x /usr/local/bin/wp

WordPress in das Site-Verzeichnis herunterladen, wp-config.php erzeugen und installieren. Die wp-Aufrufe laufen als Webserver-Benutzer apache, damit die Dateirechte stimmen:

SITE_PATH=/var/www/html/www.example.com
mkdir --parents "$SITE_PATH"
chown apache:apache "$SITE_PATH"

sudo --user apache /usr/local/bin/wp core download \
    --path="$SITE_PATH"
sudo --user apache /usr/local/bin/wp config create \
    --path="$SITE_PATH" \
    --dbhost=localhost \
    --dbname=wordpress \
    --dbuser=wordpress \
    --dbpass=linuxfabrik
sudo --user apache /usr/local/bin/wp core install \
    --path="$SITE_PATH" \
    --url=https://www.example.com \
    --title='Example' \
    --admin_user=wp-admin \
    --admin_password=linuxfabrik \
    --admin_email=info@example.com

wp core download, wp config create und wp core install sind der offizielle CLI-Installationsweg und lassen sich zu einem Skript verketten.

SELinux File-Kontexte korrekt setzen:

semanage fcontext --add \
    --type httpd_sys_rw_content_t \
    "/var/www/html(/.*)?/wp-content(/.*)?"
semanage fcontext --add \
    --type httpd_sys_rw_content_t \
    "/var/www/html(/.*)?/wp-content/upgrade(/.*)?"
semanage fcontext --add \
    --type httpd_sys_rw_content_t \
    "/var/www/html(/.*)?/wp-content/uploads(/.*)?"
semanage fcontext --add \
    --type httpd_sys_script_exec_t \
    "/var/www/html(/.*)?/wp-config\.php"
semanage fcontext --add \
    --type httpd_sys_script_exec_t \
    "/var/www/html(/.*)?/wp-includes/.*\.php"

restorecon -r /var/www/html/www.example.com

Updates

WordPress aktualisiert man von Hand oder automatisiert es. Für die Automatisierung gibt es zwei Wege: einen Cron-Job mit WP-CLI (wie der manuelle Weg, inklusive der SELinux-Kontext-Anpassung über chcon/restorecon) oder einen systemd-Timer, der wp-cron.php über HTTP anstösst (so macht es die LFOps-Rolle).

Manuell

WordPress Core, Themes, Plugins und Datenbank aktualisieren:

SITE_PATH=/var/www/html/www.example.com

chcon --recursive system_u:object_r:httpd_sys_rw_content_t:s0 $SITE_PATH

sudo --user apache /usr/local/bin/wp core check-update --path=$SITE_PATH
sudo --user apache /usr/local/bin/wp core update --path=$SITE_PATH
sudo --user apache /usr/local/bin/wp core update-db --path=$SITE_PATH
sudo --user apache /usr/local/bin/wp theme update --all --path=$SITE_PATH
sudo --user apache /usr/local/bin/wp plugin update --all --path=$SITE_PATH

restorecon -r $SITE_PATH
systemctl restart php-fpm

Einzelnes Plugin aktualisieren:

SITE_PATH=/var/www/html/www.example.com
sudo --user apache /usr/local/bin/wp plugin update myplugin --path=$SITE_PATH

Automatisiert per cron

Per Cron werden regelmässig die fälligen WP-Cron-Events ausgelöst, darunter die automatischen WordPress-Updates. Der Eintrag setzt vorher die SELinux-Kontexte schreibbar und stellt sie danach wieder her:

crontab
44 */4 * * * chcon --recursive system_u:object_r:httpd_sys_rw_content_t:s0 /var/www/html/www.example.com; wp cron event run --due-now > /dev/null 2>&1; restorecon -r /var/www/html/www.example.com

Automatisiert per systemd

Ein systemd-Timer stösst wp-cron.php regelmässig über HTTP an. Das ist der WordPress-native Weg: bei aktiviertem WP-Cron macht WordPress denselben Loopback-Aufruf bei jedem Seitenaufruf selbst. Damit das nicht zusätzlich passiert, den eingebauten WP-Cron in der wp-config.php abschalten:

SITE_PATH=/var/www/html/www.example.com
sudo --user apache /usr/local/bin/wp config set DISABLE_WP_CRON true --raw --path=$SITE_PATH
/etc/systemd/system/wordpress-cron.service
[Unit]
Description=Wordpress Cron Service

[Service]
ExecStart=/usr/bin/curl http://localhost/wp-cron.php \
    --header 'Host: www.example.com' \
    --header 'X-Forwarded-Proto: https'
Type=oneshot
User=root

[Install]
WantedBy=basic.target
/etc/systemd/system/wordpress-cron.timer
[Unit]
Description=Wordpress Cron Timer

[Timer]
OnCalendar=*:0/15

[Install]
WantedBy=timers.target

Timer aktivieren:

systemctl daemon-reload
systemctl enable --now wordpress-cron.timer

Hardening

WordPress aktiviert die REST-API unter /wp-json/ standardmässig und ohne Authentifizierung. Ungeschützt lassen sich darüber Daten abgreifen: /wp-json/wp/v2/users listet die Benutzernamen (Steilvorlage für Brute-Force über xmlrpc.php), und /wp-json/wp/v2/posts, /pages sowie /media geben Inhalte und hochgeladene Dateien preis, auch solche, die nirgends öffentlich verlinkt sind.

Die API komplett abzuschalten ist keine Lösung, der Block-Editor (Gutenberg) und viele Plugins setzen sie voraus. Stattdessen Authentifizierung erzwingen. WordPress-seitig erledigt das der Filter rest_authentication_errors in der functions.php des Themes, der anonyme Zugriffe mit 401 abweist:

wp-content/themes/<theme>/functions.php
add_filter('rest_authentication_errors', function ($result) {
    // leave an existing authentication result untouched
    if (!empty($result)) {
        return $result;
    }
    // reject anonymous REST requests
    if (!is_user_logged_in()) {
        return new WP_Error(
            'rest_not_logged_in',
            'REST API requires authentication.',
            array('status' => 401)
        );
    }
    return $result;
});

Alternativ lässt sich /wp-json am Reverse Proxy per HTTP-Basic-Auth absichern, oder man steuert die Endpunkte selektiv über ein Plugin wie „Disable REST API“.

Migration von Server A nach Server B

Eine WordPress-Instanz vom alten Server (hier old.example.com auf old-server) auf einen neuen Server mit neuer URL (hier new.example.com auf new-server) übertragen.

  1. Site-Verzeichnis und Datenbank-Dump vom alten Server holen:

rsync \
    --archive \
    --verbose \
    --rsh=ssh \
    --rsync-path='sudo rsync' \
    old-server:/var/www/html/old.example.com /tmp/
rsync \
    --archive \
    --verbose \
    --rsh=ssh \
    --rsync-path='sudo rsync' \
    old-server:/backup/mariadb-dump /tmp/
  1. Beides auf den neuen Server kopieren, das Site-Verzeichnis dabei auf den neuen Namen legen:

rsync \
    --archive \
    --verbose \
    --rsh=ssh \
    --rsync-path='sudo rsync' \
    /tmp/old.example.com/ new-server:/var/www/html/new.example.com/
rsync \
    --archive \
    --verbose \
    --rsh=ssh \
    --rsync-path='sudo rsync' \
    /tmp/mariadb-dump new-server:/tmp/
  1. Datenbank auf dem neuen Server wiederherstellen:

myloader \
    --host=localhost \
    --user=mariadb-admin \
    --ask-password \
    --directory /tmp/mariadb-dump \
    --source-db wordpress \
    --drop-database \
    --queries-per-transaction=1000 \
    --threads=0 \
    --verbose=3
  1. Die Datenbank trägt überall noch die alte URL. Sie in allen Tabellen durch die neue ersetzen, die Permalinks neu schreiben und die von Elementor generierten CSS-Dateien neu erzeugen, sonst verweisen Seiten weiter auf die alte Umgebung. Die zweite search-replace-Zeile fängt die URL in ihrer escapten Form ab, wie sie in serialisierten JSON-Daten steht:

cd /var/www/html/new.example.com/

sudo --user apache /usr/local/bin/wp option update home \
    'https://new.example.com'
sudo --user apache /usr/local/bin/wp option update siteurl \
    'https://new.example.com'
sudo --user apache /usr/local/bin/wp search-replace \
    'https://old.example.com' \
    'https://new.example.com' \
    --all-tables
sudo --user apache /usr/local/bin/wp search-replace \
    'https:\/\/old.example.com' \
    'https:\/\/new.example.com' \
    --all-tables
sudo --user apache /usr/local/bin/wp elementor flush_css
sudo --user apache /usr/local/bin/wp cache flush
sudo --user apache /usr/local/bin/wp rewrite flush

chown --recursive apache:apache /var/www/html/new.example.com
restorecon -rF /var/www/html/new.example.com

Im GUI entspricht das Elementor > Editor > Werkzeuge > „Allgemein (Elementor Cache)“ und „URL ersetzen“.

  1. Page-Cache leeren. Caching-Plugins wie „WP Fastest Cache“ legen fertige statische HTML-Dateien (mit den alten URLs) unter wp-content/cache/ ab und liefern diese an anonyme Besucher aus. Als angemeldeter Admin wird der Cache umgangen, weshalb die Vorschau korrekt aussieht, die öffentliche Seite aber weiterhin CSS und Bilder von der alten Umgebung lädt.