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

        <IfModule mod_rewrite.c>
                RewriteEngine On
                RewriteCond %{REQUEST_URI}
                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

        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
                SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
                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.