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:
Alias /stats ...explizit gesetzt — Apache ordnet das Verzeichnis sicher dem Dateisystem zu/stats/und/stats(mit und ohne Trailing Slash) beide ausgeschlossenProxyPass /error/ !verhindert dass Fehlerseiten-Subrequests durch den Proxy laufen
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 auchProxyPass /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).