How to Password-Protect a Directory in Nginx with HTTP Basic Auth

A minimalist green Nginx logo with a beige padlock secured over it.

We may earn a referral fee at no additional cost to you when you shop through the links on this page.

A quick password lock on a staging site, admin path, or private dashboard often does not need a full login system. nginx has it built in through HTTP Basic Authentication, and this DevAnswers guide installs the password tool, creates a credentials file, switches on auth_basic, and verifies it with a 401-then-200 test.

Prerequisites 

Before you start, make sure you have:

  • nginx already installed and serving a site
  • A shell with sudo or root access
  • A server block (or a specific location) you want to protect

Install the Password File Tool 

nginx does the authentication itself through its built-in auth_basic module, so there is no separate auth server to install. All it needs is a small file of usernames and hashed passwords to check against. The usual tool to generate that file is htpasswd, which on Debian and Ubuntu ships in the apache2-utils package; despite the name, you are installing a tiny helper, not Apache itself:

Terminal
sudo apt update
sudo apt install apache2-utils

You do not actually need Apache for this; the package just bundles the htpasswd tool. If you would rather not install it at all, there is a pure-openssl alternative further down.

Create the Password File 

Create the file with htpasswd. The -c flag creates a new file, so use it only the first time. Store the file outside your web root (under /etc/nginx/ is conventional) so it can never be served as a download:

Terminal
sudo htpasswd -c /etc/nginx/.htpasswd billy
Output
New password:
Re-type new password:
Adding password for user billy
htpasswd -c overwrites the file if it already exists, wiping every account in it. Use -c only when creating the file the first time; to add more users, run htpasswd without it.

To add a second user, drop the -c so the existing entries are kept:

Terminal
sudo htpasswd /etc/nginx/.htpasswd ciara

Pick a strong, random secret for each account, since this is your only barrier; a password generator is handy here. Each line in the file is just username:hashed-password. nginx accepts several hash formats, including the Apache APR1 hash that both htpasswd and openssl‘s -apr1 flag produce, so if you skipped apache2-utils you can append an entry with openssl instead:

Terminal
# Only if you did NOT install apache2-utils above
echo "billy:$(openssl passwd -apr1)" | sudo tee -a /etc/nginx/.htpasswd

Turn On Basic Auth in Nginx 

nginx’s built-in ngx_http_auth_basic_module does the checking. In the server or location block you want to protect, add two directives: auth_basic with a realm name (shown in the browser prompt), and auth_basic_user_file pointing at the file you just made. Add this inside your site’s server block:

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

That protects just the /admin/ path. To lock the entire site instead, put the same two directives in the server block rather than a location block, and every request will need credentials. You can also carve out an exception: drop auth_basic off; into a nested location (for example a public /admin/status health check) to lift the prompt for that sub-path only.

The password file only needs to be readable by the nginx worker process, not world-writable, so the defaults under /etc/nginx/ are fine. Test the configuration and reload nginx to apply it:

Terminal
sudo nginx -t && sudo systemctl reload nginx
Output
nginx: configuration file /etc/nginx/nginx.conf test is successful

Test It: 401 Then 200 

Confirm the lock works with a quick local check from the command line (plain http is fine on the box itself; a public site must be served over HTTPS, as the summary explains). A request with no credentials should be refused with 401 Unauthorized, because nginx returns that status (with a WWW-Authenticate header) whenever the credentials are missing or do not match the file:

Terminal
curl -I http://devanswers.test/admin/
Output
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Restricted Area"

Now send a valid username and password with -u. Correct credentials are required to get in, so this returns 200 OK:

Terminal
curl -I -u billy:your-password http://devanswers.test/admin/
Output
HTTP/1.1 200 OK

In a browser, that same 401 is what triggers the native username/password dialog, with your realm name (“Restricted Area”) shown as the label. If you see a 500 instead, nginx usually cannot read the password file: check the path and permissions in the nginx error log. And because browsers cache Basic Auth credentials for the session, close the tab (or use a private window) when you want to re-test the prompt from scratch.

Let Trusted IPs Skip the Prompt 

You can combine the password with an IP allowlist so your own office or VPN is let straight through while everyone else is challenged. nginx controls this with the satisfy directive: set it to any and access is granted if either the address matches or the password is correct.

Nginx
location /admin/ {
    satisfy any;

    allow 203.0.113.10;   # your trusted IP
    deny all;

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

Requests from 203.0.113.10 now skip the password box; every other address still has to authenticate. This is handy for letting an uptime monitor, a CI runner, or your office VPN through by address while human visitors elsewhere still get the prompt. Use satisfy all instead if you want to require both a trusted IP and a password.

Summary 

That is the whole flow: install apache2-utils, build a credentials file with htpasswd, point auth_basic and auth_basic_user_file at it, reload, and verify with a 401-then-200 check. One important caveat: Basic Auth sends the username and password base64-encoded, not encrypted, so they are effectively cleartext on the wire. It is only safe over an encrypted connection, so always serve a protected site over HTTPS with a TLS certificate.

To further secure your site against brute-force guessing, you can install and configure Fail2ban to automatically block IPs that repeatedly fail authentication.

Support DevAnswers
Written, tested and maintained by one developer. Your support keeps these guides accurate and ad-light.
Buy me a coffee

Comments

Leave a reply