Skip to content
返回博客
安全

如何创建 .htpasswd 文件:HTTP Basic Auth 完全指南

用 bcrypt 或 apr1 创建 .htpasswd 文件,在 Apache、nginx、Docker 和 Kubernetes 上配置 HTTP Basic Auth 并加固安全。2026 实战指南。

11 分钟阅读

如何创建 .htpasswd 文件:HTTP Basic Auth 完全指南

.htpasswd 文件是 HTTP 基本认证(Basic Authentication)在服务端的凭据库:一个纯文本文件,每一行是一对 username:hash。创建 .htpasswd 文件,就是生成这样一行经过哈希的内容,再保存到 Web 服务器能读取的位置。共有三种做法:

  • htpasswd 命令(来自 apache2-utils / httpd-tools)——最经典的工具。
  • openssl passwd——几乎所有系统都已自带,无需额外安装包。
  • 在浏览器里——用 htpasswd 生成器 在本地生成条目,零安装,且不向网络发送任何内容。

本指南不止于一行命令。它会讲清楚基本认证的握手过程如何运作、三种创建文件的方式、五种哈希格式该选哪一种、怎么把它接入 Apache、nginx、Docker、Kubernetes、Caddy 和 Traefik,以及如何加固,避免你发布一个谁都能下载的凭据文件。

什么是 .htpasswd 文件?

.htpasswd 文件的每一行都以冒号分隔的形式保存一个用户的凭据。用户名原样存储;密码只以单向哈希形式存储,因此明文永远不会写入磁盘。一行 bcrypt 记录的结构如下:

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

用户名在最前,接着是一个 :,然后是哈希。用户名最长可达 255 字节,且绝不能包含冒号,因为冒号是字段分隔符。哈希自带算法标记作为前缀(bcrypt 是 $2y$,Apache MD5 是 $apr1$,SHA-1 是 {SHA}),因此服务器无需任何额外配置就知道该如何验证它。

要添加多个用户,每个用户加一行:

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

同一个文件里可以混用不同算法。服务器逐行读取,从前缀识别格式,再用对应算法验证。这里两个 bcrypt 用户和一个 apr1 用户共存,毫无问题。

.htpasswd 与 .htaccess

这两者经常被混淆,因为它们在 Apache 上总是结伴出现,但各自的职责完全不同。.htaccess 是 Apache 的目录级配置文件,里面放的是指令——包括开启基本认证、并指向凭据库的那些指令。.htpasswd凭据数据库——只有 username:hash 这些行,没有任何配置。

一句话:.htaccess 决定某个目录是否需要登录、以及到哪里找用户列表;.htpasswd 就是那份用户列表。nginx 完全不使用 .htaccess——它的基本认证配置写在主配置的 serverlocation 块里,但读取的是同样的 .htpasswd 凭据格式。

HTTP 基本认证如何运作

HTTP 基本认证是内建于 HTTP 规范的质询-响应(challenge-response)握手,搞懂这三步,后面的排错就基本不用查文档了:

  1. 客户端请求受保护资源,但不带凭据。
  2. 服务器回复 401 Unauthorized,并附上 WWW-Authenticate: Basic realm="..." 头——这就是质询。
  3. 客户端重试请求,带上 Authorization: Basic <base64(user:password)> 头。如果凭据匹配 .htpasswd 文件中的某一行,服务器就返回资源。

整个协议就这些。没有登录表单,没有会话 Cookie,也没有 Token。之后的每一个请求都携带同样的头。

401 / WWW-Authenticate 质询

WWW-Authenticate 头做两件事。它的 Basic 令牌告诉客户端使用哪种认证方案,它的 realm 字符串则标注受保护空间。浏览器会在登录对话框里显示 realm 文本(比如「站点提示:……」),并把它当作缓存键:为某个 realm 输入的凭据会复用到同一 realm 下的其他 URL,用户就不必每打开一个页面都重新输入一次。

下面是用 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

Authorization: Basic 头

客户端发送的凭据是 base64(username:password)。这是关于基本认证最重要的一个安全事实:base64 是编码,不是加密。任何人都能完全逆向还原,因此凭据实际上是以明文形式在传输。你可以自己看一遍这个往返过程:

# 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

正因为可逆,基本认证必须跑在 HTTPS 上——没有 TLS,密码在网络路径上的任何人都能读到。如果你想手工构造或检视这个头,Base64 编码/解码器 能在浏览器里完成同样的 user:password 转换。

如何创建 .htpasswd 文件

实践中有三种创建文件的方式。根据你装了什么、以及你希望密码存在哪里来选择。

使用 htpasswd 命令

htpasswd 二进制程序随 Apache 的工具包一起发布。先安装它:

# Debian / Ubuntu
sudo apt install apache2-utils

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

创建文件及其第一个用户。-c 标志意为创建,它会覆盖已有文件——只在第一次使用:

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

要添加更多用户,去掉 -c,这样是追加而不是覆盖:

htpasswd /etc/nginx/.htpasswd alice

要强制使用 bcrypt 而非平台默认算法,加上 -B。要把条目打印到 stdout 而不碰任何文件——方便管道传入配置或 Dockerfile——把 -b(密码写在命令行)和 -n(不写文件)组合起来:

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

你真正会用到的标志:

标志含义
-c创建新文件(如已存在则覆盖)——仅用于第一个用户
-B使用 bcrypt
-b把密码作为命令行参数(不提示输入)
-n打印到 stdout 而非写文件
-D从文件中删除指定用户

使用 -b 有一个要注意的地方:密码会留在你的 shell 历史里。对于一次性的生产凭据,优先用交互提示式,或下面的浏览器方案。

没有 apache2-utils——用 OpenSSL

没有 htpasswd 二进制程序?OpenSSL 几乎装在每个系统上,可以直接生成 apr1 哈希。用 printf 包一层,拼出完整的一行:

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

apr1 格式在 Apache 和 nginx 上都通用,因此在最精简的机器上,它是最不依赖外部组件的路径。

在浏览器里——免安装、不泄露

如果你不想安装任何包,或者不愿意把生产密码敲进会留存到 ~/.bash_history 的 shell,那就在客户端生成条目。htpasswd 生成器 完全在你的浏览器里计算 bcrypt、apr1 和 SHA-1 哈希,给你一行可直接粘贴的 user:hash,外加一段对应的服务器配置块,且不传输任何内容。顺便用 随机密码生成器 生成一个强、且唯一的密码,别再复用旧密码。

htpasswd 密码格式对比

htpasswd 命令可以输出五种格式,而它们并不等价。下面这张表是选格式的速查参考:

格式前缀加盐强度适用场景
bcrypt$2y$最强Apache、Docker Registry、Caddy、Traefik
apr1(Apache MD5)$apr1$中等nginx(通用、安全的默认值)
SHA-1{SHA}仅用于遗留系统兼容
crypt(DES)(无)是(2 字符)极弱不要用
plain(无)仅用于本地测试

有几点放不进表格里。bcrypt 使用随机的 16 字节盐和自适应成本因子(cost factor,默认 10,现代推荐 12),所以相同密码会产生不同哈希,且工作量因子能随硬件提升而调高。它有一个怪癖:bcrypt 会在 72 字节处截断密码,超出的部分被静默忽略。apr1 对加盐的 MD5 跑 1000 轮,强度远不及 bcrypt,但 Apache 和 nginx 都原生实现了它,所以它通用性最好。SHA-1 不加盐,相同密码得到相同摘要,彩虹表(rainbow table)对它有效,只能留给遗留系统。cryptplain 是因为历史和测试原因留下来的,两者都不该出现在生产环境。

$2a$ / $2b$ / $2y$ 前缀

你会看到 bcrypt 哈希以 $2a$$2b$$2y$ 开头。它们是同一个算法,产生的哈希等价、可互换;这些版本字母是历史遗留——源自某些库在处理高位字符和字符串长度时的若干 Bug 修复。Apache 的 htpasswd 输出 $2y$,而 Caddy、Traefik 和 Docker Registry 都能正确验证它。

如果你想深入对比 bcrypt 与现代替代方案,bcrypt vs Argon2 vs scrypt 指南 讲解了这些密码哈希算法在成本、内存硬度和威胁模型上的差异。

在你的服务器上配置基本认证

凭据文件本身什么也做不了——你得告诉服务器去要求它。下面是六个平台。

Apache(.htaccess)

把下面这段放进你想保护的目录里的 .htaccess 文件(或放进 vhost 里的 <Directory> 块):

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

AuthName 是浏览器显示的 realm 字符串;AuthUserFile 是你的凭据文件的绝对路径;Require valid-user 接受其中列出的任何用户。

nginx(auth_basic)

nginx 把配置放在 locationserver 块里——没有 .htaccess

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

nginx -s reload 重载。这里要用 apr1 格式。nginx 把 bcrypt 验证委托给系统的 crypt(),这在许多构建上会失败(排错部分会细说),而 apr1 在所有平台上都由内部验证。

Docker Registry 与 Kubernetes ingress-nginx

私有 Docker Registry 的 htpasswd 后端只接受 bcrypt。生成条目、挂载它,再让 registry 指向它:

# 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

对于 Kubernetes ingress-nginx,把文件存为 Secret,并用注解(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"

注意 Secret 的键必须命名为 auth——ingress-nginx 找的就是这个键。

Caddy 与 Traefik

两者都需要 bcrypt 哈希。Caddy 使用 basic_auth 指令(粘贴 bcrypt 哈希,不是明文):

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

Traefik 使用 basicauth 中间件,用 user:bcrypt-hash 成对配置(按你的配置格式转义所有 $):

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

端点受保护后,从命令行验证它是否生效。cURL 命令构建器 会为你拼好 -u user:pass 请求,让你既能确认 401,也能确认通过认证后的 200

安全最佳实践

基本认证很简单,能出错的地方不多,下面这几条覆盖了绝大部分。

  • 始终通过 HTTPS 提供服务。 凭据是可逆的 base64,所以明文 HTTP 会让密码暴露在链路上。在任何受保护端点前终止 TLS,没有例外。
  • 把文件存放在 Web 根目录之外。 如果 .htpasswd 位于被对外提供的目录里,一次配置失误就可能让别人下载到它。把它放在 /etc/nginx/.htpasswd 之类的位置,设置 chmod 640,并让它归 Web 服务器用户所有(www-datanginx),这样服务器能读、其他账户不能读。
  • 使用强壮、唯一的密码。 每个账户都应从 随机密码生成器 获取自己的高熵密码,绝不复用。如果你想搞清楚「足够强」在比特位数上意味着什么,密码熵详解 拆解了背后的数学。
  • 了解它的局限。 基本认证没有登出、没有会话:浏览器按 realm 缓存凭据,直到你关闭它,并在每个请求上重发。关于哈希、HTTP 头和校验的更全面清单,参见我们的 Web 安全最佳实践

常见错误排查

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

这是 nginx 基本认证最常见的故障,它的含义永远一样:nginx 试图在不含 Blowfish 方案的 libc 上验证 bcrypt$2y$)哈希——通常是 Alpine 的 musl 或较老的 glibc。解决办法是把条目重新生成为 apr1,nginx 在任何平台上都能内部验证它:

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

换成 libc 支持 bcrypt 的基础镜像也行,但 apr1 是更简单、更通用的方案。

密码正确却仍返回 401

当你确信密码无误却还是收到 401 时,按顺序走一遍这份清单:

  1. 文件路径。 确认 AuthUserFile / auth_basic_user_file 指向真实的文件(绝对路径,无拼写错误)。
  2. 权限。 Web 服务器用户必须能读取该文件。用 sudo -u www-data cat /etc/nginx/.htpasswd 检查。
  3. 行尾 / 编码。 在 Windows 上编辑过的文件可能带有 \r 字符,会破坏哈希。运行 file .htpasswd,必要时用 dos2unix 转换。
  4. 浏览器缓存过期。 浏览器按 realm 缓存凭据。用隐私/无痕窗口测试,排除残留的旧密码。
  5. 哈希不匹配。 验证存储的哈希是否真的与密码匹配——把两者都粘进 htpasswd 生成器 的验证模式来确认,再去怀疑配置。

何时不该用基本认证

基本认证适合一小撮特定场景:预发布站点、内部管理路径、CI 制品端点、监控仪表盘、私有 registry。它零依赖,两分钟就能搭好。

它不适合做产品登录。没有登出,没有密码重置,没有限流,没有账户锁定,也没有 MFA。凭据在每个请求上重发,并被浏览器缓存到关闭为止。任何需要用户登录的场景,都应改用会话、OAuth 或 OIDC。守住这条边界,基本认证才能在它真正合适的地方一直好用。

常见问题

.htpasswd 文件里的一行实际包含什么?

一对以冒号分隔的 username:hash。哈希以算法前缀开头(bcrypt 是 $2y$,Apache MD5 是 $apr1$,SHA-1 是 {SHA}),后面跟着盐和摘要。明文密码绝不会出现在文件里。

.htpasswd 和 .htaccess 有什么区别?

.htaccess 是 Apache 的目录级配置文件——它开启基本认证并指向凭据库。.htpasswd 就是那个凭据库,保存着 username:hash 这些行。nginx 使用 .htpasswd 格式,但在它的 server/location 块里配置认证,而不是 .htaccess

我如何在 .htpasswd 文件中添加、修改或移除用户?

要添加或修改用户,运行不带 -chtpasswd /path/.htpasswd username——如果用户已存在,其哈希会被更新。要移除用户,运行 htpasswd -D /path/.htpasswd username。只在创建第一个用户时使用 -c,因为它会覆盖整个文件。

浏览器如何记住基本认证的凭据,用户又怎么登出?

浏览器按 realm 为键缓存凭据,并在每个匹配的请求上自动重发。没有标准的登出方式:唯一能清除它们的办法是关闭浏览器或清空缓存。缺少登出,正是基本认证不适合做产品认证的原因之一。

我能在 Apache 和 nginx 上共用同一个 .htpasswd 文件吗?

可以,只要哈希格式在两者上都受支持。apr1(Apache MD5)在 Apache 和 nginx 上到处都能原生验证,所以它是最稳妥的共享选择。bcrypt 在 Apache 上可用,但在 nginx 上取决于系统的 crypt(),这在 Alpine/musl 构建上会失败。

HTTP 基本认证在 2026 年还有意义吗?

有。作为架在 HTTPS 之上的轻量级门禁——内部工具、预发布环境、私有 registry、监控端点——它依然实用且零依赖。只是别把它误当成面向用户的产品认证,那需要会话、密码重置、限流和 MFA,而这些都是基本认证无法提供的。

由 Go Tools 团队审校:本指南中的每一条命令、每一个配置块以及每一种哈希格式,均已对照 Apache htpasswdapache2-utils)与 OpenSSL 的参考输出进行核验。

标签: htpasswd basic-auth http-authentication nginx apache bcrypt security