DNSTrustCheck

← All tools  |  Guides

Htpasswd Formats Explained: APR1-MD5, bcrypt, and SSHA

An htpasswd file is a plain text list of username:hash lines that Apache or nginx checks against for HTTP Basic Authentication. Which hash format works depends entirely on which server is reading the file, and the two don't agree.

Last updated: August 30, 2026

The three formats side by side

APR1-MD5 is Apache's own variant of the MD5-crypt scheme, identified by a $apr1$ prefix. It's Apache's default as of htpasswd's -m flag, and it's also one of the formats nginx's auth_basic module reads natively, which makes it the one format that works unmodified on both servers.

bcrypt, Apache's -B flag, is described in Apache's own documentation as currently considered very secure, and it's the format Apache recommends for new deployments. Its hashes carry a $2y$ prefix (or $2a$/$2b$ elsewhere) along with a cost factor. nginx's auth_basic module does not support bcrypt at all: a perfectly valid bcrypt htpasswd file will simply fail to authenticate anyone on nginx.

SSHA (salted SHA-1) is written as {SSHA} followed by base64-encoded salted hash data, one of the RFC 2307 {scheme}data formats nginx accepts. It isn't an Apache htpasswd format at all, but it's useful on nginx as an alternative to APR1-MD5. nginx's docs are explicit that its unsalted sibling, plain {SHA}, should not be used for new passwords, since it has no salt and is vulnerable to rainbow table attacks; SSHA's per-password salt avoids that specific weakness.

Why nginx can't use Apache's own recommendation

Apache's documentation calls bcrypt the most secure option it offers, but that recommendation only applies if the file is actually read by Apache. nginx's ngx_http_auth_basic_module documentation lists exactly what it supports: crypt(), Apache's apr1-MD5 variant, and the RFC 2307 schemes, with bcrypt conspicuously absent. Serving the same htpasswd file to both an Apache instance and an nginx instance, common enough behind a load balancer or during a migration, means picking a format both actually support, which rules bcrypt out regardless of which server you'd otherwise prefer it on.

The bcrypt cost factor

bcrypt's cost factor sets how many rounds of internal key setup run, as a power of two, so each single increment roughly doubles both the time to compute one hash and the time an attacker needs per guess. Apache's -C flag accepts a cost from 4 to 17 and defaults to 5. A higher cost is slower for every login attempt too, so it's a real trade-off between login latency and resistance to offline brute-forcing, not a setting to maximize blindly.

Common mistakes

  • Generating a bcrypt htpasswd file and deploying it to nginx, where auth silently fails for every user.
  • Using unsalted SHA-1 ({SHA}) for a new deployment instead of SSHA, when nginx's own docs call that combination out as insecure.
  • Setting a bcrypt cost factor so high that every login takes a noticeable, user-visible delay.
  • Storing an htpasswd file inside a web-servable directory where it can be requested directly instead of outside the document root.
Generate and hash a password

Generate a high-entropy password and get it hashed as APR1-MD5, bcrypt, and SSHA, ready to paste into an htpasswd file.

Open the Htpasswd Generator →

Frequently asked questions

Which htpasswd format should I use?

For Apache, bcrypt (htpasswd -B) is Apache's own documented recommendation and is what its docs call very secure. For nginx, bcrypt isn't an option at all, since ngx_http_auth_basic_module doesn't support it, so APR1-MD5 or SSHA are the choices there.

Why doesn't nginx support bcrypt?

nginx's own documentation lists exactly which formats ngx_http_auth_basic_module accepts: crypt(), Apache's apr1-MD5 variant, and the RFC 2307 {scheme}data schemes (PLAIN, SHA, SSHA). bcrypt is simply not on that list, so a bcrypt-hashed htpasswd file will fail to authenticate anyone on nginx even though the file itself is valid.

Is APR1-MD5 secure?

It's dated compared to bcrypt, but it isn't a single fast hash either: it runs 1,000 rounds of MD5 as a form of key stretching, which is meaningfully more resistant to brute-forcing than a single unsalted hash like plain SHA-1. It's also the one format both Apache and nginx accept, which is why it's still Apache's own default.

What does the bcrypt cost factor control?

It sets how many rounds of bcrypt's internal key-setup run, as a power of two, so each increment roughly doubles the time it takes to compute (and to brute-force) a single hash. Apache's htpasswd -C flag accepts a cost from 4 to 17, defaulting to 5; higher costs are slower to compute but harder to attack.

Sources