DEV Community

Rasika Dangamuwa
Rasika Dangamuwa

Posted on

Why HTTP Basic Auth Still Breaks in Production: 5 .htpasswd Hashing Traps, Truncation Bugs, and Config Errors

HTTP Basic Authentication is often treated as the default quick-fix for staging environments, internal metric dashboards, and private webhook endpoints. On the surface, the mechanism seems trivial: an incoming Authorization: Basic <base64> header is compared against a list of username-to-hash pairs in an .htpasswd file.

Yet behind this simplicity lies a maze of legacy cryptographic quirks, shell escaping pitfalls, and reverse proxy edge cases that routinely cause production authentication outages or silent security bypasses. Here are the five most common .htpasswd traps and how to avoid them.


1. The 8-Byte Silent Truncation Trap (DES / crypt)

If your deployment scripts invoke legacy htpasswd binaries or use the -d flag (traditional Unix crypt()), passwords are silently truncated after the first 8 bytes.

# Generated with DES crypt:
admin:ab91sU7Xm.YfQ
Enter fullscreen mode Exit fullscreen mode

In this mode, SuperSecretPassword2026! and SuperSec produce the exact same hash. Any attacker who guesses the first 8 characters gains instant access.

The fix: Always use Apache MD5 ($apr1$) or modern Bcrypt ($2y$). Never use DES crypt (-d) or plain unsalted MD5.


2. Algorithm Incompatibilities Across Web Servers

The .htpasswd format supports several distinct hashing algorithms, but server implementations differ significantly:

  • Apache MD5 ($apr1$): Standard and universally supported across Apache, Nginx, and Traefik. It runs 1,000 iterative rounds of MD5 with an 8-character salt.
  • SHA-1 ({SHA}): Base64-encoded raw SHA-1 digest (e.g. user:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=). Crucially, it is completely unsalted. An attacker with access to the hash file can reverse it instantly using precomputed rainbow tables.
  • Bcrypt ($2y$ or $2a$): High-security adaptive hash. Supported by Apache 2.4+ and modern Nginx (compiled with OpenSSL crypt_r), but older microservices and lightweight embedded proxies may fail to parse it.

If you need to quickly inspect existing hashes or generate compatible APR1 and SHA credentials without installing apache2-utils locally, you can use the Nutilz htpasswd generator to inspect hash formats and generate multi-user configurations client-side.


3. Delimiter Collisions and Dollar-Sign Expansion

The .htpasswd file uses a strict colon-delimited format: username:password_hash.

  • Username Colons and Spaces: If a username contains a colon (dev:ops) or leading whitespace, parsers treat the first colon as the delimiter, immediately corrupting the hash field.
  • Bash & Docker Compose $APR1$ Expansion: Because APR1 hashes begin with $apr1$ and SHA-512 hashes begin with $6$, passing .htpasswd strings through Docker Compose environment variables or bash scripts triggers shell variable substitution.
# Broken in docker-compose.yml:
HTPASSWD_CONTENT: "admin:$apr1$salt$hash" # Evaluates $apr1 and $salt as empty variables!

# Fixed (escape dollar signs):
HTPASSWD_CONTENT: "admin:$$apr1$$salt$$hash"
Enter fullscreen mode Exit fullscreen mode

4. Placing .htpasswd Inside the Document Root

A classic configuration vulnerability occurs when .htpasswd is stored in the same directory as public assets (e.g., /var/www/html/.htpasswd). If your web server configuration lacks an explicit block for hidden files, anyone can download your password hashes over HTTP:

# Vulnerable Nginx setup: allows GET /.htpasswd
location / {
    root /var/www/html;
    auth_basic "Restricted";
    auth_basic_user_file /var/www/html/.htpasswd;
}

# Secure configuration: deny access to dotfiles
location ~ /\.(?!well-known).* {
    deny all;
    access_log off;
    log_not_found off;
}
Enter fullscreen mode Exit fullscreen mode

Best practice: Always store .htpasswd outside the web root (e.g. /etc/nginx/.htpasswd or /etc/apache2/.htpasswd).


5. Reverse Proxy Header Stripping and 401 Caching

When running behind API gateways, CDNs, or load balancers (such as Cloudflare, AWS ALB, or Kubernetes Ingress), the Authorization header may be stripped before reaching the origin server.

In Nginx reverse proxies, ensure headers are explicitly forwarded if upstream handles basic auth:

location /internal-api/ {
    proxy_pass http://upstream_backend;
    proxy_set_header Authorization $http_authorization;
    proxy_pass_header Authorization;
}
Enter fullscreen mode Exit fullscreen mode

Furthermore, browsers cache HTTP Basic Auth credentials indefinitely for a given realm. To force a logout, your application must respond with an explicit 401 Unauthorized with a different WWW-Authenticate: Basic realm="NewRealm" header.


Summary Checklist

  1. Avoid DES (-d) and unsalted SHA-1 ({SHA}).
  2. Use $apr1$ or $2y$ (Bcrypt) for broad compatibility and cryptographic resilience.
  3. Escape $ symbols in CI/CD pipelines, Docker Compose, and Kubernetes manifests.
  4. Store .htpasswd outside /var/www/ and verify dotfile denial rules.
  5. Inspect and validate your hash lines before deployment using tools like Nutilz htpasswd generator to catch syntax and algorithm mismatches early.

Top comments (0)