Skip to main content

Password Protection with .htaccess

Sometimes you need to restrict access to a page, a directory, or an entire site to only those who know the password. Whether it is a staging environment that clients should not stumble upon, an admin panel that needs an extra layer of defense, or a private section for internal team members, Apache's built-in HTTP authentication provides a straightforward solution without writing any application code.

This guide covers everything you need to set up password protection using .htaccess: creating and managing password files, configuring authentication directives, choosing between user-level and group-level access, and understanding the security limitations you should be aware of before relying on this mechanism in production.

htpasswd Files

The foundation of Apache's password protection is the htpasswd file, a plain text file that stores usernames and their hashed passwords. Apache reads this file every time someone attempts to access a protected resource and compares the credentials they provide against the entries in the file.

Creating an htpasswd File

The htpasswd command-line utility (included with every Apache installation) creates and manages password files. Here is how to use it.

Create a new file with the first user:

htpasswd -c /etc/apache2/.htpasswd admin
  • The -c flag creates a new file. If the file already exists, it is overwritten completely.
  • /etc/apache2/.htpasswd is the path where the file will be stored.
  • admin is the username.

After running this command, you are prompted to enter and confirm a password:

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

Add additional users to an existing file:

htpasswd /etc/apache2/.htpasswd editor
htpasswd /etc/apache2/.htpasswd john

Notice there is no -c flag when adding to an existing file. This is critical.

warning

The -c flag destroys the existing file and creates a new one. If you accidentally use -c when adding a second user, you will lose all previously created users. Only use -c when creating the file for the very first time.

Update an existing user's password:

htpasswd /etc/apache2/.htpasswd admin

If the username already exists in the file, htpasswd replaces the old password with the new one.

Delete a user:

htpasswd -D /etc/apache2/.htpasswd john

The -D flag removes the specified user from the file.

View the contents of the file:

cat /etc/apache2/.htpasswd

Output:

admin:$apr1$xyz12345$A1B2C3D4E5F6G7H8I9J0K1
editor:$apr1$abc67890$L2M3N4O5P6Q7R8S9T0U1V2

Each line contains a username and a hashed password separated by a colon. The password is never stored in plain text.

Storing It Outside the Web Root

The htpasswd file contains password hashes that, while not plain text, should never be accessible via a browser. If someone can download your htpasswd file, they can run offline brute-force attacks against the hashes at their leisure.

Wrong approach:

/var/www/html/              ← Document root (publicly accessible)
├── index.html
├── .htaccess
├── .htpasswd ← DANGER: Inside the web root!
└── admin/
└── index.php

If the file is inside your document root, a direct request to https://example.com/.htpasswd could potentially serve its contents. While Apache blocks .ht* files by default, misconfigurations, alternative web servers, or proxy setups could expose it.

Correct approach:

/etc/apache2/.htpasswd      ← Outside the web root (not accessible via HTTP)

/var/www/html/ ← Document root
├── index.html
├── .htaccess
└── admin/
└── index.php

Store the htpasswd file in a location that is completely outside the document root. Common locations include:

  • /etc/apache2/.htpasswd
  • /home/username/.htpasswd (on shared hosting)
  • /var/www/.htpasswd (one level above the typical document root)

Your .htaccess file references the htpasswd file using its absolute filesystem path, so it works regardless of where the file is located.

tip

If you are on shared hosting and cannot store files outside the web root, make sure Apache's default protection for .ht* files is in place. Add this to your .htaccess as a safety net:

<FilesMatch "^\.ht">
Require all denied
</FilesMatch>

This ensures the .htpasswd file returns 403 Forbidden even if someone requests it directly.

Password Hashing

The htpasswd utility supports several hashing algorithms. The algorithm determines how securely the passwords are stored and how resistant they are to brute-force attacks.

Default behavior (recommended):

# Uses the default algorithm (bcrypt on most modern systems, or APR1/MD5)
htpasswd /etc/apache2/.htpasswd username

Explicitly specify bcrypt (most secure):

htpasswd -B /etc/apache2/.htpasswd username

The -B flag forces bcrypt hashing, which is the strongest option available.

Specify bcrypt cost factor:

htpasswd -B -C 12 /etc/apache2/.htpasswd username

The -C flag sets the bcrypt cost factor (default is 5, range is 4-17). Higher values are slower to compute, making brute-force attacks more expensive. A value of 10-12 is a good balance between security and login speed.

Here is a comparison of the available algorithms:

FlagAlgorithmHash Example PrefixSecurity LevelNotes
-Bbcrypt$2y$StrongestRecommended. Slow by design.
noneAPR1 (MD5)$apr1$ModerateDefault on many systems. Adequate.
-dcryptNo prefixWeakLegacy Unix crypt. Only 8-char passwords.
-sSHA-1{SHA}WeakNo salt. Vulnerable to rainbow tables.
-pPlain textNo prefixNoneNever use. Passwords stored in clear.

Wrong approach:

# Plain text storage - anyone who reads the file sees all passwords
htpasswd -p /etc/apache2/.htpasswd username

# SHA-1 without salt - vulnerable to precomputed attacks
htpasswd -s /etc/apache2/.htpasswd username

Correct approach:

# Bcrypt - secure, salted, computationally expensive to crack
htpasswd -B /etc/apache2/.htpasswd username
caution

Some very old Apache installations (pre-2.4) may not support bcrypt. In those cases, APR1/MD5 (the default) is the best available option. If you are running Apache 2.4 or later, always use bcrypt with the -B flag.

AuthType Basic

With the htpasswd file created, the next step is configuring .htaccess to require authentication. The AuthType directive specifies which authentication scheme Apache should use.

AuthType Basic

Basic authentication works by having the browser prompt the user for a username and password. The browser then sends these credentials with every subsequent request to the protected area, encoded in Base64 in the Authorization header.

Here is a minimal, complete configuration:

AuthType Basic
AuthName "Restricted Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

When a user visits a page protected by this configuration, their browser displays a login dialog. After entering valid credentials, they gain access. The browser remembers the credentials for the session (until the browser is closed or the cache is cleared).

How Basic Auth Works Under the Hood

The exchange between browser and server follows this sequence:

  1. Client requests the protected resource.
  2. Server responds with 401 Unauthorized and a WWW-Authenticate: Basic realm="Restricted Area" header.
  3. Browser shows a login dialog to the user.
  4. User enters username and password.
  5. Browser sends the request again with an Authorization: Basic dXNlcjpwYXNz header (Base64-encoded username:password).
  6. Server decodes the header, looks up the username in the htpasswd file, hashes the provided password, and compares it to the stored hash.
  7. If they match, the server grants access and serves the resource. If not, step 2 repeats.
warning

Base64 is encoding, not encryption. Anyone who intercepts the HTTP traffic can trivially decode the credentials. Basic authentication should always be used over HTTPS. Without HTTPS, credentials are transmitted in effectively plain text across the network.

AuthName and AuthUserFile

These two directives work alongside AuthType to complete the authentication configuration.

AuthName

AuthName sets the realm displayed in the browser's login dialog. It tells the user what they are logging into.

AuthName "Restricted Area"

The browser typically displays this as:

The server https://example.com requires a username and password.
The server says: Restricted Area

Username: [________]
Password: [________]
[Cancel] [Log In]

The realm name also has a functional purpose: browsers group credentials by realm. If two different directories use the same AuthName, the browser automatically sends the same credentials to both without prompting again. If they use different realm names, the browser treats them as separate authentication scopes.

/admin/.htaccess
AuthName "Admin Panel"
/reports/.htaccess
AuthName "Reports Section"

With different realm names, users must authenticate separately for each section, even if their credentials are valid for both.

AuthUserFile

AuthUserFile specifies the absolute filesystem path to the htpasswd file containing the usernames and password hashes.

AuthUserFile /etc/apache2/.htpasswd

This must be an absolute path, not a relative one. Apache needs to know exactly where on the filesystem to find the file.

Wrong approach:

# Relative paths do not work reliably
AuthUserFile .htpasswd
AuthUserFile ../secrets/.htpasswd

Correct approach:

# Always use absolute paths
AuthUserFile /etc/apache2/.htpasswd
AuthUserFile /home/username/.htpasswd

If you are on shared hosting and unsure of the absolute path, you can find it by creating a PHP file that outputs the server's document root:

find-path.php (delete after use)
<?php
echo $_SERVER['DOCUMENT_ROOT'];
// Output example: /home/username/public_html

If the document root is /home/username/public_html, you could store your htpasswd file at /home/username/.htpasswd (one level above the web root).

Complete Configuration Examples

Protect an entire directory:

/admin/.htaccess
AuthType Basic
AuthName "Admin Panel"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

Protect a single file:

.htaccess in the directory containing the file
<Files "secret-report.pdf">
AuthType Basic
AuthName "Confidential Document"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
</Files>

Protect specific file types:

<FilesMatch "\.(pdf|xlsx|docx)$">
AuthType Basic
AuthName "Document Library"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
</FilesMatch>

Require valid-user vs Require user

The Require directive determines which authenticated users are granted access. There are two main approaches.

Require valid-user

Require valid-user

This grants access to any user listed in the htpasswd file, as long as they provide the correct password. If the htpasswd file contains 50 users, all 50 can access the protected resource.

This is the most common choice when everyone in the password file should have the same level of access.

Require user

Require user admin editor

This grants access only to the specifically named users, even if other valid users exist in the htpasswd file. A user named john could have a valid entry in the htpasswd file, but if he is not listed in the Require user directive, he is denied access with a 401 response.

This is useful when different directories should be accessible to different people:

/admin/.htaccess
AuthType Basic
AuthName "Admin Panel"
AuthUserFile /etc/apache2/.htpasswd
Require user admin superadmin
/reports/.htaccess
AuthType Basic
AuthName "Reports"
AuthUserFile /etc/apache2/.htpasswd
Require user admin editor analyst

In this setup, the admin user can access both areas, editor and analyst can only access reports, and superadmin can only access the admin panel.

Combining Require Directives

You can combine multiple Require directives with authorization containers for more complex rules:

Allow specific users OR specific IPs:

AuthType Basic
AuthName "Mixed Access"
AuthUserFile /etc/apache2/.htpasswd

<RequireAny>
# Office IP can access without a password
Require ip 203.0.113.0/24

# Remote users need credentials
Require user admin editor
</RequireAny>

Clients from the office network get in without a login prompt. Everyone else must authenticate as either admin or editor.

Require authentication AND a specific IP:

AuthType Basic
AuthName "High Security"
AuthUserFile /etc/apache2/.htpasswd

<RequireAll>
Require user admin
Require ip 203.0.113.0/24
</RequireAll>

The user must be admin AND must be connecting from the specified IP range. Both conditions must be true.

Common Mistake: Forgetting AuthType or AuthUserFile

Wrong approach:

# Missing AuthType and AuthUserFile
AuthName "Protected"
Require valid-user

Apache will return a 500 Internal Server Error because it does not know what authentication method to use or where to find the password file. All four directives (AuthType, AuthName, AuthUserFile, Require) must be present together.

Correct approach:

AuthType Basic
AuthName "Protected"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

Limitations of Basic Auth

HTTP Basic authentication is simple and effective for many scenarios, but it has significant limitations you should understand before relying on it.

No Encryption Without HTTPS

Basic authentication transmits credentials encoded in Base64, which is trivially reversible. Without HTTPS, anyone on the same network (coffee shop Wi-Fi, corporate network, ISP) can intercept and read the username and password.

# What the Authorization header looks like:
Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM=

# Base64 decoded:
admin:password123

Always use Basic auth over HTTPS. If your site does not have HTTPS configured, set it up before enabling password protection. A redirect to HTTPS should come before any authentication:

# Force HTTPS first
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>

# Then require authentication
AuthType Basic
AuthName "Secure Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

No Logout Mechanism

Basic authentication has no built-in logout. Once a browser has accepted credentials, it continues sending them with every request until the browser is closed (and sometimes even after, due to credential caching). There is no "logout" button, no session expiration, and no way for the server to tell the browser to forget the credentials.

This makes Basic auth unsuitable for applications where users need to log out, switch accounts, or have their sessions expire after a period of inactivity.

No Brute-Force Protection

Apache's Basic auth has no built-in rate limiting or account lockout mechanism. An attacker can try thousands of username and password combinations per second without being blocked. To mitigate this, consider:

  • Using strong, long passwords.
  • Combining Basic auth with IP restrictions.
  • Using fail2ban or similar tools at the server level to block repeated failed attempts.
Combining password protection with IP restriction
AuthType Basic
AuthName "Double Protection"
AuthUserFile /etc/apache2/.htpasswd

<RequireAll>
Require valid-user
<RequireAny>
Require ip 203.0.113.0/24
Require ip 198.51.100.0/24
</RequireAny>
</RequireAll>

No Custom Login Page

The login prompt is a browser-native dialog box. You cannot style it, add your logo, include a "forgot password" link, or customize it in any way. It looks different in every browser and provides a utilitarian, often confusing experience for non-technical users.

If you need a branded login experience, you should implement form-based authentication in your application instead of relying on Basic auth.

Flat File Scalability

The htpasswd file is read and parsed on every authenticated request. With a handful of users, this is negligible. With thousands of users, it becomes a performance problem. Basic auth with htpasswd files is designed for small-scale use (dozens of users at most).

For larger user bases, consider:

  • mod_authn_dbd for database-backed authentication.
  • Application-level authentication with proper session management.
  • An identity provider with SSO (Single Sign-On).

When Basic Auth Is Appropriate

Despite its limitations, Basic auth is a perfectly good choice for:

ScenarioWhy It Works
Staging/development site protectionQuick to set up, keeps casual visitors out
Admin panel extra layerDefense in depth on top of application auth
Small team internal toolsSimple user management, no infrastructure needed
Temporary access during maintenanceEasy to add and remove
CI/CD webhook protectionMachine-to-machine auth where UX does not matter

When to Use Something Else

ScenarioBetter Alternative
Public-facing user loginApplication-level form authentication
Hundreds or thousands of usersDatabase-backed auth or identity provider
Need for session management or logoutApplication sessions with cookies
Compliance requirements (HIPAA, PCI, SOC2)Enterprise identity solutions with MFA
Social login (Google, GitHub, etc.)OAuth2/OpenID Connect

Basic authentication is a tool, not a solution. It excels at quickly locking down resources during development and adding a lightweight extra barrier in front of sensitive areas. But for user-facing authentication where security, usability, and scalability matter, invest in a proper authentication system at the application level. The key is knowing when each approach is appropriate and never treating Basic auth as a substitute for real application security.