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:
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
[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
[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:
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.
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/
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/
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
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“.
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.