How to Implement HTTP Authentication in Nginx

How to Implement HTTP Authentication in Nginx

How to Implement HTTP Authentication in Nginx

There’s a specific moment I remember from early in my sysadmin days: I’d just finished deploying a staging environment for a client project, and within an hour, someone unrelated to the project had stumbled onto the URL and was poking around. Nothing sensitive was exposed, but it was a wake-up call. Not every environment needs a full authentication system with sessions and databases — sometimes you just need a lock on the door. That’s exactly what HTTP Basic Authentication in Nginx gives you, and it takes about five minutes to set up.

In this article, I’ll walk through everything I know about implementing HTTP authentication in Nginx — Basic Auth primarily, but I’ll also touch on Digest Auth and how this compares to more modern auth approaches.

What Is HTTP Authentication and When Should You Use It?

HTTP Basic Authentication is a simple challenge-response mechanism built into the HTTP protocol itself. When a client requests a protected resource, the server responds with a 401 Unauthorized status and a WWW-Authenticate header. The browser then prompts the user for a username and password, which get base64-encoded (not encrypted!) and sent in the Authorization header on subsequent requests.

I reach for this when:

I do not use it for:

Requirements

Step 1: Install the htpasswd Utility

Nginx doesn’t ship its own password-file generator, so we borrow Apache’s htpasswd tool, which produces a compatible file format.

On Ubuntu/Debian:

sudo apt update
sudo apt install apache2-utils -y

On CentOS/RHEL:

sudo yum install httpd-tools -y

Step 2: Create the Password File

I create a dedicated directory to keep credential files separate from web content:

sudo mkdir -p /etc/nginx/auth

Now create the first user with the -c flag (which creates a new file — only use -c the first time, or you’ll overwrite existing users):

sudo htpasswd -c /etc/nginx/auth/.htpasswd adminuser

You’ll be prompted to enter and confirm a password. Add additional users without the -c flag:

sudo htpasswd /etc/nginx/auth/.htpasswd seconduser

You can verify the file contents (passwords are hashed, not stored in plaintext):

cat /etc/nginx/auth/.htpasswd

You’ll see something like:

adminuser:$apr1$H6uV9d5x$rIx3rP...
seconduser:$apr1$K8jQ2f7z$sN2xLmP...

By default, htpasswd uses MD5-based hashing (apr1). I prefer bcrypt for stronger security:

sudo htpasswd -B -c /etc/nginx/auth/.htpasswd adminuser

The -B flag forces bcrypt hashing, which is significantly more resistant to brute-force cracking than the default.

Step 3: Protect Nginx Locations with Basic Auth

Now edit your site configuration:

sudo nano /etc/nginx/sites-available/example.com

To protect the entire site:

server {
    listen 80;
    server_name example.com;

    auth_basic "Restricted Area";
    auth_basic_user_file /etc/nginx/auth/.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

To protect only a specific path (which I do more often — for example, just an /admin panel):

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }

    location /admin/ {
        auth_basic "Admin Area";
        auth_basic_user_file /etc/nginx/auth/.htpasswd;
        proxy_pass http://127.0.0.1:3000;
    }
}

auth_basic sets the realm name shown in the browser’s login prompt — it’s just a label, not a security feature, but I make it descriptive so users understand what they’re logging into.

Test and reload:

sudo nginx -t
sudo systemctl reload nginx

Now visiting the protected path will trigger a browser login prompt.

Step 4: Excluding Specific Paths from Auth

Sometimes you want most of a directory protected but need to exclude something — health check endpoints are the classic example, since monitoring tools usually can’t handle a login prompt.

location /admin/ {
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/auth/.htpasswd;
    proxy_pass http://127.0.0.1:3000;
}

location /admin/health {
    auth_basic off;
    proxy_pass http://127.0.0.1:3000;
}

The more specific location block (/admin/health) takes precedence over the broader /admin/ block, so this correctly bypasses auth just for the health check.

Step 5: Testing Authentication

From the command line, test with curl:

# Without credentials - should return 401
curl -I http://example.com/admin/

# With credentials
curl -I -u adminuser:yourpassword http://example.com/admin/

A successful request should return 200 OK; a missing or incorrect credential should return 401 Unauthorized with a WWW-Authenticate: Basic realm="Admin Area" header.

You can also test in a browser — navigating to the protected URL should pop up a native login dialog rather than your application’s own login page.

Combining Basic Auth with IP Whitelisting

I frequently layer this with IP restrictions for extra protection — useful for admin panels I only want accessible from the office or VPN:

location /admin/ {
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/auth/.htpasswd;

    allow 203.0.113.10;
    allow 203.0.113.0/24;
    deny all;

    proxy_pass http://127.0.0.1:3000;
}

With satisfy all; (the default), a request must pass both the IP check and the password check. If you want either condition to be sufficient, use satisfy any; instead.

HTTP Digest Authentication (and Why I Rarely Use It)

Digest Authentication was designed to improve on Basic Auth by hashing credentials before sending them, rather than just base64-encoding them. In theory it’s more secure over plain HTTP. In practice, Nginx’s open-source version doesn’t support Digest Authentication natively — it requires a third-party module or Nginx Plus (the commercial version).

Given that:

  1. HTTPS has become the default everywhere (thanks to free certificates from Let’s Encrypt), which already encrypts Basic Auth credentials in transit.
  2. Digest Auth adds complexity for a security benefit that HTTPS already provides.

I just use Basic Auth over HTTPS and skip Digest entirely. If you truly need Digest Auth, look into the nginx-http-auth-digest third-party module, but be aware it requires recompiling Nginx with the module included.

Troubleshooting Common Issues

401 loop, credentials never accepted — Almost always a path issue. Double check the auth_basic_user_file path is correct and readable by the Nginx worker process user (www-data or nginx):

sudo chown root:www-data /etc/nginx/auth/.htpasswd
sudo chmod 640 /etc/nginx/auth/.htpasswd

“htpasswd: command not found” — You haven’t installed apache2-utils / httpd-tools yet.

Auth not applying to a subpath — Nginx location matching can be tricky. Remember that a more specific location block overrides a less specific one, and if you have auth_basic off; somewhere in a parent block that isn’t intended, it can silently disable protection.

Basic Auth prompt appears but never succeeds even with correct password — Check whether you generated the htpasswd file with a hashing algorithm Nginx doesn’t support. Older Nginx builds (pre-1.3.13 for SHA-based hashes on some systems) may not support all htpasswd hash types — bcrypt (-B) generally works fine on modern Nginx (1.9+).

Credentials appearing in server logs — By default Nginx doesn’t log Authorization headers, but double check any custom log_format directives you’ve added don’t accidentally include $http_authorization.

Security Considerations

sudo chmod 640 /etc/nginx/auth/.htpasswd
sudo chown root:www-data /etc/nginx/auth/.htpasswd

Performance Tips

HTTP Basic Auth has negligible performance overhead — it’s a simple hash comparison per request. The main performance considerations are:

Real-World Use Cases

Best Practices I Follow

  1. Always pair Basic Auth with HTTPS — never serve it over plain HTTP in production.
  2. Store .htpasswd files outside the public web root, with restrictive file permissions.
  3. Use bcrypt hashing (-B) rather than the legacy MD5-based default.
  4. Layer IP whitelisting with Basic Auth for sensitive admin areas when possible.
  5. Use descriptive realm names so users understand what they’re authenticating into.
  6. Exclude health-check and monitoring endpoints from auth requirements explicitly.
  7. Periodically audit and rotate the users in your .htpasswd file.
  8. For anything beyond simple internal protection, graduate to proper application-level authentication (OAuth2, SSO) rather than stretching Basic Auth further than it’s meant to go.

Wrapping Up

HTTP Basic Authentication in Nginx isn’t glamorous, but it’s one of those tools that quietly saves you from a lot of embarrassing exposure — staging sites indexed by Google, internal dashboards discovered by accident, admin panels left wide open. It takes minutes to set up and, paired with HTTPS, provides a genuinely useful layer of protection for anything that isn’t meant to be fully public.

I’d encourage you to go set this up right now on any staging or internal server you have exposed without protection. It really is a five-minute job, and the peace of mind is worth far more than the setup time.

Exit mobile version