Verzeichnis mit .htaccess schützen: Basic Auth, APR1-MD5 und bcrypt
Von CaptainDNS
Veröffentlicht am 12. August 2026

- HTTP Basic Auth schützt ein Verzeichnis über eine .htpasswd-Datei (Apache) oder auth_basic_user_file (nginx) - in wenigen Minuten konfiguriert.
- Das Passwort wird niemals im Klartext gespeichert: Es wird gehasht, entweder mit APR1-MD5 (historisch, überall kompatibel) oder mit bcrypt (empfohlen, einstellbarer Rechenaufwand).
- Ohne HTTPS wird Basic Auth im Klartext übertragen: nur hinter TLS aktivieren.
- Die .htpasswd-Datei muss außerhalb des Web-Roots liegen, niemals in einem herunterladbaren Verzeichnis.
- Erstellen Sie Ihre Hashes ohne Kommandozeile mit den APR1-MD5- und bcrypt-Generatoren von CaptainDNS.
Ein Staging-Server, der von Google indexiert wurde. Ein Monitoring-Dashboard, das jeder erreichen kann, der die URL errät. Eine technische Dokumentation, die versehentlich öffentlich im Web veröffentlicht wurde. Drei alltägliche Szenarien - und eine einzige Erste-Hilfe-Lösung in allen drei Fällen: Basic Auth.
Basic Auth existiert seit den Anfängen des Webs und hat sich seitdem kaum verändert. Sie schützt ein Verzeichnis oder eine Datei mit einem einfachen Benutzername/Passwort-Paar, ohne Datenbank, ohne Session, ohne Anwendungsabhängigkeit. Unter Apache genügen eine .htaccess- und eine .htpasswd-Datei. Unter nginx reichen zwei Direktiven: auth_basic und auth_basic_user_file. Fünf Minuten Konfiguration, und der Zugang ist gesperrt.
Dieser Leitfaden beschreibt die konkrete Konfiguration unter Apache und unter nginx, mit den Fallstricken, die eine Stunde kosten: falsch eingestelltes AllowOverride, vergessener Dateipfad, nie neu geladener Dienst. Er erklärt auch, was wirklich in einer .htpasswd-Datei passiert. Warum Apache sein eigenes Hash-Format erfunden hat: APR1-MD5. Warum bcrypt es weitgehend abgelöst hat. Und wie man in voller Sachkenntnis zwischen beiden wählt, statt aus Gewohnheit oder per Copy-Paste aus einem Tutorial von 2015.
Dieser Inhalt richtet sich an Systemadministratoren, DevOps und Backend-Entwickler, die ihr eigenes Hosting verwalten und einen Zugang sperren möchten, ohne auf die Einrichtung eines SSO oder VPNs zu warten.
Warum ein Verzeichnis mit einem Passwort auf einem Webserver schützen?
Basic Auth sperrt den HTTP-Zugriff auf ein Verzeichnis in wenigen Minuten, ohne Anwendungscode oder zu verwaltende Datenbank.
Die Anwendungsfälle wiederholen sich ständig: eine Staging-Umgebung auf einer Subdomain wie preprod.captaindns.com, ein internes Backoffice ohne eigene Authentifizierung, eine technische Dokumentation, die nichts in der Öffentlichkeit zu suchen hat, ein Monitoring-Tool, das bei einem eiligen Deployment versehentlich exponiert wurde. In all diesen Fällen hat niemand die Zeit oder die Notwendigkeit, ein vollständiges Benutzerkontensystem zu programmieren. Eine HTTP-Schicht genügt völlig.
Der klassische Fallstrick: zu glauben, dass ein nicht von der Website aus verlinktes Verzeichnis unsichtbar bleibt. Eine robots.txt, die /preprod/ ausschließt, verhindert nur das Crawling durch standardkonforme Suchmaschinen, nicht den direkten Zugriff. Ein automatisierter Scanner, ein versehentlich in einem Ticket geteilter Link, ein irgendwo durchsickernder Log-Eintrag - und die URL kursiert. Es gibt ganze Tools, die systematisch gängige Pfade (staging, dev, admin, backup) auf vollständigen IP-Bereichen scannen. Nichts Paranoides daran: Das ist permanentes Hintergrundrauschen im Internet.
Eine kurze Inspektion der Zugriffslogs eines im Internet exponierten Servers genügt, um sich davon zu überzeugen. Anfragen an /admin/, /wp-admin/, /.env, /backup.zip oder /phpinfo.php kommen kontinuierlich, Minute für Minute, lange bevor ein Mensch die Zeit hatte, die URL auf anderem Wege zu finden. Diese Scans zielen auf niemanden im Besonderen ab: Sie durchkämmen ganze IP-Bereiche auf der Suche nach bekanntermaßen schlecht geschützten Pfaden. Ein Staging-Verzeichnis mit vorhersehbarem Namen, /staging/ oder /preprod/, landet früher oder später in diesen Logs.
HTTP Basic Auth ist keine vollständige Anwendungsauthentifizierung. Kein richtiges Logout: Der Browser behält die Anmeldedaten im Speicher, solange er nicht geschlossen wird. Keine Begrenzung der Anzahl von Versuchen. Keine Session mit Ablauf, keine feingranulare Rollenverwaltung. Es ist ein Schloss an der Tür, keine ausgefeilte Zugangskontrolle. Für einen Zugang, der differenzierte Rollen oder eine detaillierte Nachverfolgbarkeit erfordert, ist eine dedizierte Anwendungsschicht nötig. Um einen sensiblen Zugang schnell zu sperren, bis etwas Besseres da ist, oder als Ergänzung zu einem bereits bestehenden Schutz, leistet Basic Auth hervorragende Dienste.
Alternativen existieren und sind auf dem Papier oft robuster. Ein VPN filtert den Netzwerkzugriff, bevor überhaupt eine HTTP-Anfrage den Server erreicht. Ein Unternehmens-SSO - Okta, Google Workspace oder Azure AD - zentralisiert die Konten und die Anmeldeprotokollierung. Ein Reverse-Proxy mit OAuth2, z. B. oauth2-proxy oder Authelia, fügt eine echte Anwendungssession mit Ablauf und ordentlichem Logout hinzu. Aber diese Lösungen erfordern eine vollwertige Infrastruktur: einen zu wartenden VPN-Server, einen zu integrierenden Identitätsanbieter, einen zusätzlichen Proxy, der bereitgestellt und überwacht werden muss. Basic Auth lässt sich in wenigen Minuten mit dem konfigurieren, was bereits auf dem Server läuft, ohne externe Abhängigkeit und ohne anderswo bereitzustellende Konten. Nicht die eleganteste Lösung auf dem Papier. Aber sie sperrt einen Zugang tatsächlich noch vor Feierabend.
Basic Auth und Digest Auth: Wie funktioniert die HTTP-Authentifizierung?
Das HTTP-Protokoll enthält seit jeher einen Authentifizierungsmechanismus, der auf Headern basiert, ohne Cookie oder Anwendungssession.
Der Austausch erfolgt in zwei Schritten. Der Browser fordert eine geschützte Ressource an, der Server antwortet mit 401 Unauthorized und einem WWW-Authenticate-Header, der das erwartete Schema und den Namen des geschützten Bereichs - das Realm - angibt. Der Browser zeigt dann seinen nativen Dialog an, der Benutzer gibt seine Anmeldedaten ein, und der Browser wiederholt die Anfrage mit einem Authorization-Header. Solange die Browsersitzung geöffnet bleibt, wird dieser Header automatisch bei jeder Anfrage an dasselbe Realm mitgesendet, ohne erneute Eingabe.
Basic Auth: der Standardmechanismus (RFC 7617)
Basic Auth codiert Benutzername und Passwort in Base64, ohne jegliche Verschlüsselung. Das Format des Authorization-Headers ist Basic, gefolgt von base64(benutzername:passwort).
Wichtiger Hinweis gleich vorweg: Base64 ist keine Verschlüsselung. Es ist eine einfache, reversible Codierung, die von jedem mit einem einzigen Befehl decodiert werden kann.
$ echo -n "admin:passwort" | base64
YWRtaW46cGFzc3dvcnQ=
RFC 7617 stellt unmissverständlich klar: Das Basic-Schema bietet keinen Vertraulichkeitsschutz für die übertragenen Anmeldedaten, und seine Verwendung über eine unverschlüsselte Verbindung setzt sie jedem aus, der den Datenverkehr abfängt. Deshalb sollte Basic Auth niemals auf reinem HTTP laufen.
Dieser Dialog lässt sich direkt mit curl beobachten. Eine erste Anfrage ohne Anmeldedaten erhält die Ablehnung:
$ curl -i https://preprod.captaindns.com/
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Geschuetzter Bereich"
Mit den Anmeldedaten geht die Anfrage durch:
$ curl -i -u admin:passwort https://preprod.captaindns.com/
HTTP/1.1 200 OK
Die Option -u von curl erstellt selbst den Base64-codierten Authorization-Header. Genau das tut ein Browser im Hintergrund nach der Eingabe in seinem Dialog.
Digest Auth: warum sie praktisch verschwunden ist
Digest Auth hasht das Passwort clientseitig vor dem Senden, anstatt es codiert im Klartext zu übertragen.
Die Berechnung kombiniert Benutzername, Realm, Passwort und eine Nonce - einen vom Server bereitgestellten, einmalig verwendbaren Zufallswert - über eine Hash-Funktion: historisch MD5, mit SHA-256 verfügbar seit RFC 7616. Theoretisch schützt dieser Ansatz die Anmeldedaten auch ohne HTTPS, da das Passwort selbst niemals über das Netzwerk übertragen wird.
Der Austausch stützt sich auf eine deutlich umfangreichere Serverantwort als Basic Auth:
WWW-Authenticate: Digest realm="Geschuetzter Bereich",
qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
Der Client berechnet dann HA1 = MD5(benutzername:realm:passwort), HA2 = MD5(methode:URI) und schließlich eine Antwort MD5(HA1:nonce:nc:cnonce:qop:HA2). Diese Rechenkomplexität, die bei jeder Anfrage wiederholt wird, ist genau das, was die meisten Implementierungen dazu bewogen hat, Basic Auth hinter TLS Digest Auth ohne TLS vorzuziehen.
In der Praxis ist Digest Auth praktisch verschwunden. Ihre Implementierung ist clientseitig komplexer: Verwaltung der Nonce, des Anfragezählers nc, des cnonce. Die Unterstützung bleibt in fortgeschrittenen Fällen uneinheitlich, und sie verursacht Probleme hinter bestimmten Proxys und Load Balancern, die diese Art von Dialog nicht erwarten. Vor allem hat die Verbreitung von HTTPS ihren Hauptvorteil hinfällig gemacht. Ein Passwort während der Übertragung zu schützen, hat wenig Sinn, wenn die gesamte Verbindung bereits Ende-zu-Ende verschlüsselt ist. Apache (mod_auth_digest) und nginx über Drittanbietermodule unterstützen es noch, aber fast niemand nutzt es mehr. Der Rest dieses Leitfadens konzentriert sich auf Basic Auth, den De-facto-Standard hinter HTTPS.
Kleine Geschichte des Passwort-Hashings im Web
Das von .htpasswd verwendete Hash-Format hat eine Geschichte, die bis in die Anfänge von Unix zurückreicht, lange vor der Existenz des Webs.
crypt() von Unix und die 8-Zeichen-Grenze von DES
Die Unix-Funktion crypt(), entworfen Ende der 1970er Jahre von Robert Morris für die siebte Edition von Unix, verschlüsselt ein Passwort mit einer 25-fach wiederholten Variante von DES (Data Encryption Standard). Ein 12-Bit-Salt, also 4.096 mögliche Werte, codiert auf 2 Zeichen, verhindert die direkte Wiederverwendung einer vorberechneten Tabelle zwischen Systemen.
Problem: DES arbeitet mit 56-Bit-Nutzblöcken, was das berücksichtigte Passwort auf 8 Zeichen begrenzt. Das neunte und alle folgenden Zeichen werden schlichtweg ignoriert. Ein 20-stelliges Passwort verhält sich exakt wie seine ersten 8 Zeichen.
Ende der 1970er Jahre stellten 25 DES-Iterationen einen echten Rechenaufwand dar. In den 1990er Jahren hatte sich die Leistung von Consumer-Prozessoren um mehrere Größenordnungen vervielfacht. Dieser Schutz war einem Brute-Force-Angriff von einem einfachen PC aus bereits nicht mehr gewachsen.
Dieses Verhalten zu überprüfen erfordert nur einen Befehl, der weiterhin über openssl verfügbar ist:
$ openssl passwd -crypt -salt ab SehrLangesPasswort
abXXXXXXXXXXXX
Ersetzt man SehrLangesPasswort durch eine beliebige Zeichenkette mit mehr als 8 Zeichen, wird exakt derselbe Hash erzeugt, solange die ersten 8 Zeichen identisch bleiben. Der einfachste Beweis, dass ein 1979 entworfener Algorithmus in einer heute erstellten .htpasswd-Datei nichts mehr zu suchen hat.
Warum Apache APR1-MD5 mit Version 1.3 entwickelt hat
Das Problem von Apache war weniger die Schwäche von crypt() als vielmehr dessen Portabilität. Die Implementierung von crypt() unterscheidet sich von System zu System: Die glibc von Linux, die BSDs und Windows erzeugen nicht dieselben Hashes für dieselben Eingaben. Eine auf einem Linux-Rechner erstellte .htpasswd-Datei konnte nach dem Deployment auf einem BSD-Server unlesbar werden.
Die Lösung kam von FreeBSD, wo Poul-Henning Kamp 1994 ein auf MD5 basierendes Hash-Format entworfen hatte, das unabhängig von der crypt()-Implementierung des Hostsystems war. Apache übernahm dieses Prinzip für seinen htpasswd-Befehl unter dem Namen apr1, verfügbar über die Option -m seit Apache 1.3 im Jahr 1998. Ergebnis: Derselbe Hash, identisch erzeugt und verifiziert, unabhängig vom Betriebssystem des Servers.
APR1-MD5 wendet 1.000 MD5-Iterationen auf das Passwort und einen 8-Zeichen-Salt an, mit einer finalen Codierung im crypt-eigenen Base64-Alphabet. Gegenüber einem reinen MD5 - eine einzige Iteration - ist der Gewinn real: tausendmal mehr Rechenaufwand, um ein Passwort zu testen. Aber MD5 bleibt eine schnelle Funktion, ursprünglich für die Datenintegrität konzipiert und nicht dafür, massiven Angriffsversuchen standzuhalten. Eine moderne GPU berechnet Millionen von MD5-Iterationen pro Sekunde. Folglich bleibt APR1-MD5 anfällig für Wörterbuchangriffe auf schwache oder mittelstarke Passwörter.
Die Einführung von bcrypt, Provos und Mazières, 1999
bcrypt entstand ein Jahr nach der Übernahme von APR1-MD5 durch Apache, mit einem radikalen Ansatzwechsel: Statt eine feste Anzahl von Iterationen vorzugeben, sollte sie über die Zeit hinweg anpassbar sein.
Niels Provos und David Mazières veröffentlichten 1999 "A Future-Adaptable Password Scheme" auf der USENIX-Konferenz. Ihre Feststellung: Jede Hash-Funktion mit festem Rechenaufwand wird früher oder später von der Hardware eingeholt. Ihre Antwort, bcrypt, leitet sich von der bewusst rechen- und speicherintensiven Schlüsselinitialisierungsphase der Blowfish-Verschlüsselung ab, die 1993 von Bruce Schneier entworfen wurde. Die Anzahl der Runden wird über einen Kostenfaktor eingestellt, ausgedrückt als Zweierpotenz: Jede Erhöhung verdoppelt die erforderliche Arbeit - für den Angreifer wie für den Server.
Dieses Design ändert die Spielregeln. Ein Algorithmus mit festen Iterationen muss ersetzt werden, wenn er für die Hardware der jeweiligen Zeit zu schnell berechenbar wird. bcrypt hingegen passt sich an: Man erhöht den Kostenfaktor, und der Hash hält mit dem Hardware-Fortschritt Schritt, ohne den Algorithmus zu wechseln oder die Kompatibilität bereits gespeicherter Hashes zu brechen.
htpasswd -B erzeugt bcrypt seit Apache 2.4, veröffentlicht 2012, und es ist zum De-facto-Standard für .htpasswd sowie für die Passwortspeicherung im Allgemeinen geworden. Bei neueren Projekten konkurriert inzwischen Argon2, der Gewinner der Password Hashing Competition von 2015, insbesondere wegen seiner erhöhten Resistenz gegenüber dedizierten Schaltkreisen (ASIC). Argon2 liegt außerhalb des Rahmens von .htpasswd: Weder Apache noch nginx unterstützen es nativ für die HTTP-Authentifizierung.
Das Originalpapier von 1999 empfahl einen Standard-Kostenfaktor von 6, der für die damalige Hardware als angemessen galt. Der Standardwert von htpasswd -B liegt heute bei 10, und für einen sensiblen Zugang auf 12 zu gehen, bleibt gängig. Genau diesen Mechanismus hatten Provos und Mazières vorhergesehen: Die Zahl selbst ist bedeutungslos, einzig die Möglichkeit, sie zu erhöhen, ohne den Algorithmus zu wechseln oder bereits gespeicherte Hashes zu beschädigen, zählt.
Passwortschutz mit Apache konfigurieren (.htaccess und .htpasswd)
Unter Apache genügen zwei Dateien: .htpasswd für die Konten, .htaccess oder ein <Directory>-Block für die Deklaration.
Die .htpasswd-Datei erstellen
Der Befehl htpasswd, bereitgestellt vom Paket apache2-utils unter Debian/Ubuntu oder httpd-tools unter RHEL/CentOS, verwaltet die Erstellung und Aktualisierung der Datei.
# Datei erstellen und erstes Konto hinzufügen, mit bcrypt
htpasswd -c -B /var/www/secrets/.htpasswd admin
# Zweites Konto zur bestehenden Datei hinzufügen (ohne -c, sonst wird die Datei überschrieben)
htpasswd -B /var/www/secrets/.htpasswd redakteur
Die Option -c erstellt die Datei. Nur einmal verwenden: Ein erneuter Aufruf löscht die bereits vorhandenen Konten. -B erzwingt das bcrypt-Format. Ohne diese Option fällt htpasswd standardmäßig auf APR1-MD5 zurück oder akzeptiert -d für das alte crypt()-DES, -s für SHA-1. Diese letzten beiden Formate haben in einer heute erstellten Datei nichts mehr zu suchen.
Kein SSH-Zugang oder apache2-utils nicht auf dem Server vorhanden? Ein Online-Generator erzeugt dieselbe Zeile direkt im Browser, ohne etwas zu installieren.
Unter Apache muss das entsprechende Modul - mod_auth_basic, mit mod_authn_file zum Lesen der Datei - aktiviert sein:
sudo a2enmod auth_basic authn_file
sudo systemctl reload apache2
Auf einer Distribution, bei der diese Module nicht standardmäßig geladen werden, schlägt die untenstehende Konfiguration stillschweigend fehl oder liefert einen Fehler 500 Internal Server Error, der nur in error.log sichtbar ist.
Den Schutz deklarieren: AuthType, AuthName, AuthUserFile, Require valid-user
Zwei mögliche Stellen für diese Direktiven: die .htaccess-Datei des zu schützenden Verzeichnisses oder ein <Directory>-Block in der VirtualHost-Konfiguration.
In .htaccess, im Stammverzeichnis des zu schützenden Ordners:
AuthType Basic
AuthName "Geschuetzter Bereich"
AuthUserFile /var/www/secrets/.htpasswd
Require valid-user
Dasselbe Ergebnis im VirtualHost, als <Directory>-Block:
<VirtualHost *:443>
ServerName preprod.captaindns.com
DocumentRoot /var/www/html/preprod
<Directory "/var/www/html/preprod">
AuthType Basic
AuthName "Geschuetzter Bereich"
AuthUserFile /var/www/secrets/.htpasswd
Require valid-user
</Directory>
</VirtualHost>
Beide Syntaxvarianten sind funktional gleichwertig, aber nicht in der Performance. Eine .htaccess-Datei wird von Apache bei jeder Anfrage neu gelesen - für jedes übergeordnete Verzeichnis des angeforderten Pfades. Der <Directory>-Block hingegen wird nur einmal beim Serverstart geladen. Bei einer Website mit hohem Datenverkehr vermeidet der <Directory>-Block wiederholte Festplattenzugriffe bei jeder Anfrage.
Require valid-user autorisiert jedes in der .htpasswd-Datei vorhandene Konto, unabhängig von seinem Namen. Um den Zugriff auf bestimmte Konten zu beschränken, ersetzt Require user admin redakteur die Zeile.
Der AllowOverride-AuthConfig-Fallstrick
Eine .htaccess-Datei mit perfekt korrekten Authentifizierungsdirektiven, die aber keinerlei Wirkung zeigt. Dies ist das am häufigsten in Hoster-Foren und auf StackOverflow gemeldete Symptom.
Die Ursache: Die Direktive AllowOverride, definiert auf VirtualHost-Ebene oder in der globalen Konfiguration, steuert, welche Kategorien von Direktiven eine .htaccess-Datei ändern darf. Steht sie auf None, ignoriert Apache die .htaccess-Datei schlicht und einfach. Ohne Fehler, ohne sichtbare Warnung in den Anwendungslogs. Nur ein Verzeichnis, das offen bleibt.
<Directory "/var/www/html">
AllowOverride AuthConfig
</Directory>
AuthConfig erlaubt die authentifizierungsbezogenen Direktiven (AuthType, AuthName, AuthUserFile, Require). All erlaubt alles - das funktioniert, öffnet aber mehr als nötig. Nach der Änderung genügt ein Neuladen:
sudo apachectl configtest && sudo systemctl reload apache2
configtest prüft die Syntax vor dem Neuladen. Ein einfacher Test verhindert, einen Produktionsserver wegen eines Tippfehlers in der Konfigurationsdatei lahmzulegen.
Überprüfen, dass der Schutz funktioniert
Nach dem Neuladen bestätigt eine curl-Anfrage die Einrichtung vor dem Test im Browser:
curl -i https://preprod.captaindns.com/preprod/
# Muss 401 Unauthorized zurückgeben
curl -i -u admin:passwort https://preprod.captaindns.com/preprod/
# Muss 200 OK zurückgeben
Wenn der erste Befehl direkt 200 zurückgibt, gibt es zwei wahrscheinliche Ursachen: AllowOverride ignoriert die .htaccess-Datei, oder der <Directory>-Block zielt auf den falschen Pfad. Die Fehlerprotokolldatei - /var/log/apache2/error.log unter Debian/Ubuntu - bestätigt bei jedem Versuch, ob die AuthUserFile-Datei gelesen wird oder nicht.
Passwortschutz mit nginx konfigurieren (auth_basic)
nginx schützt ein Verzeichnis mit zwei Direktiven in einem location-Block, ganz ohne .htaccess-Datei.
auth_basic und auth_basic_user_file
Das Modul ngx_http_auth_basic_module ist standardmäßig in nginx einkompiliert. Ein location-Block genügt:
server {
listen 443 ssl;
server_name preprod.captaindns.com;
location /preprod/ {
auth_basic "Geschuetzter Bereich";
auth_basic_user_file /var/www/secrets/.htpasswd;
}
}
auth_basic definiert den Realm-Text, der im Browser-Dialog angezeigt wird, oder off, um die Authentifizierung auf einem von einem übergeordneten location geerbten Unterpfad zu deaktivieren. auth_basic_user_file verweist auf dasselbe Dateiformat, das auch von Apache verwendet wird.
Diese .htpasswd-Datei ist direkt zwischen beiden Servern kompatibel. Ein auf der Apache-Seite generiertes Benutzername/Passwort-Paar funktioniert unverändert auf nginx und umgekehrt. nginx liest das Format $apr1$ seit langem über seine eigene eingebettete MD5-crypt-Implementierung und das bcrypt-Format $2y$ seit nginx 1.0.3, auf Systemen, deren crypt_r() bcrypt unterstützt - was die gängigen, mit libxcrypt ausgestatteten Linux-Distributionen abdeckt.
nginx liest keine .htaccess: ein zu verstehender Architekturunterschied
nginx sucht niemals nach einer .htaccess-Datei. Die gesamte Konfiguration lebt in nginx.conf und den eingebundenen Dateien, die nur einmal beim Dienststart geladen werden.
Diese Architekturentscheidung, seit den Anfängen von nginx getroffen, erklärt einen Teil seines Performance-Rufs. Apache prüft bei aktiviertem AllowOverride bei jeder Anfrage die Existenz einer .htaccess-Datei in jedem übergeordneten Verzeichnis des angeforderten Pfades. nginx hat nichts dergleichen zu tun: Die gesamte Konfiguration ist bereits im Speicher geladen, einsatzbereit, noch bevor die erste Anfrage eintrifft.
Praktische Konsequenz: Das Copy-Pasten einer Apache-.htaccess-Konfiguration in ein von nginx bedientes Verzeichnis bewirkt rein gar nichts. Die Datei wird stillschweigend ignoriert, ohne Fehlermeldung. Die entsprechenden Direktiven müssen in den zugehörigen server- oder location-Block umgeschrieben und dann durch einen Neuladevorgang angewendet werden:
sudo nginx -t && sudo systemctl reload nginx
Mit einer IP-Einschränkung kombinieren
nginx akzeptiert allow und deny zusätzlich zu auth_basic im selben location-Block:
location /preprod/ {
allow 203.0.113.0/24;
deny all;
auth_basic "Geschuetzter Bereich";
auth_basic_user_file /var/www/secrets/.htpasswd;
}
Beide Schichten kombinieren sich. Eine IP außerhalb des erlaubten Bereichs erhält einen 403, noch bevor nginx die Anmeldedaten anfordert. Eine autorisierte IP muss sich dennoch anschließend authentifizieren. Diese doppelte Barriere schützt vor dem Diebstahl des Passworts allein: Ein Angreifer, der die Anmeldedaten erbeutet, aber nicht im richtigen Netzwerk ist, wird vorgelagert blockiert.
Basic Auth hinter einem Reverse-Proxy: Traefik und Caddy
Das .htpasswd-Format beschränkt sich nicht auf Apache und nginx. Moderne Reverse-Proxys übernehmen es unverändert, mit einer nennenswerten Einschränkung bezüglich des akzeptierten Hash-Formats je nach Proxy.
Traefik liest eine .htpasswd-Datei direkt über seine basicAuth-Middleware, deklariert als Docker-Label oder in der statischen Konfiguration:
labels:
- "traefik.http.middlewares.preprod-auth.basicauth.usersfile=/etc/traefik/.htpasswd"
- "traefik.http.routers.preprod.middlewares=preprod-auth"
Die referenzierte Datei ist dieselbe .htpasswd-Datei, die für Apache oder nginx generiert wurde: APR1-MD5 und bcrypt funktionieren beide, ohne Konvertierung.
Caddy hingegen beschränkt die Auswahl auf das absolute Minimum. Seine basic_auth-Direktive akzeptiert nur das bcrypt-Format, erzeugt durch seinen eigenen Befehl caddy hash-password oder, gleichwertig, durch htpasswd -B:
caddy hash-password --plaintext SehrLangesPasswort
Eine in APR1-MD5 übernommene .htpasswd-Datei schlägt hinter Caddy stillschweigend fehl: Eine Authentifizierung ist unmöglich, solange die Konten nicht in bcrypt neu generiert wurden. Diese Einschränkung bestätigt, was die Geschichte des Formats bereits vermuten ließ: bcrypt ist heute das einzige Format, das alle gängigen Webserver und Reverse-Proxys ausnahmslos durchläuft.

APR1-MD5 vs. bcrypt: Welchen Algorithmus für .htpasswd wählen?
Für eine heute erstellte .htpasswd-Datei ist bcrypt die Standardwahl. APR1-MD5 rechtfertigt sich nur durch eine präzise Kompatibilitätsanforderung.
Funktionsweise von APR1-MD5
Die Berechnung verknüpft zwei MD5-Zwischenwerte, dann 1.000 Iterationen, gefolgt von einer spezifischen Codierung.
Zunächst werden zwei initiale Hashwerte berechnet: einer über die Verkettung Passwort + $apr1$ + Salt, der andere über Passwort + Salt + Passwort. Anschließend werden Fragmente des zweiten Hashwerts nach einem Muster, das von der Passwortlänge abhängt, in den ersten zurückgespeist. Dann folgt die Schleife von 1.000 Iterationen: In jeder Runde werden Passwort, Salt und der Hashwert der vorherigen Runde in einer Reihenfolge neu kombiniert, die je nach Parität der Rundennummer variiert. Das Endergebnis, 16 Bytes, wird in einer verschränkten Reihenfolge gelesen und dann im crypt-eigenen Base64-Alphabet (./0-9A-Za-z) codiert, um die 22 Zeichen des Hashes zu erzeugen.
Es sind diese 1.000 Iterationen, die APR1-MD5 von einem reinen MD5 unterscheiden: Sie vertausendfachen den Aufwand eines einzelnen Versuchs. Tausend ist lächerlich angesichts einer modernen GPU, die mehrere Milliarden MD5-Kombinationen pro Sekunde testen kann.
Funktionsweise von bcrypt
bcrypt leitet sich von der bewusst rechenintensiven Schlüsselkonfigurationsphase - dem Key Setup - der Blowfish-Verschlüsselung ab.
Diese Phase, EksBlowfish (Expensive Key Schedule Blowfish) genannt, vermischt Passwort und Salt in den Blowfish-Unterschlüsseln über eine Anzahl von Runden, die 2 hoch dem gewählten Kostenfaktor entspricht. Anders als bei MD5 beansprucht dieser Schritt nichtsequentielle Speicherzugriffe, die die Parallelisierung auf GPU oder dedizierten Schaltkreisen stark einschränken: Jede Recheneinheit muss auf unvorhersehbare Weise auf eine große Tabelle zugreifen - ein Muster, das massiv parallele Hardware schlecht bewältigt. Der Salt, 128 Bit, ist direkt in den finalen Hash integriert, anders als bei APR1-MD5, das ihn in einem separaten Feld speichert.
Mit dem in htpasswd -B und den CaptainDNS-Generatoren standardmäßig verwendeten Kostenfaktor 10 liegt die Berechnung bei etwa 60 ms auf einem generischen Server. Ein Wechsel auf 12 bringt sie auf etwa 250 ms. Diese Zeit wird bei jeder authentifizierten Anfrage bezahlt, nicht nur bei der Kontoerstellung: Ein zu hoher Faktor macht sich sofort in der Nutzung bemerkbar.
Erweiterte Vergleichstabelle
| Format | Präfix | Salt | Rechenaufwand | GPU/ASIC-Resistenz | Serverkompatibilität | Einführung | Bewertung |
|---|---|---|---|---|---|---|---|
| crypt() DES | keines | 12 Bit (2 Zch.) | 25 DES-Runden, Passwort auf 8 Zch. gekürzt | Keine | Nur historisch | Ende der 1970er | Veraltet |
| SHA-1 | {SHA} | Keiner | 1 Iteration | Keine | Apache, nginx | 1990er Jahre | Vermeiden |
| APR1-MD5 | $apr1$ | 8 Zch. | 1.000 MD5-Iterationen | Gering | Apache, nginx, Traefik | Apache 1.3, 1998 | Maximale Kompatibilität |
| bcrypt | $2y$ | 128 Bit | 2^N Runden, einstellbar | Hoch | Apache 2.4+, aktuelles nginx, Traefik, Caddy | 1999 (USENIX) | Empfohlen |
Der Graben zwischen den letzten beiden Zeilen ist nicht nur eine Generationenfrage. APR1-MD5 hat einen 1998 ein für alle Mal festgelegten Aufwand. bcrypt kalibriert sich neu: Heute von einem Kostenfaktor 10 auf 12 zu wechseln, reproduziert für einen Angreifer eine relative Schwierigkeit, die mit der des Papiers von 1999 vergleichbar ist, obwohl die Hardware sich inzwischen vervielfacht hat.
Einen Hash offline verifizieren
Beide Formate lassen sich mit den Systemwerkzeugen ohne Online-Generator überprüfen:
# APR1-MD5, mit erzwungenem Salt zum Vergleichen
openssl passwd -apr1 -salt Xq7nD2mR SehrLangesPasswort
# bcrypt, Kostenfaktor 12
htpasswd -nbB -C 12 admin SehrLangesPasswort
Bei bcrypt funktioniert der direkte Vergleich zweier Hashes niemals: Der Salt ändert sich bei jeder Berechnung, selbst bei identischem Passwort und Kostenfaktor. Die einzige gültige Verifikation spielt die Berechnung mit dem bereits im gespeicherten Hash vorhandenen Salt erneut durch - genau das tut die crypt()-Funktion des Systems nativ zum Zeitpunkt der Authentifizierung.

Dateiberechtigungen: Den Passwortschutz vervollständigen
Basic Auth schützt den HTTP-Zugriff auf ein Verzeichnis. Sie schützt überhaupt nichts mehr, wenn die .htpasswd-Datei selbst falsch platziert oder auf Dateisystemebene schlecht geschützt ist.
Speicherort der .htpasswd-Datei: außerhalb des DocumentRoot
Die .htpasswd-Datei darf sich niemals in einem direkt vom Webserver ausgelieferten Verzeichnis befinden.
/var/www/html/ <- DocumentRoot, über HTTP ausgeliefert
/var/www/secrets/ <- außerhalb DocumentRoot, niemals ausgeliefert
.htpasswd
Eine versehentlich in /var/www/html/.htpasswd abgelegte Datei wird - sofern mit einem Punkt beginnende Dateien nicht explizit gesperrt sind - mit einer einfachen HTTP-Anfrage herunterladbar. Ein Angreifer erbeutet dann alle Hashes der Datei und greift sie offline an, in aller Ruhe, ohne dass der Kostenfaktor des Servers ihn im Geringsten bremst. Die Berechnung findet bei ihm statt, nicht bei Ihnen.
Empfohlene Berechtigungen (chmod, chown, umask)
Zwei Berechtigungsebenen zählen: die .htpasswd-Datei selbst und das geschützte Verzeichnis.
Für .htpasswd 640 anstreben (Lesen/Schreiben für den Eigentümer, Lesen für die Gruppe, nichts für Andere), mit einem Eigentümer, der zum Webserver-Benutzer passt - www-data unter Debian/Ubuntu, apache unter RHEL - oder zu dessen Gruppe:
chown root:www-data /var/www/secrets/.htpasswd
chmod 640 /var/www/secrets/.htpasswd
644 bleibt ein klassischer Fallstrick bei schnellen Fehlerbehebungen: Dieser Modus macht die Datei für alle Benutzer des Systems lesbar, nicht nur für den Apache- oder nginx-Prozess. Auf einem Shared-Hosting- oder Mehrbenutzerserver kann dann jedes lokale Konto die Hashes lesen. Derselbe Reflex gilt für das geschützte Verzeichnis: 750 genügt völlig, nur Eigentümer und Gruppe, nichts für Andere. 777 zu Debugging-Zwecken sieht man immer noch zu oft - und das hat noch nie etwas dauerhaft gelöst.
Ein Wort zu umask, das oft vergessen wird: Wenn die .htpasswd-Datei von einem Deployment-Skript statt manuell mit htpasswd neu erstellt wird, bestimmt der umask-Wert des schreibenden Prozesses ihre Standardberechtigungen. Ein zu freizügiges umask - 022 oder noch weniger restriktiv - kann bei jedem Deployment stillschweigend eine für alle lesbare Datei neu erstellen und damit das zuvor manuell gesetzte chmod 640 überschreiben. Die Berechtigungen nach jedem automatisierten Deployment zu prüfen, verhindert die Regression.
Letzte nützliche Erinnerung: Basic Auth schützt den HTTP-Zugriff, nicht den Dateisystemzugriff. Ein falsches chmod umgeht die gesamte Anwendungsschicht - stillschweigend, ohne dass irgendetwas in den HTTP-Logs darauf hinweist.
Bewährte Praktiken und zu vermeidende Fallstricke
Schlecht konfigurierte Basic Auth vermittelt ein falsches Sicherheitsgefühl. Ein paar einfache Regeln vermeiden die meisten blinden Flecken.
HTTPS ist nicht verhandelbar. RFC 7617 stellt klar: Das Basic-Schema verschlüsselt nichts. Ohne TLS wird das Base64-codierte Passwort von jedem im Klartext gelesen, der den Datenverkehr abfängt - in einem öffentlichen WLAN, über einen kompromittierten Proxy oder mit einem einfachen Paket-Sniffer-Tool. Basic Auth auf reinem HTTP zu aktivieren, kommt dem Aushängen des Passworts an einer Tafel gleich.
Niemals ein Anwendungs- oder persönliches Passwort für ein .htpasswd-Konto wiederverwenden. Diese Dateien werden oft weniger überwacht als die Hauptauthentifizierungssysteme, und ein zwischen zwei Systemen geteiltes Passwort vervielfacht die Angriffsfläche im Falle eines Lecks in einem der beiden.
Mit einer IP-Einschränkung kombinieren, wenn die Zielgruppe im Voraus bekannt ist. Unter Apache: Require ip 203.0.113.0/24 zusätzlich zu Require valid-user, über RequireAny. Unter nginx: allow und deny vor der auth_basic-Direktive deklariert. Zwei Schichten sind besser als eine für einen wirklich sensiblen Zugang.
Ein von einem ganzen Team gemeinsam genutztes Konto protokolliert nichts. Wenn sich drei Personen mit demselben Benutzernamen admin anmelden, lässt sich im Falle eines Vorfalls unmöglich nachvollziehen, wer was getan hat. Ein Konto pro Person kostet dreißig Sekunden mehr Konfiguration und ändert alles an dem Tag, an dem man verstehen muss, was passiert ist.
Das Realm - der in AuthName oder auth_basic deklarierte Text - ist nicht nur ein kosmetisches Detail. Eine vage Bezeichnung wie "Geschützter Bereich" vermeidet es, die Art des geschützten Dienstes preiszugeben, falls jemand unaufgefordert auf den Prompt stößt. Das Realm "Odoo Admin Panel" oder "Internes Grafana" zu nennen, gibt jedem, der zufällig URLs testet, kostenlos eine Information.
Ein Passwort in .htpasswd zu ändern, reicht nicht aus, um einen bereits authentifizierten Browser abzumelden. Die Anmeldedaten bleiben clientseitig gespeichert, solange der Browser keinen neuen 401 erhält - und Basic Auth hat keinen Abmeldemechanismus, um einen solchen zu provozieren. Zwei Methoden erzwingen dennoch einen neuen Prompt ohne clientseitiges Eingreifen: Das Ändern des Realms - des in AuthName oder auth_basic deklarierten Textes - erzeugt aus Sicht des Browsers einen separaten Bereich; dieser sendet die alten Anmeldedaten nicht mehr und fordert die Eingabe erneut an. Andernfalls bleibt das Schließen des Browsers oder das Öffnen der URL in einem privaten Browserfenster die einzige zuverlässige Möglichkeit, bereits gespeicherte Basic-Auth-Anmeldedaten zu löschen.
Standard-Zugriffslogs zeichnen das Passwort niemals im Klartext auf: Der Authorization-Header erscheint nicht im Standard-Logformat von Apache oder nginx. Dies ist ein sicheres Standardverhalten, das jedoch ausdrücklich überprüft werden sollte, wenn irgendwo in der Konfiguration ein benutzerdefiniertes Logformat hinzugefügt wurde. Ein falsch platziertes %i in einem Apache-LogFormat würde das Base64-codierte Benutzername/Passwort-Paar in eine Logdatei schreiben, die möglicherweise weniger geschützt ist als die .htpasswd-Datei selbst.
In einer Infrastruktur mit mehreren Umgebungen - Dev, Staging, Preproduction - wird die manuelle Verwaltung der .htpasswd-Dateien schnell zum Chaos: ein vergessenes Konto nach dem Ausscheiden eines Kollegen, ein Passwort, das immer noch in APR1-MD5 vorliegt, während der Rest auf bcrypt umgestellt wurde. Ein Ansible-Playbook oder ein Deployment-Skript, das die Datei aus einer einzigen Quelle neu generiert - z. B. einer Datei mit den autorisierten Konten -, verhindert das Auseinanderdriften zwischen Umgebungen und bietet einen einzigen Punkt, um einen Zugang zu entziehen.
Basic Auth bleibt ein HTTP-Schloss, kein Anwendungsauthentifizierungssystem. Kein richtiges Logout: Den Browser zu schließen oder die URL zu wechseln, bleibt die einzige clientseitige Möglichkeit, es loszuwerden. Keine Sperrung nach mehreren Fehlversuchen, keine automatische Passwortrotation. Für einen Zugang, der auf Dauer angelegt ist und mehrere Benutzer mit unterschiedlichen Rechten aufnehmen soll, ist so bald wie möglich eine Migration auf eine dedizierte Anwendungsschicht vorzusehen.
Empfohlener Aktionsplan
- Den betroffenen Server wählen: Apache (.htaccess/.htpasswd) oder nginx (auth_basic), je nach vorhandener Infrastruktur.
- Einen bcrypt-Hash für jedes Konto generieren - oder APR1-MD5 nur, wenn eine alte Kompatibilitätsanforderung dies erzwingt.
- Die .htpasswd-Datei außerhalb des Web-Roots platzieren, mit 640-Berechtigungen.
- Sicherstellen, dass die Website HTTPS verwendet, bevor Basic Auth aktiviert wird: niemals auf reinem HTTP.
- Mit einem echten Konto testen, dann die Anmeldedaten in einem Team-Passwortmanager dokumentieren, nicht in einer geteilten Textdatei.
Ihre .htpasswd-Hashes ohne Kommandozeile generieren
Kein apache2-utils-Paket installiert, kein SSH-Zugang oder einfach schneller vorankommen: Die beiden CaptainDNS-Generatoren erzeugen direkt die in .htpasswd einzufügende Zeile.
Generieren Sie Ihren .htpasswd-Hash
FAQ
Was ist eine .htaccess-Datei?
Eine Konfigurationsdatei, die von Apache in jedem Verzeichnis gelesen wird, in dem sie sich befindet - es sei denn, eine AllowOverride-None-Direktive hat sie auf Serverebene deaktiviert. Sie deklariert lokale Regeln, einschließlich Authentifizierung, ohne die globale VirtualHost-Konfiguration zu berühren. nginx liest sie niemals: Seine gesamte Konfiguration lebt in nginx.conf.
Wo soll die .htpasswd-Datei auf dem Server abgelegt werden?
Außerhalb des Web-Roots (DocumentRoot für Apache, root für nginx), zum Beispiel in /var/www/secrets/. Eine über HTTP zugängliche .htpasswd-Datei kann heruntergeladen und ihre Hashes offline angegriffen werden, ohne dass der bcrypt-Kostenfaktor den Angreifer im Geringsten bremst.
Ist .htpasswd heute noch sicher?
Ja, vorausgesetzt, man verwendet bcrypt (htpasswd -B) anstelle von APR1-MD5 oder dem alten crypt()-DES und platziert die Datei außerhalb des Web-Roots mit restriktiven Berechtigungen (640). Der Basic-Auth-Mechanismus selbst bleibt sicher, solange er hinter HTTPS läuft.
Wie sichert man den Zugriff auf ein Verzeichnis mit Apache oder nginx?
Unter Apache: eine .htpasswd-Datei und die Direktiven AuthType, AuthName, AuthUserFile, Require valid-user in einer .htaccess-Datei oder einem Directory-Block. Unter nginx: die Direktiven auth_basic und auth_basic_user_file in einem location-Block, mit demselben .htpasswd-Dateiformat. In beiden Fällen ist HTTPS unverzichtbar.
Ist APR1-MD5 im Jahr 2026 noch sicher?
Es bleibt für Abwärtskompatibilität akzeptabel, widersteht jedoch einem offline auf GPU-Hardware durchgeführten Angriff deutlich schlechter als bcrypt. Für eine neue .htpasswd-Datei bcrypt (htpasswd -B) bevorzugen, es sei denn, eine präzise technische Einschränkung erzwingt APR1-MD5.
Wie generiert man einen bcrypt-Hash für .htpasswd?
Mit htpasswd -B aus dem Paket apache2-utils oder httpd-tools auf der Kommandozeile oder über einen Online-Generator, wenn der Befehl nicht verfügbar ist. Das Ergebnis im Format $2y$ wird unabhängig vom verwendeten Webserver direkt in die .htpasswd-Datei eingefügt.
Was ist der Unterschied zwischen auth_basic (nginx) und AuthType Basic (Apache)?
Das zugrundeliegende HTTP-Protokoll ist identisch: Beide implementieren dasselbe Basic-Schema aus RFC 7617 und lesen dasselbe .htpasswd-Dateiformat. Der Unterschied liegt in der Deklaration: Apache akzeptiert .htaccess oder einen Directory-Block, nginx ausschließlich Direktiven in einem location-Block seiner zentralisierten Konfiguration.
Reicht Basic Auth aus, um ein sensibles Verzeichnis zu schützen?
Um anonymen Zugriff zu blockieren und eine erratene oder versehentlich indexierte URL zu sperren: ja. Für einen Zugang, der eine echte Kontoverwaltung, ein Logout, einen Session-Ablauf oder eine detaillierte Protokollierung erfordert: nein. Basic Auth verfügt über keinen dieser Mechanismen und muss dann durch eine Anwendungsauthentifizierung ergänzt oder ersetzt werden.
Funktioniert Basic Auth hinter einem CDN wie Cloudflare?
Ja, vorausgesetzt, die Anfrage erreicht tatsächlich den Ursprungsserver, ohne vor dem Authorization-Header aus dem Cache ausgeliefert zu werden. Ein Proxy-Eintrag lässt Basic Auth standardmäßig durch, aber eine falsch auf den geschützten Pfad ausgerichtete Cache-Regel kann eine bereits gecachte Antwort an einen nicht authentifizierten Client zurücksenden. Vor der Annahme, dass der Schutz zuverlässig ist, überprüfen, dass keine Cache-Regel auf das geschützte Verzeichnis angewendet wird.
Vergleichstabellen herunterladen
Assistenten können die JSON- oder CSV-Exporte unten nutzen, um die Kennzahlen weiterzugeben.
Glossar
- Basic Auth: HTTP-Authentifizierungsschema, das einen Benutzernamen und ein Passwort Base64-codiert im Authorization-Header überträgt. Definiert durch RFC 7617.
- Digest Auth: HTTP-Authentifizierungsschema, das das Passwort clientseitig hasht, bevor es übertragen wird, anstatt es im Klartext zu codieren. Definiert durch RFC 7616, in der Praxis sehr wenig genutzt.
- .htpasswd: Textdatei, die Konten im Format benutzername:hash auflistet und von Apache und nginx zur Überprüfung einer Basic-Authentifizierung gelesen wird.
- .htaccess: Apache-Konfigurationsdatei, die verzeichnisweise gelesen wird und einen Passwortschutz deklarieren kann, sofern AllowOverride dies erlaubt.
- Salt: Zufallswert, der dem Passwort vor dem Hashing hinzugefügt und im Klartext neben dem Hash gespeichert wird. Verhindert die Wiederverwendung einer vorberechneten Tabelle zwischen mehreren Konten oder Systemen.
- Kostenfaktor: bcrypt-Parameter, der die Anzahl der Rechenrunden als Zweierpotenz festlegt. Jede Erhöhung verdoppelt die erforderliche Rechenzeit.
- crypt(): Familie historischer Unix-Funktionen für das Passwort-Hashing, von der sowohl das ursprüngliche DES-Format als auch APR1-MD5 abstammen.
- Rainbow Table: Vorberechnete Tabelle, die Hashes wahrscheinlichen Passwörtern zuordnet und verwendet wird, um ein Passwort schnell aus seinem ungesalzenen Hash wiederherzustellen.
- AllowOverride: Apache-Direktive, die festlegt, welche Kategorien von Direktiven eine .htaccess-Datei ändern darf. AllowOverride None deaktiviert das Lesen der .htaccess vollständig.


