Skip to content
Terug naar blog
Beveiliging

Een .htpasswd-bestand maken: gids voor HTTP Basic Auth

Maak een .htpasswd-bestand met bcrypt of apr1, configureer HTTP Basic Auth op Apache, nginx, Docker en Kubernetes en beveilig het. Praktische gids 2026.

11 min leestijd

Een .htpasswd-bestand maken: gids voor HTTP Basic Auth

Een .htpasswd-bestand is de opslag van inloggegevens aan serverzijde voor HTTP Basic Authentication: een platte-tekstbestand waarin elke regel één paar username:hash bevat. Om een .htpasswd-bestand te maken genereer je die regel met de hash en sla je hem op een plek op waar je webserver hem kan lezen. Er zijn drie manieren:

  • Het commando htpasswd (uit apache2-utils / httpd-tools) — de standaardtool.
  • openssl passwd — bijna overal al geïnstalleerd, geen extra pakket nodig.
  • In je browser — gebruik de htpasswd generator om lokaal een regel aan te maken, zonder installatie en zonder dat er iets over het netwerk gaat.

Deze gids gaat verder dan de one-liner. We behandelen hoe de Basic Auth-handshake echt werkt, hoe je het bestand op drie manieren maakt, welke van de vijf hash-formaten je kiest, hoe je het inhaakt op Apache, nginx, Docker, Kubernetes, Caddy en Traefik, en hoe je alles beveiligt zodat je geen bestand met inloggegevens uitlevert dat iedereen kan downloaden.

Wat is een .htpasswd-bestand?

Elke regel in een .htpasswd-bestand bevat de inloggegevens van één gebruiker als een paar gescheiden door een dubbele punt. De gebruikersnaam wordt opgeslagen zoals hij is; het wachtwoord wordt alleen opgeslagen als eenrichtings-hash, zodat de platte tekst nooit op schijf belandt. De anatomie van één bcrypt-regel ziet er zo uit:

admin    :    $2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
│             │
└─ username   └─ hash (algorithm prefix $2y$ + cost + salt + digest)

De gebruikersnaam komt eerst, dan één :, dan de hash. Een gebruikersnaam mag tot 255 bytes lang zijn en mag nooit een dubbele punt bevatten, want de dubbele punt is het scheidingsteken tussen velden. De hash draagt zijn eigen algoritmemarkering als prefix ($2y$ voor bcrypt, $apr1$ voor Apache MD5, {SHA} voor SHA-1), zodat de server zonder extra configuratie weet hoe hij hem moet verifiëren.

Voor meerdere gebruikers voeg je per gebruiker één regel toe:

admin:$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
alice:$2y$10$3bQ8xY7tLp2mZ0xW5cR4fO9vK1jH6sD2nG8aQ5wE3rT7uI4oP1cm
bob:$apr1$mZ0xW5cR$4fK1jH6sD2nG8aQ5wE3rT2

Algoritmes mogen in hetzelfde bestand door elkaar staan. De server leest elke regel, herkent het formaat aan de prefix en verifieert tegen wat er ook gebruikt is. Hier bestaan twee bcrypt-gebruikers en één apr1-gebruiker probleemloos naast elkaar.

.htpasswd vs .htaccess

Deze twee worden voortdurend verward omdat ze op Apache samen reizen, maar ze doen verschillend werk. .htaccess is het configuratiebestand per map van Apache. Het bevat directives — waaronder de directives die Basic Auth inschakelen en naar je opslag van inloggegevens wijzen. .htpasswd is de database met inloggegevens — alleen de regels username:hash, geen configuratie.

Kort gezegd: .htaccess bepaalt dat een map een login vereist en waar de gebruikerslijst staat; .htpasswd is die gebruikerslijst. nginx gebruikt helemaal geen .htaccess — de Basic Auth-configuratie staat in het server- of location-blok van de hoofdconfiguratie, maar leest hetzelfde .htpasswd-formaat voor inloggegevens.

Hoe HTTP Basic Authentication werkt

HTTP Basic Authentication is een challenge-response-handshake die in de HTTP-spec is ingebouwd, en als je de drie stappen begrijpt, wordt elke latere stap bij het oplossen van problemen vanzelfsprekend:

  1. De client vraagt een beveiligde resource op zonder inloggegevens.
  2. De server antwoordt met 401 Unauthorized en stuurt een header WWW-Authenticate: Basic realm="..." mee — dat is de challenge.
  3. De client herhaalt het verzoek met een header Authorization: Basic <base64(user:password)>. Als de inloggegevens overeenkomen met een regel in het .htpasswd-bestand, geeft de server de resource terug.

Dat is het hele protocol. Geen inlogformulier, geen session-cookie, geen token. Elk volgend verzoek draagt dezelfde header.

De 401 / WWW-Authenticate-challenge

De header WWW-Authenticate doet twee dingen. De Basic-token vertelt de client welk schema hij moet gebruiken, en de realm-string benoemt de beveiligde ruimte. Browsers tonen de realm-tekst in het inlogvenster (“De site zegt: …”) en gebruiken hem als cache-sleutel: inloggegevens die voor één realm zijn ingevoerd, worden hergebruikt voor andere URL’s in dezelfde realm, zodat de gebruiker niet op elke pagina opnieuw wordt gevraagd.

Hier is de ruwe uitwisseling, vastgelegd met curl -i:

$ curl -i https://example.com/admin/
HTTP/2 401
www-authenticate: Basic realm="Restricted Area"

$ curl -i -u admin:s3cret https://example.com/admin/
HTTP/2 200

De header Authorization: Basic

De inloggegevens die de client verstuurt zijn base64(username:password). Dit is het allerbelangrijkste beveiligingsfeit over Basic Auth: base64 is encodering, geen encryptie. Het is door iedereen volledig omkeerbaar, dus de inloggegevens reizen feitelijk als platte tekst. Je kunt de heen-en-terugweg zelf zien:

# Encode the credential the way a browser does
printf 'admin:s3cret' | base64
# → YWRtaW46czNjcmV0

# Anyone who captures the header can decode it instantly
printf 'YWRtaW46czNjcmV0' | base64 -d
# → admin:s3cret

Die omkeerbaarheid is de reden dat Basic Auth over HTTPS moet draaien — zonder TLS is het wachtwoord leesbaar voor iedereen op het netwerkpad. Wil je die header met de hand opbouwen of inspecteren, dan voert de Base64 encoder/decoder dezelfde transformatie user:password uit in de browser.

Een .htpasswd-bestand maken

Er zijn drie praktische manieren om het bestand te maken. Kies op basis van wat er geïnstalleerd is en waar je het wachtwoord wilt laten staan.

Met het commando htpasswd

De binary htpasswd zit in het hulppakket van Apache. Installeer hem eerst:

# Debian / Ubuntu
sudo apt install apache2-utils

# RHEL / CentOS / Fedora
sudo yum install httpd-tools

Maak het bestand en de eerste gebruiker aan. De vlag -c betekent create, en die zal een bestaand bestand overschrijven — gebruik hem alleen de allereerste keer:

htpasswd -c /etc/nginx/.htpasswd admin
# prompts twice for the password, then writes the file

Om meer gebruikers toe te voegen laat je de -c weg, zodat je toevoegt in plaats van overschrijft:

htpasswd /etc/nginx/.htpasswd alice

Om bcrypt af te dwingen in plaats van de platformstandaard voeg je -B toe. Om de regel naar stdout te printen zonder een bestand aan te raken — handig om door te sluizen naar een config of een Dockerfile — combineer je -b (wachtwoord op de commandoregel) en -n (geen bestand):

htpasswd -Bbn admin 's3cret'
# → admin:$2y$10$N9qo8uLOickgx2ZMRZoMye...

De vlaggen die je echt gaat gebruiken:

VlagBetekenis
-cEen nieuw bestand maken (overschrijft als het bestaat) — alleen eerste gebruiker
-Bbcrypt gebruiken
-bHet wachtwoord als command-line-argument meegeven (geen prompt)
-nNaar stdout printen in plaats van een bestand te schrijven
-DDe genoemde gebruiker uit het bestand verwijderen

Eén kanttekening bij -b: het wachtwoord belandt in je shell-historie. Voor eenmalige productie-inloggegevens kun je beter de prompt-vorm of de browseroptie hieronder gebruiken.

Zonder apache2-utils — met OpenSSL

Geen binary htpasswd? OpenSSL staat op vrijwel elk systeem en kan rechtstreeks een apr1-hash produceren. Wikkel het in printf om een complete regel op te bouwen:

printf "admin:$(openssl passwd -apr1 's3cret')\n" >> /etc/nginx/.htpasswd
# admin:$apr1$k3l4Hj9.$qN8vY7tLp2mZ0xW5cR4f.

Het apr1-formaat is overdraagbaar tussen zowel Apache als nginx, wat dit de meest afhankelijkheidsvrije route maakt op een minimale machine.

In de browser — geen installatie, geen lek

Wil je geen pakket installeren, of liever geen productiewachtwoord typen in een shell waar het in ~/.bash_history belandt, genereer de regel dan aan clientzijde. De htpasswd generator berekent bcrypt-, apr1- en SHA-1-hashes volledig in je browser, geeft je een kant-en-klare regel user:hash plus een bijpassend serverconfiguratieblok, en verstuurt nooit iets. Maak terwijl je daar bent meteen een sterk, uniek wachtwoord aan met de willekeurig wachtwoord generator in plaats van er een te hergebruiken.

htpasswd-wachtwoordformaten vergeleken

Het commando htpasswd kan vijf formaten uitvoeren, en ze zijn niet gelijkwaardig. Deze tabel is de snelle referentie om er een te kiezen:

FormaatPrefixSaltedSterkteGebruik het voor
bcrypt$2y$JaSterkstApache, Docker Registry, Caddy, Traefik
apr1 (Apache MD5)$apr1$JaGemiddeldnginx (overdraagbaar, veilige standaard)
SHA-1{SHA}NeeZwakAlleen legacy-compatibiliteit
crypt (DES)(geen)Ja (2 tekens)Zeer zwakNiet gebruiken
plain(geen)NeeGeenAlleen lokaal testen

Een paar opmerkingen die niet in een tabelcel passen. bcrypt gebruikt een willekeurige salt van 16 bytes en een adaptieve cost-factor (standaard 10, moderne aanbeveling 12), zodat identieke wachtwoorden verschillende hashes opleveren en de werkfactor meeschaalt met de hardware. De ene eigenaardigheid: bcrypt kapt het wachtwoord af op 72 bytes — alles wat langer is wordt stilzwijgend genegeerd. apr1 draait 1.000 rondes van salted MD5; veel zwakker dan bcrypt, maar natief geïmplementeerd door zowel Apache als nginx, wat de reden is dat het de overdraagbare keuze is. SHA-1 is unsalted, dus identieke wachtwoorden leveren identieke digests op en rainbow tables zijn van toepassing — houd het alleen voor legacy-systemen. crypt en plain bestaan om historische en testredenen; geen van beide hoort in productie thuis.

De prefixen $2a$ / $2b$ / $2y$

Je ziet bcrypt-hashes beginnen met $2a$, $2b$ of $2y$. Het is hetzelfde algoritme en ze leveren gelijkwaardige, uitwisselbare hashes op; de versieletters zijn overblijfselen van historische bugfixes in de manier waarop bepaalde libraries omgingen met high-bit-tekens en stringlengte. Apache’s htpasswd levert $2y$, en Caddy, Traefik en Docker Registry verifiëren het allemaal correct.

Wil je de diepere vergelijking van bcrypt met moderne alternatieven, dan behandelt de gids bcrypt vs Argon2 vs scrypt hoe deze algoritmes voor wachtwoord-hashing verschillen in cost, geheugenhardheid en dreigingsmodel.

Basic Auth configureren op je server

Een bestand met inloggegevens doet op zichzelf niets — je server moet de opdracht krijgen om het te vereisen. Hier zijn zes platformen.

Apache (.htaccess)

Zet dit in een .htaccess-bestand in de map die je wilt beveiligen (of in een <Directory>-blok in je vhost):

AuthType Basic
AuthName "Restricted Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

AuthName is de realm-string die de browser toont; AuthUserFile is het absolute pad naar je bestand met inloggegevens; Require valid-user accepteert elke gebruiker die erin staat.

nginx (auth_basic)

nginx zet de configuratie in een location- of server-blok — er is geen .htaccess:

location /admin/ {
    auth_basic           "Restricted Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

Herlaad met nginx -s reload. Gebruik hier het apr1-formaat. nginx delegeert de verificatie van bcrypt aan de systeem-crypt(), wat op veel builds mislukt (daarover meer bij het oplossen van problemen), terwijl apr1 intern op elk platform wordt geverifieerd.

Docker Registry & Kubernetes ingress-nginx

De htpasswd-backend van een private Docker Registry accepteert alleen bcrypt. Genereer de regel, mount hem en wijs de registry erop:

# Generate a bcrypt entry into a file
htpasswd -Bbn admin 's3cret' > auth/htpasswd

# Run the registry with that file
docker run -d -p 5000:5000 \
  -v "$(pwd)/auth:/auth" \
  -e REGISTRY_AUTH=htpasswd \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry:2

Voor Kubernetes ingress-nginx sla je het bestand op als Secret en verwijs je ernaar met annotations:

kubectl create secret generic basic-auth --from-file=auth=./auth/htpasswd
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"

Let op: de Secret-sleutel moet auth heten — ingress-nginx zoekt precies die sleutel.

Caddy & Traefik

Beide verwachten bcrypt-hashes. Caddy gebruikt de directive basic_auth (plak de bcrypt-hash, niet de platte tekst):

example.com {
    basic_auth /admin/* {
        admin $2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
    }
}

Traefik gebruikt een basicauth-middleware, met paren user:bcrypt-hash (escape elke $ voor je configuratieformaat):

http:
  middlewares:
    admin-auth:
      basicAuth:
        users:
          - "admin:$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"

Zodra een endpoint beveiligd is, controleer je vanaf de commandoregel of het werkt. De cURL command builder stelt het verzoek -u user:pass voor je samen, zodat je zowel de 401 als de geauthenticeerde 200 kunt bevestigen.

Aanbevolen aanpak voor beveiliging

Basic Auth is eenvoudig, waardoor de paar manieren om het verkeerd te gebruiken makkelijk te herkennen zijn.

  • Serveer altijd over HTTPS. De inloggegevens zijn omkeerbare base64, dus plat HTTP stelt het wachtwoord bloot op de lijn. Beëindig TLS vóór elk beveiligd endpoint, zonder uitzondering.
  • Sla het bestand op buiten de web root. Als .htpasswd in een geserveerde map staat, kan een verkeerde configuratie iemand het laten downloaden. Houd het op een plek als /etc/nginx/.htpasswd, zet chmod 640 en laat het eigendom zijn van de gebruiker van de webserver (www-data, nginx) zodat de server het kan lezen en andere accounts niet.
  • Gebruik sterke, unieke wachtwoorden. Elk account hoort zijn eigen wachtwoord met hoge entropie te krijgen van de willekeurig wachtwoord generator, nooit hergebruikt. Wil je begrijpen wat “sterk genoeg” in bits betekent, dan ontleedt de uitleg over wachtwoord-entropie de wiskunde.
  • Ken de grenzen. Basic Auth heeft geen logout en geen session: de browser cachet de inloggegevens per realm tot je hem sluit, en ze worden bij elk verzoek opnieuw verstuurd. Voor een bredere checklist over hashing, headers en validatie, zie onze aanbevolen aanpak voor webbeveiliging.

Veelvoorkomende fouten oplossen

nginx: crypt_r() failed (22: Invalid argument)

Dit is de allervaakst voorkomende Basic Auth-fout van nginx, en hij betekent altijd hetzelfde: nginx probeerde een bcrypt-hash ($2y$) te verifiëren op een libc die het Blowfish-schema niet bevat — meestal de musl van Alpine of een oudere glibc. De oplossing is de regel opnieuw te genereren als apr1, die nginx intern op elk platform verifieert:

printf "admin:$(openssl passwd -apr1 's3cret')\n" > /etc/nginx/.htpasswd
nginx -s reload

Overstappen op een base image waarvan de libc bcrypt ondersteunt werkt ook, maar apr1 is de eenvoudiger, overdraagbare oplossing.

401 zelfs met het juiste wachtwoord

Als je zeker weet dat het wachtwoord klopt maar toch een 401 krijgt, loop dan deze checklist op volgorde door:

  1. Bestandspad. Bevestig dat AuthUserFile / auth_basic_user_file naar het werkelijke bestand wijst (absoluut pad, geen typefouten).
  2. Rechten. De gebruiker van de webserver moet het bestand kunnen lezen. Controleer met sudo -u www-data cat /etc/nginx/.htpasswd.
  3. Regeleindes / encodering. Een bestand dat op Windows is bewerkt kan \r-tekens dragen die de hash beschadigen. Draai file .htpasswd en dos2unix het indien nodig.
  4. Verouderde browsercache. De browser cachet inloggegevens per realm. Test in een privé-/incognitovenster om een onthouden oud wachtwoord uit te sluiten.
  5. Hash komt niet overeen. Verifieer dat de opgeslagen hash echt bij het wachtwoord past — plak beide in de verify-modus van de htpasswd generator om het te bevestigen voordat je de config de schuld geeft.

Wanneer je Basic Auth NIET moet gebruiken

Basic Auth is het juiste gereedschap voor een smalle set taken: een staging-site, een intern adminpad, een endpoint voor CI-artefacten, een dashboard met metrieken, een private registry. Het is afhankelijkheidsvrij en in twee minuten opgezet.

Het is het verkeerde gereedschap voor een product-login. Er is geen logout, geen wachtwoordreset, geen rate limiting, geen account-lockout en geen MFA. Inloggegevens worden bij elk verzoek opnieuw verstuurd en door de browser gecachet tot hij sluit. Voor alles waar gebruikers op inloggen kun je beter sessions, OAuth of OIDC pakken. Zolang je die grens kent, blijft Basic Auth precies daar nuttig waar het hoort.

FAQ

Wat bevat één regel in een .htpasswd-bestand eigenlijk?

Een paar username:hash gescheiden door een dubbele punt. De hash begint met een algoritmeprefix ($2y$ voor bcrypt, $apr1$ voor Apache MD5, {SHA} voor SHA-1), gevolgd door de salt en de digest. Het wachtwoord in platte tekst verschijnt nooit in het bestand.

Wat is het verschil tussen .htpasswd en .htaccess?

.htaccess is het configuratiebestand per map van Apache — het schakelt Basic Auth in en wijst naar de opslag van inloggegevens. .htpasswd is die opslag van inloggegevens en bevat de regels username:hash. nginx gebruikt het .htpasswd-formaat maar configureert de auth in zijn server-/location-blokken, niet in .htaccess.

Hoe voeg ik een gebruiker toe aan, wijzig of verwijder ik er een in een .htpasswd-bestand?

Om een gebruiker toe te voegen of te wijzigen draai je htpasswd /path/.htpasswd username zonder -c — als de gebruiker bestaat, wordt zijn hash bijgewerkt. Om er een te verwijderen draai je htpasswd -D /path/.htpasswd username. Gebruik -c alleen voor de allereerste gebruiker, want het overschrijft het hele bestand.

Hoe onthoudt de browser de Basic Auth-inloggegevens, en hoe loggen gebruikers uit?

De browser cachet de inloggegevens met de realm als sleutel en verstuurt ze automatisch opnieuw bij elk passend verzoek. Er is geen standaard logout: de enige manieren om ze te wissen zijn de browser sluiten of zijn cache leegmaken. Die ontbrekende logout is een van de redenen dat Basic Auth niet geschikt is voor productauthenticatie.

Kan ik hetzelfde .htpasswd-bestand gebruiken voor zowel Apache als nginx?

Ja, zolang het hash-formaat op beide wordt ondersteund. apr1 (Apache MD5) wordt overal natief geverifieerd door Apache en nginx, dus dat is de veiligste gedeelde keuze. bcrypt werkt op Apache, maar op nginx hangt het af van de systeem-crypt(), die op Alpine/musl-builds mislukt.

Is HTTP Basic Authentication in 2026 nog relevant?

Ja. Als een lichtgewicht poort boven op HTTPS — interne tools, staging-omgevingen, private registries, monitoring-endpoints — is het nog steeds praktisch en afhankelijkheidsvrij. Verwar het alleen niet met productauthenticatie voor gebruikers, die sessions, resets, rate limiting en MFA nodig heeft die Basic Auth niet kan bieden.

Gecontroleerd door het Go Tools-team: elk commando, elk configuratieblok en elk hash-formaat in deze gids is gecontroleerd aan de hand van de referentie-output van Apache htpasswd (apache2-utils) en OpenSSL.

Tags: htpasswd basic-auth http-authentication nginx apache bcrypt security

Gerelateerde artikelen

Alle artikelen bekijken