NGINX / Security

NGINX Basic Auth with htpasswd: Complete Configuration Guide

by , , revisited on


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

NGINX basic authentication provides a simple yet effective way to restrict access to your web resources. Whether you need to protect a staging site, secure an admin panel, or add an extra layer of security to sensitive areas, understanding how to properly configure nginx htpasswd authentication is essential for any system administrator.

In this comprehensive guide, you’ll learn everything about implementing nginx basic auth—from creating password files with htpasswd to advanced configurations for protecting WordPress admin areas and staging environments.

Understanding How NGINX Basic Auth Works

Before diving into configuration, it’s worth understanding what happens under the hood. When a client requests a protected resource, NGINX checks for the Authorization header. If missing, it returns a 401 Unauthorized response with a WWW-Authenticate: Basic realm="Your Realm" header, prompting the browser to display a login dialog.

The browser then sends credentials as a Base64-encoded username:password string. NGINX decodes this and compares the password hash against entries in your htpasswd file.

Looking at the NGINX source code (ngx_http_auth_basic_module.c), the authentication happens in the access phase—after URL rewriting but before content generation. This is important because it means authentication is enforced regardless of what content handler processes the request. For more on how location blocks are matched, see our guide on NGINX location priority.

Supported Password Hash Formats

NGINX supports multiple password hash formats through its ngx_crypt() function:

Format Prefix Security Level Notes
APR1 MD5 $apr1$ Moderate Apache-compatible, 1000 iterations
bcrypt $2y$ High CPU-intensive, recommended
SHA-512 $6$ High System crypt, configurable rounds
SHA-256 $5$ High System crypt, configurable rounds
SHA-1 {SHA} Low Deprecated, avoid
Plain {PLAIN} None Development only

The NGINX source explicitly zeros password buffers after use (ngx_explicit_memzero) to prevent memory disclosure—a detail that shows the developers take security seriously.

Installing htpasswd

The htpasswd utility comes with the Apache HTTP server tools package. Despite its Apache origins, it’s the standard tool for creating password files compatible with NGINX basic auth.

On RHEL/Rocky Linux/AlmaLinux:

dnf install httpd-tools

On Debian/Ubuntu:

apt install apache2-utils

On Alpine Linux:

apk add apache2-utils

Creating Your First htpasswd File

The htpasswd command creates and manages password files. Here’s how to create a new file with your first user:

htpasswd -c /etc/nginx/.htpasswd admin

The -c flag creates a new file (overwriting any existing one). You’ll be prompted to enter and confirm the password:

New password:
Re-type new password:
Adding password for user admin

Important: Only use -c when creating a new file. Using it on an existing file will erase all other users.

Adding Additional Users

To add more users to an existing file, omit the -c flag:

htpasswd /etc/nginx/.htpasswd developer

Non-Interactive Password Creation

For scripts and automation, use the -b flag to provide the password on the command line:

htpasswd -b /etc/nginx/.htpasswd api_user MySecurePassword123

Security note: This exposes the password in your shell history and process list. For better security in scripts, use the -i flag to read from stdin:

echo "MySecurePassword123" | htpasswd -i /etc/nginx/.htpasswd api_user

Choosing a Password Hash Algorithm

By default, htpasswd uses APR1 MD5 hashing. For better security, consider using bcrypt or SHA-512:

Bcrypt (recommended for maximum security):

htpasswd -B /etc/nginx/.htpasswd secure_user

The resulting hash looks like: secure_user:$2y$05$...

You can adjust the bcrypt cost factor (higher = slower but more secure):

htpasswd -B -C 10 /etc/nginx/.htpasswd secure_user

SHA-512 (excellent security with configurable rounds):

htpasswd -5 /etc/nginx/.htpasswd secure_user

The hash prefix is $6$ for SHA-512. You can specify the number of rounds:

htpasswd -5 -r 10000 /etc/nginx/.htpasswd secure_user

Viewing and Managing Users

To see which users exist in your htpasswd file:

cat /etc/nginx/.htpasswd

Output shows usernames and their hashed passwords:

admin:$apr1$vZoPBgxo$aLU58eCINcYB/7JWTF3Kd0
developer:$2y$05$K20V4yqxtMD/yYK8JDYCoOuojts.5.lEUftl7DeTJxAewWU/K0JKS

To delete a user:

htpasswd -D /etc/nginx/.htpasswd developer

To update a user’s password, simply add them again (without -c):

htpasswd /etc/nginx/.htpasswd admin

OpenSSL Alternative for Password Generation

If htpasswd isn’t available, you can use OpenSSL to generate compatible password hashes. This is particularly useful on minimal systems or when you need to generate passwords programmatically.

Generate an APR1 MD5 hash:

openssl passwd -apr1 "yourpassword"

Output: $apr1$1MFCM5cr$D4jG4xLTYqmSIsGkVg.wl/

Generate a SHA-512 hash:

openssl passwd -6 "yourpassword"

Create a complete htpasswd line:

echo "admin:$(openssl passwd -apr1 'yourpassword')" > /etc/nginx/.htpasswd

Each time you run openssl passwd, it generates a different hash due to random salt—this is expected and correct behavior. The salt is embedded in the hash itself.

Basic NGINX Configuration for Password Protection

Now let’s configure NGINX to use your htpasswd file. The two essential directives are:

  • auth_basic — Sets the authentication realm (the name shown in the browser’s login dialog)
  • auth_basic_user_file — Path to your htpasswd file

Protecting an Entire Server Block

server {
    listen 80;
    server_name staging.example.com;

    root /var/www/staging;

    auth_basic "Staging Environment";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        try_files $uri $uri/ =404;
    }
}

Every request to this server requires authentication.

Protecting a Specific Location

server {
    listen 80;
    server_name example.com;

    root /var/www/html;

    location / {
        # Public access
        try_files $uri $uri/ =404;
    }

    location /admin {
        auth_basic "Admin Area";
        auth_basic_user_file /etc/nginx/.htpasswd;
        try_files $uri $uri/ =404;
    }
}

Disabling Auth for Specific Locations

When you need most of a directory protected but specific files public, use auth_basic off:

location /admin {
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    try_files $uri $uri/ =404;
}

# Allow public access to specific file
location = /admin/health-check.php {
    auth_basic off;
    try_files $uri =404;
}

Protecting WordPress Admin Areas

WordPress sites are frequent targets for brute-force attacks. Adding nginx basic auth creates a powerful additional barrier before attackers even reach wp-login.php.

Complete WordPress Protection Configuration

server {
    listen 80;
    server_name example.com;

    root /var/www/wordpress;
    index index.php;

    # Main site - public access
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # Protect wp-login.php
    location = /wp-login.php {
        auth_basic "WordPress Login";
        auth_basic_user_file /etc/nginx/.htpasswd;

        include fastcgi_params;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Protect wp-admin directory
    location /wp-admin {
        auth_basic "WordPress Admin";
        auth_basic_user_file /etc/nginx/.htpasswd;

        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass unix:/run/php-fpm/www.sock;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }
    }

    # CRITICAL: Allow admin-ajax.php without auth
    # Many plugins and themes use AJAX on the frontend
    location = /wp-admin/admin-ajax.php {
        auth_basic off;

        include fastcgi_params;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # PHP handling for other files
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Why exclude admin-ajax.php? WordPress uses admin-ajax.php for frontend AJAX requests—contact forms, live search, add-to-cart functionality, and countless plugin features depend on it. Requiring authentication would break these features for regular visitors.

Protecting xmlrpc.php

The XML-RPC endpoint is another common attack vector. If you don’t use it (most modern WordPress setups don’t), block it entirely:

location = /xmlrpc.php {
    deny all;
    return 444;
}

If you need XML-RPC (for Jetpack or the WordPress mobile app), protect it with basic auth:

location = /xmlrpc.php {
    auth_basic "XML-RPC Access";
    auth_basic_user_file /etc/nginx/.htpasswd;

    include fastcgi_params;
    fastcgi_pass unix:/run/php-fpm/www.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Protecting Staging and Development Sites

Staging sites often contain sensitive data or features not ready for public viewing. Basic authentication is an effective way to restrict access without complex application-level changes.

Full Staging Site Protection

server {
    listen 80;
    server_name staging.example.com;

    root /var/www/staging;
    index index.php index.html;

    # Require authentication for everything
    auth_basic "Staging - Authorized Personnel Only";
    auth_basic_user_file /etc/nginx/.htpasswd_staging;

    # Your normal location blocks follow
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Different Credentials for Different Environments

Maintain separate htpasswd files for different purposes:

# Production admin access
htpasswd -cB /etc/nginx/.htpasswd_admin admin

# Staging site access (shared with QA team)
htpasswd -cB /etc/nginx/.htpasswd_staging qa_team
htpasswd -B /etc/nginx/.htpasswd_staging developer1
htpasswd -B /etc/nginx/.htpasswd_staging developer2

# Client preview access
htpasswd -cB /etc/nginx/.htpasswd_preview client

Combining Basic Auth with SSL/TLS

Basic authentication sends credentials in Base64 encoding—easily decoded if intercepted. Always use HTTPS when implementing basic auth. For a complete guide on hardening your TLS configuration, see our article on NGINX TLS 1.3 hardening.

Secure HTTPS Configuration with Basic Auth

server {
    listen 80;
    server_name secure.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name secure.example.com;

    ssl_certificate /etc/letsencrypt/live/secure.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/secure.example.com/privkey.pem;

    # Modern SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    root /var/www/secure;

    # Now basic auth is transmitted over encrypted connection
    auth_basic "Secure Area";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        try_files $uri $uri/ =404;
    }
}

The HTTP to HTTPS redirect ensures credentials are never sent over an unencrypted connection.

IP-Based Access with Basic Auth Fallback

For internal applications, you might want to allow access without authentication from trusted IP addresses while requiring credentials from everywhere else.

location /admin {
    satisfy any;

    # Allow without auth from office IP
    allow 203.0.113.50;
    # Allow without auth from VPN range
    allow 10.8.0.0/24;
    deny all;

    # Require auth from all other IPs
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;

    try_files $uri $uri/ =404;
}

The satisfy any directive means the request is allowed if either the IP is whitelisted or valid credentials are provided. Use satisfy all if you want to require both conditions.

Troubleshooting Common Issues

Authentication Not Being Enforced

If requests succeed without credentials, check:

  1. File permissions: NGINX worker process (usually nginx user) must read the htpasswd file:
chmod 644 /etc/nginx/.htpasswd
chown root:nginx /etc/nginx/.htpasswd
  1. File path: Use absolute paths in auth_basic_user_file

  • Location matching: Ensure your protected location actually matches the requested URL

  • SELinux (RHEL/Rocky/Alma): The htpasswd file needs proper context:

  • restorecon -v /etc/nginx/.htpasswd
    

    “Password Mismatch” Errors

    Check the NGINX error log:

    tail -f /var/log/nginx/error.log
    

    Common causes:
    – Password was changed but not reloaded in NGINX
    – Hash format not supported by system’s crypt library
    – Special characters in password causing shell escaping issues

    401 Response Even with Correct Credentials

    Verify the password works:

    # Test with curl
    curl -u admin:yourpassword http://localhost/protected-area/
    

    If it works with curl but not the browser, clear browser cache and stored credentials.

    Security Best Practices

    File Permissions

    Store htpasswd files outside the web root and restrict permissions:

    # Create secure directory
    mkdir -p /etc/nginx/auth
    chmod 750 /etc/nginx/auth
    
    # Create htpasswd file with restricted permissions
    htpasswd -cB /etc/nginx/auth/.htpasswd admin
    chmod 640 /etc/nginx/auth/.htpasswd
    chown root:nginx /etc/nginx/auth/.htpasswd
    

    Use Strong Passwords

    Basic auth passwords should be strong since they’re protecting web resources:

    # Generate a secure random password
    openssl rand -base64 16
    

    Implement Rate Limiting

    Combine basic auth with NGINX rate limiting to prevent brute-force attacks:

    # Define rate limiting zone
    limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m;
    
    location /admin {
        # Apply strict rate limiting
        limit_req zone=auth_limit burst=3 nodelay;
    
        auth_basic "Admin Area";
        auth_basic_user_file /etc/nginx/.htpasswd;
    
        try_files $uri $uri/ =404;
    }
    

    This limits authentication attempts to 5 per minute per IP address.

    Log Authentication Attempts

    The $remote_user variable contains the authenticated username. Include it in your log format:

    log_format auth_log '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent"';
    
    access_log /var/log/nginx/auth_access.log auth_log;
    

    Additional Security Layers

    For production environments, consider adding the OWASP ModSecurity Core Rule Set to protect against common web attacks beyond authentication bypass.

    Testing Your Configuration

    Always test NGINX configuration before reloading:

    nginx -t
    

    If successful, reload NGINX:

    systemctl reload nginx
    

    Verify authentication works with curl:

    # Should return 401
    curl -I http://localhost/admin/
    
    # Should return 200
    curl -I -u admin:password http://localhost/admin/
    

    Conclusion

    NGINX basic auth with htpasswd provides a straightforward, effective method for restricting access to web resources. While it shouldn’t replace application-level authentication for user accounts, it’s ideal for:

    • Protecting staging and development environments
    • Adding an extra security layer to admin areas
    • Restricting access to internal tools and dashboards
    • Quick access control without application changes

    Remember to always use HTTPS when implementing basic auth, choose strong hashing algorithms like bcrypt, and combine with rate limiting for defense in depth. With proper configuration, nginx basic auth remains a valuable tool in your web security arsenal.

    For more NGINX security topics, explore our guides on NGINX reverse proxy configuration and 502 Bad Gateway troubleshooting.

    D

    Danila Vershinin

    Founder & Lead Engineer

    NGINX configuration and optimizationLinux system administrationWeb performance engineering

    10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    You may use these HTML tags and attributes:

    <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

    This site uses Akismet to reduce spam. Learn how your comment data is processed.