Protéger un dossier avec .htaccess : Basic Auth, APR1-MD5 et bcrypt
Par CaptainDNS
Publié le 12 août 2026

- Basic Auth HTTP protège un dossier via un fichier .htpasswd (Apache) ou auth_basic_user_file (nginx), en quelques minutes de configuration.
- Le mot de passe n'est jamais stocké en clair : il est haché, en APR1-MD5 (historique, compatible partout) ou en bcrypt (recommandé, coût de calcul réglable).
- Sans HTTPS, Basic Auth transite en clair : à activer uniquement derrière TLS.
- Le fichier .htpasswd doit vivre hors de la racine web, jamais dans un dossier téléchargeable.
- Générez vos hash sans ligne de commande avec les générateurs APR1-MD5 et bcrypt de CaptainDNS.
Un serveur de préproduction qui traîne indexé par Google. Un tableau de bord de monitoring accessible à quiconque devine l'URL. Une documentation technique publiée par erreur en clair sur le web. Trois scénarios ordinaires, et une seule solution de premier secours dans les trois cas : Basic Auth.
Basic Auth existe depuis les débuts du web et n'a presque pas changé depuis. Elle protège un dossier ou un fichier avec un simple couple identifiant/mot de passe, sans base de données, sans session, sans dépendance applicative. Sur Apache, un fichier .htaccess et un fichier .htpasswd suffisent. Sur nginx, deux directives, auth_basic et auth_basic_user_file. Cinq minutes de configuration, et l'accès est fermé.
Ce guide détaille la configuration concrète sous Apache et sous nginx, avec les pièges qui font perdre une heure : AllowOverride mal réglé, chemin du fichier oublié, service jamais rechargé. Il explique aussi ce qui se passe réellement dans un fichier .htpasswd. Pourquoi Apache a inventé son propre format de hachage, APR1-MD5. Pourquoi bcrypt l'a largement remplacé. Et comment choisir entre les deux en connaissance de cause, plutôt que par habitude ou par copier-coller d'un tutoriel de 2015.
Ce contenu s'adresse aux administrateurs système, aux DevOps et aux développeurs backend qui gèrent leur propre hébergement, et veulent fermer un accès sans attendre la mise en place d'un SSO ou d'un VPN.
Pourquoi protéger un dossier avec un mot de passe sur un serveur web ?
Basic Auth ferme l'accès HTTP à un dossier en quelques minutes, sans code applicatif ni base de données à gérer.
Les cas d'usage reviennent tout le temps : un environnement de préproduction sur un sous-domaine du type preprod.captaindns.com, un back-office interne sans authentification propre, une documentation technique qui n'a rien à faire en public, un outil de monitoring exposé par erreur lors d'un déploiement pressé. Dans tous ces cas, personne n'a le temps ni le besoin de coder un système de comptes complet. Une couche HTTP suffit largement.
Le piège classique : croire qu'un dossier non lié depuis le site reste invisible. Un robots.txt qui exclut /preprod/ n'empêche que le crawl des moteurs respectueux du standard, pas l'accès direct. Un scanner automatisé, un lien partagé par erreur dans un ticket, une entrée de log qui fuite quelque part, et l'URL circule. Des outils entiers existent pour scanner systématiquement les chemins courants (staging, dev, admin, backup) sur des plages IP complètes. Rien de paranoïaque là-dedans : c'est du bruit de fond permanent sur Internet.
Une inspection rapide des logs d'accès d'un serveur exposé sur Internet suffit à s'en convaincre. Des requêtes vers /admin/, /wp-admin/, /.env, /backup.zip ou /phpinfo.php arrivent en continu, minute après minute, bien avant qu'un humain n'ait eu le temps de trouver l'URL par un autre moyen. Ces scans ne ciblent personne en particulier : ils balaient des plages IP entières à la recherche de chemins connus pour être mal protégés. Un dossier de préproduction au nom prévisible, /staging/ ou /preprod/, finit tôt ou tard dans ces logs.
Basic Auth HTTP n'est pas une authentification applicative complète. Pas de logout propre : le navigateur garde les identifiants en mémoire tant qu'il n'est pas fermé. Pas de limitation du nombre de tentatives. Pas de session avec expiration, pas de gestion fine des rôles. C'est un verrou sur la porte, pas un contrôle d'accès élaboré. Pour un accès qui exige des rôles différenciés ou une traçabilité fine, il faut une couche applicative dédiée. Pour fermer vite un accès sensible en attendant mieux, ou en complément d'une protection déjà en place, Basic Auth fait très bien le travail.
Les alternatives existent, et sont souvent plus robustes sur le papier. Un VPN filtre l'accès réseau avant même que la moindre requête HTTP n'atteigne le serveur. Un SSO d'entreprise, Okta, Google Workspace ou Azure AD, centralise les comptes et la journalisation des connexions. Un reverse proxy avec OAuth2, oauth2-proxy ou Authelia par exemple, ajoute une vraie session applicative avec expiration et déconnexion propre. Mais ces solutions demandent une infrastructure à part entière : un serveur VPN à maintenir, un fournisseur d'identité à intégrer, un proxy supplémentaire à déployer et à surveiller. Basic Auth se configure en quelques minutes avec ce qui tourne déjà sur le serveur, sans dépendance externe ni compte à provisionner ailleurs. Pas la plus élégante sur le papier. Mais elle ferme réellement un accès avant la fin de la journée.
Basic Auth et Digest Auth : comment fonctionne l'authentification HTTP ?
Le protocole HTTP inclut depuis toujours un mécanisme d'authentification fondé sur des en-têtes, sans cookie ni session applicative.
L'échange se déroule en deux temps. Le navigateur demande une ressource protégée, le serveur répond 401 Unauthorized avec un en-tête WWW-Authenticate qui précise le schéma attendu et le nom de la zone protégée, le realm. Le navigateur affiche alors sa boîte de dialogue native, l'utilisateur saisit ses identifiants, et le navigateur rejoue la requête avec un en-tête Authorization. Tant que la session du navigateur reste ouverte, cet en-tête repart automatiquement à chaque requête vers le même realm, sans nouvelle saisie.
Basic Auth : le mécanisme standard (RFC 7617)
Basic Auth encode l'identifiant et le mot de passe en base64, sans aucun chiffrement. Le format de l'en-tête Authorization est Basic suivi de base64(identifiant:motdepasse).
Point à retenir tout de suite : base64 n'est pas du chiffrement. C'est un simple encodage réversible, décodable par n'importe qui en une commande.
$ echo -n "admin:motdepasse" | base64
YWRtaW46bW90ZGVwYXNzZQ==
La RFC 7617 le précise sans détour : le schéma Basic ne fournit aucune protection de confidentialité pour les identifiants transmis, et son usage sur une connexion non chiffrée les expose à quiconque intercepte le trafic. C'est pour cela que Basic Auth ne devrait jamais tourner sur du HTTP nu.
Ce dialogue se voit directement avec curl. Une première requête sans identifiants reçoit le refus :
$ curl -i https://preprod.captaindns.com/
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Zone reservee"
Avec les identifiants, la requête passe :
$ curl -i -u admin:motdepasse https://preprod.captaindns.com/
HTTP/1.1 200 OK
L'option -u de curl construit elle-même l'en-tête Authorization encodé en base64. C'est exactement ce que fait un navigateur en coulisses après la saisie dans sa boîte de dialogue.
Digest Auth : pourquoi il a quasiment disparu
Digest Auth hache le mot de passe côté client avant envoi, plutôt que de le transmettre en clair encodé.
Le calcul combine l'identifiant, le realm, le mot de passe et un nonce, une valeur aléatoire à usage unique fournie par le serveur, via une fonction de hachage : historiquement MD5, avec SHA-256 disponible depuis la RFC 7616. En théorie, cette approche protège les identifiants même sans HTTPS, puisque le mot de passe lui-même ne transite jamais sur le réseau.
L'échange s'appuie sur une réponse serveur nettement plus riche que Basic Auth :
WWW-Authenticate: Digest realm="Zone reservee",
qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
Le client calcule alors HA1 = MD5(identifiant:realm:motdepasse), HA2 = MD5(méthode:URI), puis une réponse finale MD5(HA1:nonce:nc:cnonce:qop:HA2). Cette complexité de calcul, répétée à chaque requête, est précisément ce qui a poussé la majorité des implémentations à préférer Basic Auth derrière TLS plutôt que Digest Auth sans TLS.
En pratique, Digest Auth a quasiment disparu. Sa mise en œuvre est plus complexe côté client : gestion du nonce, du compteur de requêtes nc, du cnonce. Le support reste inégal sur les cas avancés, et il pose des problèmes derrière certains proxys et load balancers qui n'attendent pas ce genre de dialogue. Surtout, la généralisation de HTTPS a rendu son principal avantage caduc. Protéger un mot de passe en transit n'a plus grand intérêt quand toute la connexion est déjà chiffrée de bout en bout. Apache (mod_auth_digest) et nginx via des modules tiers le supportent encore, mais presque personne ne l'utilise plus. La suite de ce guide se concentre sur Basic Auth, la norme de fait derrière HTTPS.
Petite histoire du hachage de mots de passe sur le web
Le format de hachage utilisé par .htpasswd a une histoire qui remonte aux débuts d'Unix, bien avant l'existence du web.
crypt() Unix et la limite des 8 caractères DES
La fonction crypt() d'Unix, conçue à la fin des années 1970 par Robert Morris pour la septième édition d'Unix, chiffre un mot de passe avec une variante du DES (Data Encryption Standard) répétée 25 fois. Un sel de 12 bits, soit 4 096 valeurs possibles, encodé sur 2 caractères, empêche la réutilisation directe d'une table pré-calculée entre systèmes.
Problème : DES travaille sur des blocs de 56 bits utiles, ce qui limite le mot de passe pris en compte à 8 caractères. Le neuvième caractère et les suivants sont ignorés, purement et simplement. Un mot de passe de 20 caractères se comporte exactement comme ses 8 premiers.
À la fin des années 1970, 25 itérations de DES représentaient un coût de calcul réel. Dans les années 1990, la puissance des processeurs grand public avait été multipliée par plusieurs ordres de grandeur. Cette protection était déjà dépassée face à une attaque par force brute menée depuis un simple PC.
Vérifier ce comportement ne demande qu'une commande, toujours disponible via openssl :
$ openssl passwd -crypt -salt ab motdepasseTresLong
abXXXXXXXXXXXX
Remplacer motdepasseTresLong par n'importe quelle chaîne de plus de 8 caractères produit rigoureusement le même hash tant que les 8 premiers caractères restent identiques. La preuve la plus simple qu'un algorithme conçu en 1979 n'a plus sa place dans un fichier .htpasswd créé aujourd'hui.
Pourquoi Apache a créé APR1-MD5 avec la version 1.3 ?
Le problème d'Apache n'était pas tant la faiblesse de crypt() que sa portabilité. L'implémentation de crypt() diffère d'un système à l'autre : la glibc Linux, les BSD et Windows ne produisent pas les mêmes hash pour les mêmes entrées. Un fichier .htpasswd généré sur une machine Linux pouvait devenir illisible une fois déployé sur un serveur BSD.
La solution est venue de FreeBSD, où Poul-Henning Kamp avait conçu en 1994 un format de hachage fondé sur MD5, indépendant de l'implémentation crypt() du système hôte. Apache a repris ce principe pour sa commande htpasswd, sous le nom apr1, disponible via l'option -m depuis Apache 1.3 en 1998. Résultat : le même hash, produit et vérifié à l'identique quel que soit l'OS du serveur.
APR1-MD5 applique 1 000 itérations de MD5 sur le mot de passe et un sel de 8 caractères, avec un encodage final sur l'alphabet base64 propre au format crypt. Face à un MD5 nu, une seule itération, le gain est réel : mille fois plus de calcul pour tester un mot de passe. Mais MD5 reste une fonction rapide, conçue à l'origine pour l'intégrité de données et non pour résister à des tentatives massives. Un GPU moderne calcule des millions d'itérations MD5 par seconde. Résultat, APR1-MD5 reste vulnérable aux attaques par dictionnaire sur des mots de passe faibles ou moyens.
L'arrivée de bcrypt, Provos et Mazières, 1999
bcrypt naît un an après l'adoption d'APR1-MD5 par Apache, avec un changement d'approche radical : plutôt que de fixer un nombre d'itérations, autant le rendre réglable dans le temps.
Niels Provos et David Mazières publient en 1999 « A Future-Adaptable Password Scheme » à la conférence USENIX. Leur constat : toute fonction de hachage à coût fixe finit rattrapée par le matériel, un jour ou l'autre. Leur réponse, bcrypt, dérive de la phase d'initialisation de clé du chiffrement Blowfish, conçu par Bruce Schneier en 1993, volontairement coûteuse en mémoire et en calcul. Le nombre de tours se règle via un facteur de coût, exprimé comme une puissance de deux : chaque incrément double le travail exigé de l'attaquant comme du serveur.
Cette conception change la donne. Un algorithme à itérations fixes doit être remplacé quand il devient trop rapide à calculer pour le matériel de l'époque. bcrypt, lui, s'ajuste : on augmente le facteur de coût, et le hash suit le rythme du progrès matériel sans changer d'algorithme ni casser la compatibilité des hash déjà stockés.
htpasswd -B produit du bcrypt depuis Apache 2.4, sorti en 2012, et c'est devenu le standard de facto pour .htpasswd comme pour le stockage de mots de passe en général. Sur les projets récents, Argon2, vainqueur de la Password Hashing Competition de 2015, le concurrence désormais, notamment pour sa résistance renforcée aux circuits dédiés (ASIC). Argon2 reste hors du périmètre de .htpasswd : ni Apache ni nginx ne le prennent en charge nativement pour l'authentification HTTP.
Le papier original de 1999 recommandait un facteur de coût par défaut de 6, jugé raisonnable pour le matériel de l'époque. La valeur par défaut d'htpasswd -B est aujourd'hui 10, et grimper à 12 pour un accès sensible reste courant. C'est exactement le mécanisme que Provos et Mazières avaient anticipé : le chiffre lui-même n'a aucune importance, seule compte la possibilité de l'augmenter sans changer d'algorithme ni casser les hash déjà stockés.
Configurer la protection par mot de passe avec Apache (.htaccess et .htpasswd)
Sous Apache, deux fichiers suffisent : .htpasswd pour les comptes, .htaccess ou un bloc <Directory> pour la déclaration.
Créer le fichier .htpasswd
La commande htpasswd, fournie par le paquet apache2-utils sur Debian/Ubuntu ou httpd-tools sur RHEL/CentOS, gère la création et la mise à jour du fichier.
# Créer le fichier et ajouter un premier compte, en bcrypt
htpasswd -c -B /var/www/secrets/.htpasswd admin
# Ajouter un second compte au fichier existant (sans -c, sinon le fichier est écrasé)
htpasswd -B /var/www/secrets/.htpasswd editeur
L'option -c crée le fichier. À n'utiliser qu'une seule fois : la relancer efface les comptes déjà présents. -B force le format bcrypt. Sans elle, htpasswd retombe sur APR1-MD5 par défaut, ou accepte -d pour l'ancien crypt() DES, -s pour SHA-1. Ces deux derniers formats n'ont plus leur place dans un fichier créé aujourd'hui.
Pas d'accès SSH, ou apache2-utils absent du serveur ? Un générateur en ligne produit la même ligne, directement dans le navigateur, sans rien installer.
Sur Apache, le module correspondant, mod_auth_basic, avec mod_authn_file pour la lecture du fichier, doit être activé :
sudo a2enmod auth_basic authn_file
sudo systemctl reload apache2
Sur une distribution où ces modules ne sont pas chargés par défaut, la configuration ci-dessous échoue silencieusement ou renvoie une erreur 500 Internal Server Error, visible uniquement dans error.log.
Déclarer la protection : AuthType, AuthName, AuthUserFile, Require valid-user
Deux emplacements possibles pour ces directives : le fichier .htaccess du dossier à protéger, ou un bloc <Directory> dans la configuration du VirtualHost.
Dans .htaccess, à la racine du dossier à protéger :
AuthType Basic
AuthName "Zone reservee"
AuthUserFile /var/www/secrets/.htpasswd
Require valid-user
Le même résultat, dans le VirtualHost, en bloc <Directory> :
<VirtualHost *:443>
ServerName preprod.captaindns.com
DocumentRoot /var/www/html/preprod
<Directory "/var/www/html/preprod">
AuthType Basic
AuthName "Zone reservee"
AuthUserFile /var/www/secrets/.htpasswd
Require valid-user
</Directory>
</VirtualHost>
Les deux syntaxes sont équivalentes fonctionnellement, mais pas en performance. Un .htaccess est relu par Apache à chaque requête, pour chaque dossier parent du chemin demandé. Le bloc <Directory>, lui, est chargé une seule fois au démarrage du serveur. Sur un site à fort trafic, le bloc <Directory> évite une lecture disque répétée à chaque requête.
Require valid-user autorise tout compte présent dans le fichier .htpasswd, quel que soit son nom. Pour restreindre l'accès à des comptes précis, Require user admin editeur remplace la ligne.
Le piège AllowOverride AuthConfig
Un .htaccess avec des directives d'authentification parfaitement correctes, mais qui ne produit aucun effet. C'est le symptôme numéro un signalé sur les forums d'hébergeurs et sur StackOverflow.
La cause : la directive AllowOverride, définie au niveau du VirtualHost ou de la configuration globale, contrôle quelles catégories de directives un .htaccess a le droit de modifier. Si elle vaut None, Apache ignore purement et simplement le fichier .htaccess. Sans erreur, sans avertissement visible dans les logs applicatifs. Juste un dossier qui reste ouvert.
<Directory "/var/www/html">
AllowOverride AuthConfig
</Directory>
AuthConfig autorise les directives liées à l'authentification (AuthType, AuthName, AuthUserFile, Require). All autorise tout, ce qui fonctionne mais ouvre plus large que nécessaire. Après modification, un rechargement suffit :
sudo apachectl configtest && sudo systemctl reload apache2
configtest vérifie la syntaxe avant de recharger. Un simple test évite de casser un serveur en production sur une faute de frappe dans le fichier de configuration.
Vérifier que la protection fonctionne
Après rechargement, une requête curl confirme la mise en place avant de tester au navigateur :
curl -i https://preprod.captaindns.com/preprod/
# Doit renvoyer 401 Unauthorized
curl -i -u admin:motdepasse https://preprod.captaindns.com/preprod/
# Doit renvoyer 200 OK
Si la première commande renvoie directement 200, deux causes probables : AllowOverride qui ignore le .htaccess, ou le bloc <Directory> mal ciblé sur le mauvais chemin. Le fichier journal d'erreurs, /var/log/apache2/error.log sur Debian/Ubuntu, confirme la lecture ou non du fichier AuthUserFile à chaque tentative.
Configurer la protection par mot de passe avec nginx (auth_basic)
nginx protège un dossier avec deux directives dans un bloc location, sans aucun fichier .htaccess.
auth_basic et auth_basic_user_file
Le module ngx_http_auth_basic_module est compilé par défaut dans nginx. Un bloc location suffit :
server {
listen 443 ssl;
server_name preprod.captaindns.com;
location /preprod/ {
auth_basic "Zone reservee";
auth_basic_user_file /var/www/secrets/.htpasswd;
}
}
auth_basic définit le texte du realm, affiché dans la boîte de dialogue du navigateur, ou off pour désactiver l'authentification sur un sous-chemin hérité d'un location parent. auth_basic_user_file pointe vers le même format de fichier que celui utilisé par Apache.
Ce fichier .htpasswd est directement compatible entre les deux serveurs. Un couple identifiant/mot de passe généré côté Apache fonctionne tel quel sur nginx, et inversement. nginx lit le format $apr1$, via sa propre implémentation embarquée de MD5 crypt, depuis longtemps, et le format bcrypt $2y$ depuis nginx 1.0.3, sur les systèmes dont la crypt_r() du système supporte bcrypt, ce qui couvre les distributions Linux courantes équipées de libxcrypt.
nginx ne lit pas .htaccess : une différence d'architecture à comprendre
nginx ne cherche jamais de fichier .htaccess. Toute la configuration vit dans nginx.conf et les fichiers inclus, chargés une seule fois au démarrage du service.
Ce choix d'architecture, assumé depuis les débuts de nginx, explique une partie de sa réputation de performance. Apache, avec AllowOverride actif, vérifie l'existence d'un .htaccess dans chaque dossier parent du chemin demandé, à chaque requête. nginx n'a rien de tel à faire : toute la configuration est déjà chargée en mémoire, prête à l'emploi, avant même la première requête.
Conséquence pratique : copier-coller une configuration .htaccess Apache dans un dossier servi par nginx ne fait rigoureusement rien. Le fichier est ignoré en silence, sans message d'erreur. Les directives équivalentes doivent être réécrites dans le bloc server ou location correspondant, puis appliquées par un rechargement :
sudo nginx -t && sudo systemctl reload nginx
Combiner avec une restriction IP
nginx accepte allow et deny en complément de auth_basic, dans le même bloc location :
location /preprod/ {
allow 203.0.113.0/24;
deny all;
auth_basic "Zone reservee";
auth_basic_user_file /var/www/secrets/.htpasswd;
}
Les deux couches se combinent. Une IP hors de la plage autorisée reçoit un 403 avant même que nginx ne demande les identifiants. Une IP autorisée doit malgré tout s'authentifier ensuite. Cette double barrière protège contre le vol du seul mot de passe : un attaquant qui récupère les identifiants mais n'est pas sur le bon réseau reste bloqué en amont.
Basic Auth derrière un reverse proxy : Traefik et Caddy
Le format .htpasswd ne se limite pas à Apache et nginx. Les reverse proxies modernes le reprennent tel quel, avec une contrainte notable sur le format de hachage accepté selon le proxy.
Traefik lit directement un fichier .htpasswd via son middleware basicAuth, déclaré en label Docker ou en configuration statique :
labels:
- "traefik.http.middlewares.preprod-auth.basicauth.usersfile=/etc/traefik/.htpasswd"
- "traefik.http.routers.preprod.middlewares=preprod-auth"
Le fichier référencé est le même .htpasswd que celui généré pour Apache ou nginx : APR1-MD5 et bcrypt fonctionnent tous les deux, sans conversion.
Caddy, lui, restreint le choix au strict minimum. Sa directive basic_auth n'accepte que le format bcrypt, produit par sa propre commande caddy hash-password ou, de façon équivalente, par htpasswd -B :
caddy hash-password --plaintext motdepasseTresLong
Un fichier .htpasswd hérité en APR1-MD5 échoue silencieusement derrière Caddy : impossible de s'authentifier tant que les comptes n'ont pas été régénérés en bcrypt. Cette contrainte confirme ce que l'histoire du format laissait déjà deviner : bcrypt est aujourd'hui le seul format qui traverse tous les serveurs et reverse proxies web courants, sans exception.

APR1-MD5 vs bcrypt : quel algorithme choisir pour .htpasswd ?
Pour un fichier .htpasswd créé aujourd'hui, bcrypt est le choix par défaut. APR1-MD5 ne se justifie que par une contrainte de compatibilité précise.
Fonctionnement d'APR1-MD5
Le calcul enchaîne deux empreintes MD5 intermédiaires, puis 1 000 itérations, avant un encodage spécifique.
D'abord, deux empreintes initiales sont calculées : l'une sur la concaténation mot de passe + $apr1$ + sel, l'autre sur mot de passe + sel + mot de passe. Ensuite, des fragments de la seconde empreinte sont réinjectés dans la première selon un motif qui dépend de la longueur du mot de passe. Vient alors la boucle de 1 000 itérations : à chaque tour, mot de passe, sel et empreinte du tour précédent se recombinent dans un ordre qui varie selon la parité du numéro de tour. Le résultat final, 16 octets, est relu dans un ordre entrelacé puis codé sur l'alphabet base64 propre à crypt (./0-9A-Za-z), pour produire les 22 caractères de l'empreinte.
Ce sont ces 1 000 itérations qui distinguent APR1-MD5 d'un MD5 nu : elles multiplient par mille le coût d'une tentative. Mille, c'est dérisoire face à un GPU moderne, capable de tester plusieurs milliards de combinaisons MD5 par seconde.
Fonctionnement de bcrypt
bcrypt dérive la phase de configuration de clé, ou key setup, du chiffrement Blowfish, volontairement coûteuse à calculer.
Cette phase, nommée EksBlowfish (Expensive Key Schedule Blowfish), mélange mot de passe et sel dans les sous-clés de Blowfish à travers un nombre de tours égal à 2 exposant le facteur de coût choisi. Contrairement à MD5, cette étape sollicite des accès mémoire non séquentiels qui limitent fortement la parallélisation sur GPU ou circuit dédié : chaque unité de calcul doit accéder à une table de grande taille de façon imprévisible, un schéma que le matériel massivement parallèle gère mal. Le sel, 128 bits, est intégré directement dans le hash final, contrairement à APR1-MD5 qui le stocke dans un champ séparé.
Avec le facteur de coût 10 adopté par défaut dans htpasswd -B et les générateurs CaptainDNS, le calcul tourne autour de 60 ms sur un serveur générique. Passer à 12 le porte à environ 250 ms. Ce temps est payé à chaque requête authentifiée, pas seulement à la création du compte : un facteur trop élevé se sent immédiatement à l'usage.
Tableau comparatif étendu
| Format | Préfixe | Sel | Coût de calcul | Résistance GPU/ASIC | Compatibilité serveurs | Introduction | Verdict |
|---|---|---|---|---|---|---|---|
| crypt() DES | aucun | 12 bits (2 car.) | 25 tours DES, mot de passe tronqué à 8 car. | Nulle | Historique uniquement | Fin des années 1970 | Obsolète |
| SHA-1 | {SHA} | Aucun | 1 itération | Nulle | Apache, nginx | Années 1990 | À éviter |
| APR1-MD5 | $apr1$ | 8 car. | 1 000 itérations MD5 | Faible | Apache, nginx, Traefik | Apache 1.3, 1998 | Compatibilité maximale |
| bcrypt | $2y$ | 128 bits | 2^N tours, réglable | Élevée | Apache 2.4+, nginx récent, Traefik, Caddy | 1999 (USENIX) | Recommandé |
Le fossé entre les deux dernières lignes n'est pas qu'une question de génération. APR1-MD5 a un coût fixé une fois pour toutes en 1998. bcrypt se recalibre : passer un facteur de coût de 10 à 12 aujourd'hui reproduit, pour un attaquant, une difficulté relative comparable à celle du papier de 1999, alors que le matériel a été démultiplié entre-temps.
Vérifier un hash hors ligne
Les deux formats se contrôlent avec les utilitaires système, sans générateur en ligne :
# APR1-MD5, en imposant le sel pour comparer
openssl passwd -apr1 -salt Xq7nD2mR motdepasseTresLong
# bcrypt, coût 12
htpasswd -nbB -C 12 admin motdepasseTresLong
Pour bcrypt, la comparaison directe de deux hash ne fonctionne jamais : le sel change à chaque calcul, même à mot de passe et coût identiques. La seule vérification valable rejoue le calcul avec le sel déjà présent dans le hash stocké, ce que fait nativement la fonction crypt() du système au moment de l'authentification.

Permissions de fichiers : compléter la protection par mot de passe
Basic Auth protège l'accès HTTP à un dossier. Elle ne protège plus rien du tout si le fichier .htpasswd lui-même est mal placé ou mal protégé côté système de fichiers.
Emplacement du fichier .htpasswd, hors DocumentRoot
Le fichier .htpasswd ne doit jamais se trouver dans un dossier servi directement par le serveur web.
/var/www/html/ <- DocumentRoot, servi par HTTP
/var/www/secrets/ <- hors DocumentRoot, jamais servi
.htpasswd
Un fichier .htpasswd placé par erreur dans /var/www/html/.htpasswd devient, sauf blocage explicite des fichiers commençant par un point, téléchargeable en une simple requête HTTP. Un attaquant récupère alors tous les hash du fichier et les attaque hors ligne, tranquillement, sans que le facteur de coût du serveur ne le ralentisse le moins du monde. Le calcul se fait chez lui, pas chez vous.
Permissions recommandées (chmod, chown, umask)
Deux niveaux de permissions comptent : le fichier .htpasswd lui-même, et le dossier protégé.
Pour .htpasswd, viser 640 (lecture/écriture pour le propriétaire, lecture pour le groupe, rien pour les autres), avec un propriétaire cohérent avec l'utilisateur du serveur web, www-data sur Debian/Ubuntu, apache sur RHEL, ou son groupe :
chown root:www-data /var/www/secrets/.htpasswd
chmod 640 /var/www/secrets/.htpasswd
644 reste un piège classique en dépannage rapide : ce mode rend le fichier lisible par tous les utilisateurs du système, pas seulement par le processus Apache ou nginx. Sur un serveur mutualisé ou multi-utilisateurs, n'importe quel compte local peut alors lire les hash. Le même réflexe s'applique au dossier protégé : 750 suffit largement, propriétaire et groupe seulement, rien pour les autres. 777 en dépannage, ça se voit encore trop souvent, et ça n'a jamais rien réglé durablement.
Un mot sur umask, souvent oublié : si le fichier .htpasswd est recréé par un script de déploiement plutôt qu'à la main avec htpasswd, la valeur d'umask du processus qui l'écrit détermine ses permissions par défaut. Un umask trop permissif, 022 ou moins restrictif encore, peut recréer un fichier lisible par tous à chaque déploiement, silencieusement, en écrasant le chmod 640 posé manuellement la fois précédente. Vérifier les permissions après chaque déploiement automatisé évite la régression.
Dernier rappel utile : Basic Auth protège l'accès HTTP, pas l'accès filesystem. Un mauvais chmod contourne toute la couche applicative, en silence, sans que rien dans les logs HTTP ne le signale.
Bonnes pratiques et pièges à éviter
Basic Auth mal configurée donne une fausse impression de sécurité. Quelques règles simples évitent la plupart des angles morts.
HTTPS n'est pas négociable. La RFC 7617 le précise : le schéma Basic ne chiffre rien. Sans TLS, le mot de passe encodé en base64 se lit en clair par quiconque intercepte le trafic, sur un Wi-Fi public, un proxy compromis ou un simple outil de capture de paquets. Activer Basic Auth sur du HTTP nu revient à afficher le mot de passe sur une pancarte.
Ne jamais réutiliser un mot de passe applicatif ou personnel pour un compte .htpasswd. Ces fichiers sont souvent moins surveillés que les systèmes d'authentification principaux, et un mot de passe partagé entre deux systèmes multiplie la surface d'attaque en cas de fuite de l'un des deux.
Combiner avec une restriction IP quand le public cible est connu à l'avance. Sur Apache, Require ip 203.0.113.0/24 en complément de Require valid-user, via RequireAny. Sur nginx, allow et deny déclarés avant la directive auth_basic. Deux couches valent mieux qu'une pour un accès vraiment sensible.
Un compte partagé par toute une équipe ne trace rien. Si trois personnes se connectent avec le même identifiant admin, impossible de savoir qui a fait quoi en cas d'incident. Un compte par personne coûte trente secondes de configuration en plus, et change tout le jour où il faut comprendre ce qui s'est passé.
Le realm, le texte déclaré dans AuthName ou auth_basic, n'est pas qu'un détail cosmétique. Un intitulé vague comme « Zone reservee » évite de révéler la nature du service protégé à qui tomberait sur le prompt sans y être invité. Nommer le realm « Admin panel Odoo » ou « Grafana interne » donne gratuitement une information à quiconque teste des URLs au hasard.
Changer un mot de passe dans .htpasswd ne suffit pas à déconnecter un navigateur déjà authentifié. L'identifiant reste mémorisé côté client tant que le navigateur ne reçoit pas un nouveau 401, et Basic Auth n'a aucun mécanisme de déconnexion pour en provoquer un. Deux méthodes forcent tout de même un nouveau prompt sans intervention côté client : changer le realm, le texte déclaré dans AuthName ou auth_basic, crée une zone distincte aux yeux du navigateur, qui ne renvoie plus l'ancien identifiant et redemande la saisie. Sinon, fermer le navigateur ou ouvrir l'URL dans une fenêtre de navigation privée reste la seule façon fiable de vider des identifiants Basic Auth déjà mémorisés.
Les logs d'accès standards n'enregistrent jamais le mot de passe en clair : l'en-tête Authorization n'apparaît pas dans le format de log par défaut d'Apache ou de nginx. C'est un comportement sûr par défaut, mais il vaut la peine de le vérifier explicitement si un format de log personnalisé a été ajouté quelque part dans la configuration. Un %i mal placé dans un LogFormat Apache écrirait le couple identifiant/mot de passe encodé en base64 dans un fichier de log, potentiellement moins protégé que le fichier .htpasswd lui-même.
Sur une infrastructure avec plusieurs environnements, dev, staging, préproduction, gérer les fichiers .htpasswd à la main tourne vite au bazar : un compte oublié après le départ d'un collègue, un mot de passe qui traîne encore en APR1-MD5 pendant que le reste est passé en bcrypt. Un playbook Ansible ou un script de déploiement qui régénère le fichier à partir d'une source unique, un fichier listant les comptes autorisés par exemple, évite la dérive entre environnements et donne un point unique où retirer un accès.
Basic Auth reste un verrou HTTP, pas un système d'authentification applicatif. Pas de logout propre : fermer le navigateur ou changer d'URL reste la seule façon de s'en défaire côté client. Pas de verrouillage après plusieurs échecs, pas de rotation automatique des mots de passe. Pour un accès qui a vocation à durer et à accueillir plusieurs utilisateurs avec des droits différents, prévoir une migration vers une couche applicative dédiée dès que possible.
Plan d'action recommandé
- Choisir le serveur concerné : Apache (.htaccess/.htpasswd) ou nginx (auth_basic), selon l'infrastructure en place.
- Générer un hash bcrypt pour chaque compte, ou APR1-MD5 seulement si une contrainte de compatibilité ancienne l'impose.
- Placer le fichier .htpasswd hors de la racine web, avec des permissions 640.
- Vérifier que le site est en HTTPS avant d'activer Basic Auth : jamais sur du HTTP nu.
- Tester avec un compte réel, puis documenter les identifiants dans un gestionnaire de mots de passe d'équipe, pas dans un fichier texte partagé.
Générer vos hash .htpasswd sans ligne de commande
Pas de paquet apache2-utils installé, pas d'accès SSH, ou simplement envie d'aller plus vite : les deux générateurs CaptainDNS produisent directement la ligne à coller dans .htpasswd.
Générez votre hash .htpasswd
FAQ
Qu'est-ce qu'un fichier .htaccess ?
Un fichier de configuration lu par Apache dans chaque dossier où il se trouve, sauf si une directive AllowOverride None l'a désactivé au niveau du serveur. Il déclare des règles locales, dont l'authentification, sans toucher à la configuration globale du VirtualHost. nginx ne le lit jamais : toute sa configuration vit dans nginx.conf.
Où placer le fichier .htpasswd sur le serveur ?
Hors de la racine web (DocumentRoot pour Apache, root pour nginx), par exemple dans /var/www/secrets/. Un fichier .htpasswd accessible en HTTP peut être téléchargé, et ses hash attaqués hors ligne, sans que le facteur de coût de bcrypt ne ralentisse l'attaquant le moins du monde.
.htpasswd est-il encore sûr aujourd'hui ?
Oui, à condition d'utiliser bcrypt (htpasswd -B) plutôt qu'APR1-MD5 ou l'ancien crypt() DES, et de placer le fichier hors de la racine web avec des permissions restrictives, 640. Le mécanisme Basic Auth lui-même reste sûr, tant qu'il tourne derrière HTTPS.
Comment sécuriser l'accès à un dossier avec Apache ou nginx ?
Sous Apache, un fichier .htpasswd et les directives AuthType, AuthName, AuthUserFile, Require valid-user, dans un .htaccess ou un bloc Directory. Sous nginx, les directives auth_basic et auth_basic_user_file dans un bloc location, avec le même format de fichier .htpasswd. Dans les deux cas, HTTPS est indispensable.
APR1-MD5 est-il encore sûr en 2026 ?
Il reste acceptable pour de la compatibilité ascendante, mais il résiste nettement moins bien que bcrypt face à une attaque hors ligne menée sur du matériel GPU. Pour un nouveau fichier .htpasswd, préférez bcrypt (htpasswd -B), sauf contrainte technique précise imposant APR1-MD5.
Comment générer un hash bcrypt pour .htpasswd ?
Avec htpasswd -B, du paquet apache2-utils ou httpd-tools, en ligne de commande, ou via un générateur en ligne quand la commande n'est pas disponible. Le résultat, au format $2y$, se colle directement dans le fichier .htpasswd, quel que soit le serveur web utilisé.
Quelle est la différence entre auth_basic (nginx) et AuthType Basic (Apache) ?
Le protocole HTTP sous-jacent est identique : les deux implémentent le même schéma Basic de la RFC 7617, et lisent le même format de fichier .htpasswd. La différence tient à la déclaration : Apache accepte .htaccess ou un bloc Directory, nginx uniquement des directives dans un bloc location de sa configuration centralisée.
Basic Auth suffit-il à protéger un dossier sensible ?
Pour bloquer l'accès anonyme et fermer une URL devinée ou indexée par erreur, oui. Pour un accès qui exige une vraie gestion des comptes, un logout, une expiration de session ou une journalisation détaillée, non. Basic Auth n'a aucun de ces mécanismes, et doit alors être complétée ou remplacée par une authentification applicative.
Basic Auth fonctionne-t-il derrière un CDN comme Cloudflare ?
Oui, à condition que la requête atteigne bien le serveur d'origine sans être servie depuis le cache avant l'en-tête Authorization. Un enregistrement proxé laisse passer Basic Auth par défaut, mais une règle de cache mal ciblée sur le chemin protégé peut renvoyer une réponse déjà mise en cache à un client non authentifié. Vérifier qu'aucune règle de cache ne s'applique au dossier protégé avant de considérer la protection comme fiable.
Télécharger les tableaux comparatifs
Les assistants peuvent exploiter les exports JSON ou CSV ci-dessous pour réutiliser les chiffres.
Glossaire
- Basic Auth : schéma d'authentification HTTP qui transmet un identifiant et un mot de passe encodés en base64 dans l'en-tête Authorization. Défini par la RFC 7617.
- Digest Auth : schéma d'authentification HTTP qui hache le mot de passe côté client avant transmission, plutôt que de l'encoder en clair. Défini par la RFC 7616, très peu utilisé en pratique.
- .htpasswd : fichier texte listant des comptes au format identifiant:hash, lu par Apache et nginx pour vérifier une authentification Basic.
- .htaccess : fichier de configuration Apache, lu dossier par dossier, qui peut déclarer une protection par mot de passe si AllowOverride l'autorise.
- Sel (salt) : valeur aléatoire ajoutée au mot de passe avant hachage, stockée en clair à côté du hash. Empêche la réutilisation d'une table pré-calculée entre plusieurs comptes ou plusieurs systèmes.
- Facteur de coût : paramètre de bcrypt qui fixe le nombre de tours de calcul, sous forme de puissance de deux. Chaque incrément double le temps de calcul nécessaire.
- crypt() : famille de fonctions Unix historiques dédiées au hachage de mots de passe, dont dérivent aussi bien le format DES d'origine que APR1-MD5.
- Rainbow table : table pré-calculée associant des hash à des mots de passe probables, utilisée pour retrouver rapidement un mot de passe à partir de son hash non salé.
- AllowOverride : directive Apache qui définit quelles catégories de directives un fichier .htaccess a le droit de modifier. AllowOverride None désactive totalement la lecture du .htaccess.


