Come creare un file .htpasswd: guida a HTTP Basic Auth
Un file .htpasswd è l’archivio delle credenziali lato server per l’autenticazione HTTP Basic: un file di testo dove ogni riga è una coppia username:hash. Per creare un file .htpasswd generi quella riga con l’hash e la salvi in un punto che il tuo web server possa leggere. Ci sono tre modi per farlo:
- Il comando
htpasswd(daapache2-utils/httpd-tools) — lo strumento canonico. openssl passwd— già installato quasi ovunque, nessun pacchetto extra.- Nel browser — usa il generatore htpasswd per creare una voce in locale, senza installare nulla e senza inviare niente in rete.
Questa guida va oltre la riga di comando secca. Vediamo come funziona davvero l’handshake della Basic Auth, come creare il file in tre modi, quale dei cinque formati di hash conviene, come integrarlo in Apache, nginx, Docker, Kubernetes, Caddy e Traefik, e come mettere tutto in sicurezza per non lasciare in giro un file di credenziali che chiunque può scaricare.
Cos’è un file .htpasswd?
Ogni riga di un file .htpasswd contiene le credenziali di un utente come coppia separata da due punti. Lo username è memorizzato così com’è; la password è salvata solo come hash unidirezionale, così il testo in chiaro non viene mai scritto su disco. L’anatomia di una singola riga bcrypt è questa:
admin : $2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
│ │
└─ username └─ hash (algorithm prefix $2y$ + cost + salt + digest)
Prima viene lo username, poi un solo :, poi l’hash. Uno username può arrivare a 255 byte e non deve mai contenere i due punti, perché i due punti sono il separatore di campo. L’hash porta con sé il proprio marcatore di algoritmo come prefisso ($2y$ per bcrypt, $apr1$ per Apache MD5, {SHA} per SHA-1), così il server sa come verificarlo senza alcuna configurazione aggiuntiva.
Per più utenti aggiungi una riga per utente:
admin:$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
alice:$2y$10$3bQ8xY7tLp2mZ0xW5cR4fO9vK1jH6sD2nG8aQ5wE3rT7uI4oP1cm
bob:$apr1$mZ0xW5cR$4fK1jH6sD2nG8aQ5wE3rT2
Gli algoritmi possono essere mescolati nello stesso file. Il server legge ogni riga, rileva il formato dal prefisso e verifica con qualsiasi cosa sia stata usata. Qui due utenti bcrypt e un utente apr1 convivono senza problemi.
.htpasswd e .htaccess a confronto
Questi due vengono confusi di continuo perché su Apache viaggiano insieme, ma fanno lavori diversi. .htaccess è il file di configurazione per directory di Apache. Contiene direttive — comprese quelle che attivano la Basic Auth e puntano al tuo archivio di credenziali. .htpasswd è il database delle credenziali — solo le righe username:hash, nessuna configurazione.
In breve: .htaccess decide che una directory richiede un login e dove trovare la lista utenti; .htpasswd è quella lista utenti. nginx non usa affatto .htaccess — la sua configurazione della Basic Auth vive nel blocco server o location della configurazione principale, ma legge lo stesso formato di credenziali .htpasswd.
Come funziona l’autenticazione HTTP Basic
L’autenticazione HTTP Basic è un handshake challenge-response integrato nella specifica HTTP. Una volta chiari questi tre passaggi, il troubleshooting che segue diventa molto più semplice:
- Il client richiede una risorsa protetta senza credenziali.
- Il server risponde
401 Unauthorizede include un headerWWW-Authenticate: Basic realm="..."— questa è la challenge. - Il client ripete la richiesta con un header
Authorization: Basic <base64(user:password)>. Se le credenziali corrispondono a una riga del file.htpasswd, il server restituisce la risorsa.
Questo è tutto il protocollo: niente form di login, niente cookie di sessione, niente token. Ogni richiesta successiva porta con sé lo stesso header.
La challenge 401 / WWW-Authenticate
L’header WWW-Authenticate fa due cose. Il suo token Basic indica al client quale schema usare, e la sua stringa realm etichetta lo spazio di protezione. I browser mostrano il testo del realm nella finestra di login (“Il sito dice: …”) e lo usano come chiave di cache: le credenziali inserite per un realm vengono riusate per altri URL nello stesso realm, così all’utente non viene richiesto di nuovo a ogni pagina.
Ecco lo scambio grezzo, catturato con 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
L’header Authorization: Basic
La credenziale che il client invia è base64(username:password). Questo è il singolo fatto di sicurezza più importante sulla Basic Auth: base64 è codifica, non cifratura. È completamente reversibile da chiunque, quindi la credenziale viaggia di fatto in chiaro. Puoi vedere tu stesso il giro completo:
# 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
Questa reversibilità è il motivo per cui la Basic Auth deve girare su HTTPS — senza TLS, la password è leggibile da chiunque si trovi sul percorso di rete. Se vuoi costruire o ispezionare quell’header a mano, il codificatore/decodificatore Base64 esegue la stessa trasformazione user:password nel browser.
Come creare un file .htpasswd
Ci sono tre modi pratici per creare il file. Scegli in base a cosa è installato e a dove vuoi che viva la password.
Con il comando htpasswd
Il binario htpasswd è incluso nel pacchetto di utilità di Apache. Installalo per prima cosa:
# Debian / Ubuntu
sudo apt install apache2-utils
# RHEL / CentOS / Fedora
sudo yum install httpd-tools
Crea il file e il suo primo utente. Il flag -c significa create, e sovrascrive un file esistente — usalo solo la primissima volta:
htpasswd -c /etc/nginx/.htpasswd admin
# prompts twice for the password, then writes the file
Per aggiungere altri utenti, togli il -c così aggiungi in coda invece di sovrascrivere:
htpasswd /etc/nginx/.htpasswd alice
Per forzare bcrypt invece del default della piattaforma, aggiungi -B. Per stampare la voce su stdout senza toccare alcun file — comodo per piparla in una configurazione o in un Dockerfile — combina -b (password sulla riga di comando) e -n (nessun file):
htpasswd -Bbn admin 's3cret'
# → admin:$2y$10$N9qo8uLOickgx2ZMRZoMye...
I flag che userai davvero:
| Flag | Significato |
|---|---|
-c | Crea un nuovo file (sovrascrive se esiste) — solo per il primo utente |
-B | Usa bcrypt |
-b | Prende la password come argomento da riga di comando (nessun prompt) |
-n | Stampa su stdout invece di scrivere un file |
-D | Elimina dal file l’utente indicato |
Un’avvertenza su -b: la password finisce nella cronologia della tua shell. Per credenziali di produzione una tantum, preferisci la forma con prompt o l’opzione browser più sotto.
Senza apache2-utils — con OpenSSL
Niente binario htpasswd? OpenSSL è praticamente su ogni sistema e può produrre direttamente un hash apr1. Avvolgilo in printf per costruire una riga completa:
printf "admin:$(openssl passwd -apr1 's3cret')\n" >> /etc/nginx/.htpasswd
# admin:$apr1$k3l4Hj9.$qN8vY7tLp2mZ0xW5cR4f.
Il formato apr1 è portabile sia su Apache che su nginx, il che lo rende la via con meno dipendenze su una macchina minimale.
Nel browser — niente installazioni, niente fughe
Se non vuoi installare un pacchetto, o preferisci non digitare una password di produzione in una shell dove finisce in ~/.bash_history, genera la voce lato client. Il generatore htpasswd calcola gli hash bcrypt, apr1 e SHA-1 interamente nel tuo browser, ti consegna una riga user:hash pronta da incollare più un blocco di configurazione del server corrispondente, e non trasmette mai niente. Già che ci sei, crea una password forte e univoca con il generatore di password casuali invece di riusarne una.
Formati password di htpasswd a confronto
Il comando htpasswd può emettere cinque formati, e non sono equivalenti. Questa tabella è il riferimento rapido per sceglierne uno:
| Formato | Prefisso | Con salt | Robustezza | Usalo per |
|---|---|---|---|---|
| bcrypt | $2y$ | Sì | Massima | Apache, Docker Registry, Caddy, Traefik |
| apr1 (Apache MD5) | $apr1$ | Sì | Moderata | nginx (portabile, default sicuro) |
| SHA-1 | {SHA} | No | Debole | Solo per compatibilità legacy |
| crypt (DES) | (nessuno) | Sì (2 caratteri) | Molto debole | Non usarlo |
| plain | (nessuno) | No | Nessuna | Solo per test in locale |
Qualche nota che non sta in una cella della tabella. bcrypt usa un salt casuale di 16 byte e un fattore di costo adattivo (default 10, raccomandazione moderna 12), così password identiche producono hash diversi e il work factor scala con l’hardware. Una sua particolarità: bcrypt tronca la password a 72 byte — tutto ciò che eccede viene ignorato in silenzio. apr1 esegue 1.000 round di MD5 con salt; molto più debole di bcrypt, ma implementato nativamente sia da Apache che da nginx, ed è per questo la scelta portabile. SHA-1 è senza salt, quindi password identiche danno digest identici e le rainbow table si applicano — tienilo solo per i sistemi legacy. crypt e plain esistono per ragioni storiche e di test; nessuno dei due ha posto in produzione.
I prefissi $2a$ / $2b$ / $2y$
Vedrai hash bcrypt che iniziano con $2a$, $2b$ o $2y$. Sono lo stesso algoritmo e producono hash equivalenti e intercambiabili; le lettere di versione sono residui di correzioni di bug storiche su come certe librerie gestivano i caratteri high-bit e la lunghezza della stringa. L’htpasswd di Apache emette $2y$, e Caddy, Traefik e Docker Registry lo verificano tutti correttamente.
Se vuoi il confronto più approfondito di bcrypt con le alternative moderne, la guida bcrypt vs Argon2 vs scrypt spiega come questi algoritmi di hashing delle password differiscono per costo, memory hardness e threat model.
Configurare la Basic Auth sul tuo server
Un file di credenziali da solo non fa nulla — al server va detto di richiederlo. Ecco sei piattaforme.
Apache (.htaccess)
Inserisci questo in un file .htaccess nella directory che vuoi proteggere (oppure in un blocco <Directory> del tuo vhost):
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
AuthName è la stringa del realm che il browser mostra; AuthUserFile è il percorso assoluto del tuo file di credenziali; Require valid-user accetta qualsiasi utente elencato al suo interno.
nginx (auth_basic)
nginx mette la configurazione in un blocco location o server — non c’è alcun .htaccess:
location /admin/ {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}
Ricarica con nginx -s reload. Qui usa il formato apr1. nginx delega la verifica di bcrypt al crypt() di sistema, che fallisce su molte build (più avanti nel troubleshooting), mentre apr1 viene verificato internamente su ogni piattaforma.
Docker Registry e Kubernetes ingress-nginx
Il backend htpasswd di un Docker Registry privato accetta solo bcrypt. Genera la voce, montala e fai puntare il registry ad essa:
# 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
Per Kubernetes ingress-nginx, salva il file come Secret e referenzialo con le annotation:
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"
Nota che la chiave del Secret deve chiamarsi auth — ingress-nginx cerca esattamente quella chiave.
Caddy e Traefik
Entrambi si aspettano hash bcrypt. Caddy usa la direttiva basic_auth (incolla l’hash bcrypt, non il testo in chiaro):
example.com {
basic_auth /admin/* {
admin $2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
}
}
Traefik usa un middleware basicauth, con coppie user:bcrypt-hash (fai l’escape di ogni $ per il formato della tua configurazione):
http:
middlewares:
admin-auth:
basicAuth:
users:
- "admin:$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"
Una volta protetto un endpoint, verifica che funzioni da riga di comando. Il costruttore di comandi cURL assembla per te la richiesta -u user:pass così puoi confermare sia il 401 che il 200 autenticato.
Best practice di sicurezza
La Basic Auth è semplice, il che rende facili da individuare i pochi modi di usarla male.
- Servi sempre su HTTPS. La credenziale è base64 reversibile, quindi l’HTTP semplice espone la password sul filo. Termina il TLS davanti a ogni endpoint protetto, nessuna eccezione.
- Tieni il file fuori dalla web root. Se
.htpasswdsi trova in una directory servita, una configurazione errata può permettere a qualcuno di scaricarlo. Tienilo in un punto come/etc/nginx/.htpasswd, impostachmod 640e rendilo di proprietà dell’utente del web server (www-data,nginx) così che il server possa leggerlo e gli altri account no. - Usa password forti e univoche. Ogni account dovrebbe avere la propria password ad alta entropia dal generatore di password casuali, mai riusata. Se vuoi capire cosa significa “abbastanza forte” in bit, il chiarimento sull’entropia delle password scompone la matematica.
- Conoscine i limiti. La Basic Auth non ha logout né sessione: il browser mette in cache la credenziale per realm finché non lo chiudi, e la rinvia a ogni richiesta. Per una checklist più ampia su hashing, header e validazione, vedi le nostre best practice di sicurezza web.
Risoluzione degli errori comuni
nginx: crypt_r() failed (22: Invalid argument)
Questo è il singolo errore più comune della Basic Auth su nginx, e significa sempre la stessa cosa: nginx ha provato a verificare un hash bcrypt ($2y$) su una libc che non include lo schema Blowfish — tipicamente il musl di Alpine o una glibc più vecchia. La soluzione è rigenerare la voce come apr1, che nginx verifica internamente su qualsiasi piattaforma:
printf "admin:$(openssl passwd -apr1 's3cret')\n" > /etc/nginx/.htpasswd
nginx -s reload
Anche passare a un’immagine di base la cui libc supporta bcrypt funziona, ma apr1 è la soluzione più semplice e portabile.
401 anche con la password giusta
Quando sei certo che la password è corretta ma ricevi comunque un 401, segui questa checklist in ordine:
- Percorso del file. Conferma che
AuthUserFile/auth_basic_user_filepunti al file reale (percorso assoluto, nessun refuso). - Permessi. L’utente del web server deve poter leggere il file. Verifica con
sudo -u www-data cat /etc/nginx/.htpasswd. - Fine riga / codifica. Un file modificato su Windows può portare caratteri
\rche corrompono l’hash. Eseguifile .htpasswde applicados2unixse serve. - Cache del browser obsoleta. Il browser mette in cache le credenziali per realm. Prova in una finestra privata/incognito per escludere una vecchia password ricordata.
- Hash non corrispondente. Verifica che l’hash memorizzato corrisponda davvero alla password — incolla entrambi nella modalità di verifica del generatore htpasswd per confermare prima di incolpare la configurazione.
Quando NON usare la Basic Auth
La Basic Auth è lo strumento giusto per un insieme ristretto di compiti: un sito di staging, un percorso admin interno, un endpoint di artefatti CI, una dashboard di metriche, un registry privato. È a zero dipendenze e richiede due minuti per essere configurata.
È lo strumento sbagliato per un login di prodotto. Non c’è logout, né reset della password, né rate limiting, né blocco dell’account, né MFA. Le credenziali vengono rinviate a ogni richiesta e messe in cache dal browser finché non lo chiudi. Per qualsiasi cosa a cui gli utenti accedano, scegli sessioni, OAuth o OIDC. Essere onesti su quel confine è ciò che mantiene utile la Basic Auth dove serve davvero.
FAQ
Cosa contiene davvero una singola riga di un file .htpasswd?
Una coppia username:hash separata da due punti. L’hash inizia con un prefisso di algoritmo ($2y$ per bcrypt, $apr1$ per Apache MD5, {SHA} per SHA-1), seguito dal salt e dal digest. La password in chiaro non compare mai nel file.
Qual è la differenza tra .htpasswd e .htaccess?
.htaccess è il file di configurazione per directory di Apache — attiva la Basic Auth e punta all’archivio di credenziali. .htpasswd è quell’archivio di credenziali, e contiene le righe username:hash. nginx usa il formato .htpasswd ma configura l’auth nei suoi blocchi server/location, non in .htaccess.
Come aggiungo, cambio o rimuovo un utente in un file .htpasswd?
Per aggiungere o cambiare un utente, esegui htpasswd /path/.htpasswd username senza -c — se l’utente esiste, il suo hash viene aggiornato. Per rimuoverne uno, esegui htpasswd -D /path/.htpasswd username. Usa -c solo per il primissimo utente, dato che sovrascrive l’intero file.
Come fa il browser a ricordare le credenziali Basic Auth, e come fanno gli utenti a uscire?
Il browser mette in cache le credenziali con chiave il realm e le rinvia automaticamente a ogni richiesta corrispondente. Non c’è un logout standard: gli unici modi per cancellarle sono chiudere il browser o svuotarne la cache. Quel logout mancante è uno dei motivi per cui la Basic Auth non è adatta all’autenticazione di prodotto.
Posso usare lo stesso file .htpasswd sia per Apache che per nginx?
Sì, purché il formato dell’hash sia supportato da entrambi. apr1 (Apache MD5) è verificato nativamente da Apache e nginx ovunque, quindi è la scelta condivisa più sicura. bcrypt funziona su Apache ma su nginx dipende dal crypt() di sistema, che fallisce sulle build Alpine/musl.
L’autenticazione HTTP Basic è ancora rilevante nel 2026?
Sì. Come barriera leggera sopra l’HTTPS — strumenti interni, ambienti di staging, registry privati, endpoint di monitoraggio — è ancora pratica e a zero dipendenze. Solo non scambiarla per l’autenticazione di prodotto rivolta agli utenti, che ha bisogno di sessioni, reset, rate limiting e MFA che la Basic Auth non può fornire.
Verificato dal team Go Tools: ogni comando, blocco di configurazione e formato di hash di questa guida è stato controllato rispetto all’output di riferimento di Apache htpasswd (apache2-utils) e OpenSSL.