Da ich mich zur Zeit sehr viel mit Docker und dem Umzug meiner Dienste in Container beschäftige, war ich vor kurzem an dem Punkt angekommen, an dem es darum ging, meine öffentlich zugänglichen Dienste (Nextcloud, WordPress, Plex Media Server und Roundcube) in Container umzuziehen.
Hierzu hatte ich mir schon seit langem Gedanken macht, wie ich am besten die Erreichbarkeit von außen löse. Anfangs liefen noch alle Dienste im gleichen gemeinsamen Apache 2 Container, was jedoch nicht dem Konzept hinter Containern (Ein Dienst, ein Container) entsprach. Relativ schnell kam ich also auf die Idee, die eigentlichen Dienste hinter einem transparenten Reverse-Proxy zu verstecken und nur diesen von außen erreichbar zu machen.
Dieser Ansatz funktionierte auf Anhieb bei allen Diensten sehr gut, außer bei WordPress. Nach der Umstellung von WordPress auf den Reverse-Proxy hatte ich viel mit Redirect-Loops, falschen Links und verwirrenden Fehlermeldungen zu kämpfen. Da es scheinbar nur relativ wenige Menschen im Internet gibt, die eine solche Kombination betreiben, habe ich mich dazu entschieden, kurz über meine Lösungsansätze zu berichten.
Phase 1: VirtualHost auf dem Reverse-Proxy vorbereiten
Da der Reverse-Proxy mehrere Dienste bereitstellt, arbeite ich mit einer VirtualHost-Datei pro Dienst. Diese sind bei mir immer ähnlich aufgebaut. Im folgenden ist die VirtualHost-Datei für WordPress zu sehen:
<IfModule mod_ssl.c>
<VirtualHost *:80>
ServerName blog.example.com
ServerAdmin webmaster@example.com
Header set X-Frame-Options: "SAMEORIGIN"
Header set Referrer-Policy: "same-origin"
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^/(.*) https://%{SERVER_NAME}/$1 [R=301,L]
</IfModule>
</VirtualHost>
<VirtualHost *:443>
ServerName blog.example.com
ServerAdmin webmaster@example.com
ErrorLog /log/blog.example.com_error.log
TransferLog /log/blog.example.com_access.log
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header set X-Frame-Options: "SAMEORIGIN"
Header set Referrer-Policy: "same-origin"
ProxyPreserveHost On
ProxyRequests Off
ProxyPass / http://wordpress/
ProxyPassReverse / http://wordpress/
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
RequestHeader set "X-Forwarded-SSL" expr=%{HTTPS}
RequestHeader set "X-Forwarded-For" expr=%{REMOTE_ADDR}
RequestHeader set "X-Forwarded-Host" expr=%{SERVER_NAME}
SSLEngine on
SSLProtocol +TLSV1.2 +TLSV1.3
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:!DSS
SSLCertificateFile /ssl/example.com/cert.pem
SSLCertificateKeyFile /ssl/example.com/privkey.pem
SSLCertificateChainFile /ssl/example.com/fullchain.pem
</VirtualHost>
</IfModule>
Kurz zur Erklärung:
Der erste VirtualHost hat nur die Aufgabe, Verbindungen die per HTTP ankommen auf die HTTPS-Seite umzuleiten. Danach beginnt der eigentliche Dienst. Wichtig hierbei sind eigentlich nur die mit Proxy* und Request* beginnenden Zeilen. Hinter ProxyPass und ProxyPassReverse kommt der interne Hostname bzw. die interne IP-Adresse des Dienstes, der von außen erreichbar sein soll. Hinter den X-Forwarded Zeilen verbergen sich HTTP-Header die WordPress unter anderem die richtige IP-Adresse des Besuchers mitteilen. Dies ist wichtig, da sonst alle Seitenaufrufe scheinbar von der internen IP-Adresse des Proxys kommen.
Phase 2: Modifikation der wp-config.php
Damit WordPress korrekt funktioniert müssen nun die vom Proxy zusätzlich gesendeten Header verarbeitet werden. Hierzu sind einige Anpassungen in der wp-config.php in eurem WordPress-Hauptverzeichnis erforderlich. Der folgende Code muss hier hinzugefügt werden:
/** Einstellungen fuer Betrieb hinter einem Reverse-Proxy. */
if($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'){
$_SERVER['HTTPS'] = 'on';
$_SERVER['SERVER_PORT'] = 443;
}
if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_X_FORWARDED_FOR"];
}
if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
}
Hierbei ist es extrem wichtig, dass dieser Absatz vor der folgenden Zeile steht:
/** Definiert WordPress-Variablen und fügt Dateien ein. */
require_once(ABSPATH . 'wp-settings.php');
Wenn diese Zeile vor der Header-Verarbeitung stehen sollte, funktioniert zwar die Seite normal, jedoch bekommt ihr beim Zugriff auf das Admin-Panel die Meldung „Du hast nicht die erforderlichen Rechte, um auf diese Seite zuzugreifen.„
Damit sollte die Magie auch schon erledigt sein, und die Seite sollte wie gewohnt funktionieren. Und durch die zusätzlichen Header sollten trotzdem Plugins wie WordFence noch die richtige IP-Adresse des Besuchers erhalten.