Ein Wiki privater Natur

Apache Reverse Proxy: Basic Auth schlägt fehl hinter ProxyPass

Symptom

Ein Unterverzeichnis (z.B. /stats/) soll vom Apache-Webserver direkt aus dem Dateisystem bedient werden — nicht durch den Reverse Proxy (z.B. wiki.js, Node.js, o.ä.). Das Verzeichnis ist per .htaccess mit Basic Auth geschützt.

Statt eines Login-Dialogs erscheint jedoch eine Fehlerseite des Backend-Dienstes.

Diagnose

Mit curl lässt sich das Problem eindeutig identifizieren:

curl -sI https://app.example.com/stats/

Fehlerhafte Antwort:

HTTP/1.1 404 Not Found
WWW-Authenticate: Basic realm="Members Only"
X-Frame-Options: deny
X-Powered-By: Express
Content-Type: text/html; charset=utf-8

Das Muster 404 vom Backend (erkennbar an X-Powered-By: Express oder ähnlichen Backend-Headern) kombiniert mit WWW-Authenticate von Apache ist der charakteristische Hinweis auf diesen Fehler. Apache hat die .htaccess gelesen und den Auth-Header gesetzt, aber die Antwort kam trotzdem vom Proxy-Backend.

Ausgangskonfiguration

Typischer VirtualHost mit Reverse Proxy und Fehlerseiten — auf den ersten Blick korrekt:

<VirtualHost *:443>
    ServerName app.example.com
    DocumentRoot /var/www/app.example.com/web

    # Fehlerseiten lokal vom Dateisystem
    Alias /error/ "/var/www/app.example.com/web/error/"
    ErrorDocument 400 /error/400.html
    ErrorDocument 401 /error/401.html
    ErrorDocument 403 /error/403.html
    ErrorDocument 404 /error/404.html
    ErrorDocument 500 /error/500.html
    ErrorDocument 502 /error/502.html
    ErrorDocument 503 /error/503.html

    <Directory /var/www/app.example.com/web>
        AllowOverride All
        Require all granted
    </Directory>

    # SSL ...

    # Reverse Proxy zur Node.js-Anwendung
    ProxyPreserveHost On
    ProxyPass /stats !          # Ausnahme: stats soll lokal bedient werden
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/
</VirtualHost>

.htaccess im Verzeichnis /var/www/app.example.com/web/stats/:

AuthType Basic
AuthName "Members Only"
AuthUserFile /var/www/app.example.com/web/stats/.htpasswd_stats
Require valid-user
DirectoryIndex index.html

Ursache

ProxyPass /stats ! funktioniert korrekt — Apache bedient /stats/ nicht über den Proxy. Die Basic Auth greift, und Apache möchte 401 Unauthorized zurückgeben.

Dafür erzeugt Apache intern einen Subrequest auf /error/401.html. Für diesen Subrequest existiert kein Proxy-Ausschluss — ProxyPass / greift, und der Subrequest wird an das Backend weitergeleitet. Das Backend (Node.js) kennt die Route /error/401.html nicht und antwortet mit seinem eigenen 404.

Apache mischt dann beide Antworten: WWW-Authenticate-Header aus der .htaccess-Verarbeitung + 404-Body vom Backend.

Das Tückische: Der eigentliche ProxyPass /stats ! ist korrekt und wirksam. Das Problem liegt eine Ebene tiefer, in der Fehlerbehandlung.

Lösung

ProxyPass /error/ ! hinzufügen, damit Fehlerseiten-Subrequests ebenfalls vom Dateisystem bedient werden:

<VirtualHost *:443>
    ServerName app.example.com
    DocumentRoot /var/www/app.example.com/web

    Alias /error/ "/var/www/app.example.com/web/error/"
    ErrorDocument 400 /error/400.html
    ErrorDocument 401 /error/401.html
    ErrorDocument 403 /error/403.html
    ErrorDocument 404 /error/404.html
    ErrorDocument 500 /error/500.html
    ErrorDocument 502 /error/502.html
    ErrorDocument 503 /error/503.html

    <Directory /var/www/app.example.com/web>
        AllowOverride All
        Require all granted
    </Directory>

    # SSL ...

    ProxyPreserveHost On
    Alias /stats /var/www/app.example.com/web/stats   # explizit dem Dateisystem zuordnen
    ProxyPass /stats/ !
    ProxyPass /stats !
    ProxyPass /error/ !                               # ← entscheidend
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/
</VirtualHost>

Änderungen gegenüber der fehlerhaften Konfiguration:

Verifikation

Nach apachectl configtest && systemctl reload apache2:

curl -sI https://app.example.com/stats/

Korrekte Antwort:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Members Only"
Server: Apache
Content-Type: text/html; charset=utf-8

401 (statt 404) bedeutet: Apache bedient /stats/ selbst, die Basic Auth greift korrekt, und der Login-Dialog erscheint im Browser.

Merksatz

Bei Apache Reverse Proxy mit ErrorDocument-Direktiven immer auch ProxyPass /error/ ! setzen — sonst laufen Fehlerseiten-Subrequests durch den Proxy.

Umgebung

Apache 2.4, ISPConfig, wiki.js / Node.js als Backend, Debian.


Gefunden nach systematischer Fehleranalyse, Mai 2026 — gemeinsam erarbeitet mit Claude (Anthropic).