Skip to content
Volver al blog
Seguridad

Cómo crear un archivo .htpasswd: guía de HTTP Basic Auth

Crea un archivo .htpasswd con bcrypt o apr1, configura HTTP Basic Auth en Apache, nginx, Docker y Kubernetes y protégelo. Guía práctica de 2026.

11 min de lectura

Cómo crear un archivo .htpasswd: guía de HTTP Basic Auth

Un archivo .htpasswd es el almacén de credenciales del lado del servidor para la autenticación HTTP Basic: un archivo de texto plano donde cada línea es un par username:hash. Para crear un archivo .htpasswd, generas esa línea con el hash y la guardas en algún lugar que tu servidor web pueda leer. Hay tres maneras de hacerlo:

  • El comando htpasswd (de apache2-utils / httpd-tools): la herramienta canónica.
  • openssl passwd: ya viene instalado casi en todas partes, sin paquetes extra.
  • En tu navegador: usa el generador de htpasswd para crear una entrada localmente, sin instalar nada y sin enviar nada por la red.

Esta guía va más allá del comando de una sola línea. Cubrimos cómo funciona realmente el intercambio de Basic Auth, cómo crear el archivo de tres maneras, cuál de los cinco formatos de hash elegir, cómo integrarlo en Apache, nginx, Docker, Kubernetes, Caddy y Traefik, y cómo protegerlo todo para que no termines publicando un archivo de credenciales que cualquiera pueda descargar.

¿Qué es un archivo .htpasswd?

Cada línea de un archivo .htpasswd contiene las credenciales de un usuario como un par separado por dos puntos. El nombre de usuario se guarda tal cual; la contraseña se guarda solo como un hash de un solo sentido, así que el texto plano nunca se escribe en disco. La anatomía de una sola línea bcrypt se ve así:

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

Primero va el nombre de usuario, luego un único :, y después el hash. Un nombre de usuario puede tener hasta 255 bytes y nunca debe contener dos puntos, ya que estos son el separador de campos. El hash lleva su propio marcador de algoritmo como prefijo ($2y$ para bcrypt, $apr1$ para Apache MD5, {SHA} para SHA-1), así que el servidor sabe cómo verificarlo sin ninguna configuración adicional.

Para varios usuarios, agregas una línea por usuario:

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

Se pueden mezclar algoritmos en el mismo archivo. El servidor lee cada línea, detecta el formato por su prefijo y verifica contra el que se haya usado. Aquí conviven sin problema dos usuarios bcrypt y un usuario apr1.

.htpasswd vs .htaccess

Estos dos se confunden constantemente porque van juntos en Apache, pero hacen trabajos distintos. .htaccess es el archivo de configuración por directorio de Apache. Contiene directivas, incluidas las que activan Basic Auth y apuntan a tu almacén de credenciales. .htpasswd es la base de datos de credenciales: solo las líneas username:hash, sin configuración.

En resumen: .htaccess decide que un directorio requiere inicio de sesión y dónde encontrar la lista de usuarios; .htpasswd es esa lista de usuarios. nginx no usa .htaccess en absoluto: su configuración de Basic Auth vive en el bloque server o location de la configuración principal, pero lee el mismo formato de credenciales .htpasswd.

Cómo funciona la autenticación HTTP Basic

La autenticación HTTP Basic es un intercambio de desafío-respuesta integrado en la especificación HTTP. Si entiendes estos tres pasos, casi todo el diagnóstico posterior se explica solo:

  1. El cliente solicita un recurso protegido sin credenciales.
  2. El servidor responde 401 Unauthorized e incluye una cabecera WWW-Authenticate: Basic realm="...": este es el desafío.
  3. El cliente reintenta la solicitud con una cabecera Authorization: Basic <base64(user:password)>. Si las credenciales coinciden con una línea del archivo .htpasswd, el servidor devuelve el recurso.

Ese es todo el protocolo. No hay formulario de inicio de sesión, ni cookie de sesión, ni token. Cada solicitud posterior lleva la misma cabecera.

El desafío 401 / WWW-Authenticate

La cabecera WWW-Authenticate hace dos cosas. Su token Basic le dice al cliente qué esquema usar, y su cadena realm etiqueta el espacio de protección. Los navegadores muestran el texto del realm en el cuadro de inicio de sesión (“El sitio dice: …”) y lo usan como clave de caché: las credenciales ingresadas para un realm se reutilizan para otras URL del mismo realm, así que no se le vuelve a pedir al usuario en cada página.

Aquí está el intercambio en bruto, capturado 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

La cabecera Authorization: Basic

La credencial que el cliente envía es base64(username:password). Este es el dato de seguridad más importante sobre Basic Auth: base64 es codificación, no cifrado. Cualquiera puede revertirlo por completo, así que la credencial viaja en una forma que es, de hecho, texto plano. Puedes ver el ciclo completo tú mismo:

# 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

Esa reversibilidad es la razón por la que Basic Auth debe ejecutarse sobre HTTPS: sin TLS, la contraseña es legible para cualquiera en la ruta de red. Si quieres construir o inspeccionar esa cabecera a mano, el codificador/decodificador Base64 hace la misma transformación user:password en el navegador.

Cómo crear un archivo .htpasswd

Hay tres maneras prácticas de crear el archivo. Elige según lo que tengas instalado y dónde quieras que viva la contraseña.

Usando el comando htpasswd

El binario htpasswd viene en el paquete de utilidades de Apache. Instálalo primero:

# Debian / Ubuntu
sudo apt install apache2-utils

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

Crea el archivo y su primer usuario. El flag -c significa crear, y sobrescribe un archivo existente: úsalo solo la primera vez:

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

Para agregar más usuarios, omite el -c para anexar en lugar de sobrescribir:

htpasswd /etc/nginx/.htpasswd alice

Para forzar bcrypt en vez del valor por defecto de la plataforma, agrega -B. Para imprimir la entrada en stdout sin tocar ningún archivo (útil para canalizar hacia una configuración o un Dockerfile), combina -b (contraseña en la línea de comandos) y -n (sin archivo):

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

Los flags que realmente vas a usar:

FlagSignificado
-cCrear un archivo nuevo (sobrescribe si existe): solo el primer usuario
-BUsar bcrypt
-bTomar la contraseña como argumento de línea de comandos (sin prompt)
-nImprimir en stdout en lugar de escribir un archivo
-DEliminar del archivo al usuario indicado

Una advertencia con -b: la contraseña termina en el historial de tu shell. Para credenciales de producción puntuales, prefiere la forma con prompt o la opción del navegador de más abajo.

Sin apache2-utils: usando OpenSSL

¿No tienes el binario htpasswd? OpenSSL está en prácticamente todos los sistemas y puede producir un hash apr1 directamente. Envuélvelo en printf para construir una línea completa:

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

El formato apr1 es portable entre Apache y nginx, lo que lo convierte en la opción con menos dependencias en una máquina mínima.

En el navegador: sin instalar, sin filtraciones

Si no quieres instalar un paquete, o prefieres no escribir una contraseña de producción en una shell donde acaba en ~/.bash_history, genera la entrada del lado del cliente. El generador de htpasswd calcula hashes bcrypt, apr1 y SHA-1 enteramente en tu navegador, te entrega una línea user:hash lista para pegar más un bloque de configuración de servidor que coincide, y nunca transmite nada. Ya que estás ahí, crea una contraseña fuerte y única con el generador de contraseñas aleatorias en lugar de reutilizar una.

Comparación de formatos de contraseña de htpasswd

El comando htpasswd puede emitir cinco formatos, y no son iguales. Esta tabla es la referencia rápida para elegir uno:

FormatoPrefijoCon saltRobustezÚsalo para
bcrypt$2y$El más robustoApache, Docker Registry, Caddy, Traefik
apr1 (Apache MD5)$apr1$Moderadanginx (portable, opción segura por defecto)
SHA-1{SHA}NoDébilSolo compatibilidad heredada
crypt (DES)(ninguno)Sí (2 caracteres)Muy débilNo usar
plain(ninguno)NoNingunaSolo pruebas locales

Algunos detalles no caben en una celda de tabla. bcrypt usa un salt aleatorio de 16 bytes y un factor de costo adaptativo (por defecto 10, recomendación moderna 12), así que contraseñas idénticas producen hashes distintos y el factor de trabajo escala con el hardware. Tiene una rareza: bcrypt trunca la contraseña a los 72 bytes, y cualquier cosa más larga se ignora en silencio. apr1 ejecuta 1.000 rondas de MD5 con salt; mucho más débil que bcrypt, pero implementado de forma nativa tanto por Apache como por nginx, por lo que es la opción portable. SHA-1 no usa salt, así que contraseñas idénticas producen digests idénticos y las rainbow tables aplican: resérvalo solo para sistemas heredados. crypt y plain existen por razones históricas y de pruebas; ninguno tiene lugar en producción.

Los prefijos $2a$ / $2b$ / $2y$

Verás hashes bcrypt que empiezan con $2a$, $2b$ o $2y$. Son el mismo algoritmo y producen hashes equivalentes e intercambiables; las letras de versión son vestigios de correcciones históricas de errores en cómo ciertas bibliotecas manejaban los caracteres de bit alto y la longitud de las cadenas. El htpasswd de Apache emite $2y$, y Caddy, Traefik y Docker Registry lo verifican correctamente.

Si quieres la comparación más profunda de bcrypt frente a las alternativas modernas, la guía de bcrypt vs Argon2 vs scrypt cubre cómo difieren estos algoritmos de hashing de contraseñas en costo, dureza de memoria y modelo de amenazas.

Configurar Basic Auth en tu servidor

Un archivo de credenciales no hace nada por sí solo: hay que decirle al servidor que lo exija. Aquí tienes seis plataformas.

Apache (.htaccess)

Coloca esto en un archivo .htaccess en el directorio que quieres proteger (o en un bloque <Directory> de tu vhost):

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

AuthName es la cadena del realm que muestra el navegador; AuthUserFile es la ruta absoluta a tu archivo de credenciales; Require valid-user acepta a cualquier usuario que figure en él.

nginx (auth_basic)

nginx pone la configuración en un bloque location o server: no hay .htaccess:

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

Recarga con nginx -s reload. Usa el formato apr1 aquí. nginx delega la verificación de bcrypt al crypt() del sistema, que falla en muchas compilaciones (más sobre eso en la sección de diagnóstico), mientras que apr1 se verifica internamente en cualquier plataforma.

Docker Registry y Kubernetes ingress-nginx

El backend htpasswd de un Docker Registry privado acepta solo bcrypt. Genera la entrada, móntala y apunta el registry hacia ella:

# 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

Para Kubernetes ingress-nginx, guarda el archivo como un Secret y referéncialo con anotaciones:

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"

Ten en cuenta que la clave del Secret debe llamarse auth: ingress-nginx busca exactamente esa clave.

Caddy y Traefik

Ambos esperan hashes bcrypt. Caddy usa la directiva basic_auth (pega el hash bcrypt, no el texto plano):

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

Traefik usa un middleware basicauth, con pares user:bcrypt-hash (escapa cualquier $ según el formato de tu configuración):

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

Una vez protegido un endpoint, verifica que funciona desde la línea de comandos. El constructor de comandos cURL arma la solicitud -u user:pass por ti para que puedas confirmar tanto el 401 como el 200 autenticado.

Buenas prácticas de seguridad

Basic Auth es simple, lo que hace que las pocas formas de usarlo mal sean fáciles de detectar.

  • Sirve siempre sobre HTTPS. La credencial es base64 reversible, así que HTTP plano expone la contraseña en la red. Termina TLS delante de cualquier endpoint protegido, sin excepciones.
  • Guarda el archivo fuera del web root. Si .htpasswd está en un directorio servido, una mala configuración puede permitir que alguien lo descargue. Mantenlo en algún lugar como /etc/nginx/.htpasswd, aplícale chmod 640 y que sea propiedad del usuario del servidor web (www-data, nginx) para que el servidor pueda leerlo y otras cuentas no.
  • Usa contraseñas fuertes y únicas. Cada cuenta debe tener su propia contraseña de alta entropía del generador de contraseñas aleatorias, nunca reutilizada. Si quieres entender qué significa “suficientemente fuerte” en bits, el explicador de entropía de contraseñas desglosa las matemáticas.
  • Conoce sus límites. Basic Auth no tiene cierre de sesión ni sesión: el navegador almacena la credencial por realm hasta que lo cierras, y se reenvía en cada solicitud. Para una lista de verificación más amplia sobre hashing, cabeceras y validación, consulta nuestras buenas prácticas de seguridad web.

Solución de errores comunes

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

Este es el fallo de Basic Auth de nginx más común con diferencia, y siempre significa lo mismo: nginx intentó verificar un hash bcrypt ($2y$) en una libc que no incluye el esquema Blowfish, normalmente el musl de Alpine o una glibc antigua. La solución es regenerar la entrada como apr1, que nginx verifica internamente en cualquier plataforma:

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

Cambiar a una imagen base cuya libc soporte bcrypt también funciona, pero apr1 es la solución más simple y portable.

401 incluso con la contraseña correcta

Cuando estás seguro de que la contraseña es correcta pero sigues obteniendo un 401, recorre esta lista en orden:

  1. Ruta del archivo. Confirma que AuthUserFile / auth_basic_user_file apunta al archivo real (ruta absoluta, sin errores de tipeo).
  2. Permisos. El usuario del servidor web debe poder leer el archivo. Comprueba con sudo -u www-data cat /etc/nginx/.htpasswd.
  3. Fin de línea / codificación. Un archivo editado en Windows puede llevar caracteres \r que corrompen el hash. Ejecuta file .htpasswd y aplícale dos2unix si hace falta.
  4. Caché obsoleta del navegador. El navegador almacena las credenciales por realm. Prueba en una ventana privada/incógnito para descartar una contraseña antigua recordada.
  5. Hash que no coincide. Verifica que el hash almacenado realmente coincida con la contraseña: pega ambos en el modo de verificación del generador de htpasswd para confirmarlo antes de culpar a la configuración.

Cuándo NO usar Basic Auth

Basic Auth es la herramienta correcta para un conjunto reducido de tareas: un sitio de staging, una ruta de administración interna, un endpoint de artefactos de CI, un dashboard de métricas, un registry privado. No tiene dependencias y se configura en dos minutos.

Es la herramienta equivocada para el inicio de sesión de un producto. No hay cierre de sesión, ni restablecimiento de contraseña, ni limitación de tasa, ni bloqueo de cuentas, ni MFA. Las credenciales se reenvían en cada solicitud y el navegador las almacena hasta que se cierra. Para cualquier cosa en la que los usuarios inicien sesión, recurre a sesiones, OAuth u OIDC. Basic Auth rinde donde encaja precisamente cuando no le pides lo que no sabe hacer.

Preguntas frecuentes

¿Qué contiene realmente una sola línea de un archivo .htpasswd?

Un par username:hash separado por dos puntos. El hash empieza con un prefijo de algoritmo ($2y$ para bcrypt, $apr1$ para Apache MD5, {SHA} para SHA-1), seguido del salt y el digest. La contraseña en texto plano nunca aparece en el archivo.

¿Cuál es la diferencia entre .htpasswd y .htaccess?

.htaccess es el archivo de configuración por directorio de Apache: activa Basic Auth y apunta al almacén de credenciales. .htpasswd es ese almacén de credenciales, que contiene las líneas username:hash. nginx usa el formato .htpasswd pero configura la autenticación en sus bloques server/location, no en .htaccess.

¿Cómo agrego, cambio o elimino un usuario en un archivo .htpasswd?

Para agregar o cambiar un usuario, ejecuta htpasswd /path/.htpasswd username sin -c: si el usuario existe, su hash se actualiza. Para eliminar uno, ejecuta htpasswd -D /path/.htpasswd username. Usa -c solo para el primer usuario, ya que sobrescribe todo el archivo.

¿Cómo recuerda el navegador las credenciales de Basic Auth y cómo cierran sesión los usuarios?

El navegador almacena las credenciales indexadas por realm y las reenvía automáticamente en cada solicitud que coincida. No hay cierre de sesión estándar: las únicas maneras de borrarlas son cerrar el navegador o limpiar su caché. Ese cierre de sesión ausente es una de las razones por las que Basic Auth no sirve para la autenticación de un producto.

¿Puedo usar el mismo archivo .htpasswd tanto para Apache como para nginx?

Sí, siempre que el formato de hash sea compatible con ambos. apr1 (Apache MD5) se verifica de forma nativa en Apache y nginx en todas partes, así que es la opción compartida más segura. bcrypt funciona en Apache, pero en nginx depende del crypt() del sistema, que falla en compilaciones Alpine/musl.

¿Sigue siendo relevante la autenticación HTTP Basic en 2026?

Sí. Como puerta ligera encima de HTTPS (herramientas internas, entornos de staging, registries privados, endpoints de monitoreo) sigue siendo práctica y sin dependencias. Solo no la confundas con la autenticación de producto de cara al usuario, que necesita sesiones, restablecimientos, limitación de tasa y MFA que Basic Auth no puede ofrecer.

Revisado por el equipo de Go Tools: cada comando, bloque de configuración y formato de hash de esta guía se verificó contra la salida de referencia de Apache htpasswd (apache2-utils) y OpenSSL.

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

Artículos relacionados

Ver todos los artículos