Password protection in Apache 2.4: Basic, Digest and LDAP
To put a password on part of a site, create a password file with htpasswd -B, add an AuthType Basic block with Require valid-user, and serve that area only over HTTPS. This page shows each step for Apache httpd 2.4, then groups, Digest (and why to skip it) and logins against LDAP or Active Directory.
Old series, current syntax. Apache Today ran "Apache Authentication, Parts 1–4" in July and August 2000 (see the 2000 archive). The ideas still hold, but the directives changed in 2.4. Every example below was tested on Apache httpd 2.4 in November 2026.
How HTTP authentication works
The first request for a protected page gets a 401 Unauthorized answer with a WWW-Authenticate header naming the realm (the text you set with AuthName). The browser shows a login box and sends the request again with an Authorization header. It then repeats that header on every later request to the same area, including each image and stylesheet.
With Basic authentication the header is only the user name and password joined by a colon and Base64-encoded. Base64 is not encryption; anyone who sees the header can reverse it:
$ echo 'YWxpY2U6UzNjcmV0LWFsaWNl' | base64 -d
alice:S3cret-alice
HTTPS only. The Apache documentation says Basic authentication sends the password unencrypted and should not protect sensitive data unless the connection uses mod_ssl. Put the protected area in your HTTPS virtual host and redirect plain HTTP to HTTPS (HTTPS guide, redirects).
The modules involved
Apache 2.4 splits the job into small modules:
- mod_auth_basic or mod_auth_digest: the method (
AuthType). - mod_authn_file: checks the password against a file (
AuthUserFile). Other providers: mod_authn_dbm, mod_authn_dbd (SQL), mod_authnz_ldap. - mod_authz_user:
Require userandRequire valid-user. mod_authz_groupfile:Require group. - mod_authn_core and mod_authz_core: the framework, including
Requireand<RequireAll>.
Debian and Ubuntu enable auth_basic, authn_core, authn_file, authz_core, authz_host and authz_user by default; groups, Digest and LDAP need a2enmod. RHEL, Alma, Rocky and Fedora load all the core auth modules from /etc/httpd/conf.modules.d/00-base.conf. Check with apachectl -M | grep auth.
Step 1: create the password file
The htpasswd tool comes in apache2-utils on Debian/Ubuntu and httpd-tools on RHEL/Fedora:
sudo apt install apache2-utils # Debian, Ubuntu
sudo dnf install httpd-tools # RHEL, Alma, Rocky, Fedora
Create the file with the first user. -c creates the file, -B selects bcrypt and -C sets the bcrypt cost. You are asked for the password twice:
sudo htpasswd -c -B -C 10 /etc/apache2/.htpasswd alice # RHEL: /etc/httpd/.htpasswd
New password:
Re-type new password:
Adding password for user alice
Add more users without -c; with it, an existing file is truncated and every other user is lost:
sudo htpasswd -B -C 10 /etc/apache2/.htpasswd bob
sudo htpasswd -v /etc/apache2/.htpasswd bob # check a password
sudo htpasswd -D /etc/apache2/.htpasswd bob # delete a user
Each line is name:hash, for example alice:$2y$10$…. Keep the file outside the DocumentRoot and readable by the Apache user only:
sudo chown root:www-data /etc/apache2/.htpasswd # RHEL: root:apache /etc/httpd/.htpasswd
sudo chmod 640 /etc/apache2/.htpasswd
For scripts, pipe the password in with -i rather than -b, which puts it on the command line where other users and shell history can see it: printf '%s\n' "$PW" | sudo htpasswd -i -B -C 10 /etc/apache2/.htpasswd carol.
Which hash to use
| Option | Hash | Verdict |
|---|---|---|
-B | bcrypt | Use this. Cost 4–17, default 5. |
-5 / -2 | SHA-512 / SHA-256 crypt | Acceptable; default 5,000 rounds (-r). |
-m | Apache MD5 ("apr1") | The default if you give no option. Weak today. |
-s | SHA-1, unsalted | Insecure. |
-d | crypt() | Insecure; uses only the first 8 characters. |
-p | plain text | Only accepted on Windows and NetWare. Never. |
The cost matters more than you might expect, because Apache checks the hash on every request, not once per login. In our test (httpd 2.4.69, one request at a time), a request took about 10 ms at cost 5, 65 ms at cost 10 and 240 ms at cost 12, against under 1 ms without a password; your hardware will differ. A page with 20 images at cost 12 adds seconds of CPU time. Cost 10 is a reasonable middle. Turning on mod_authn_socache did not help in our test, and its documentation says file lookups are unlikely to benefit.
Step 2: protect a directory or URL
Put the block in the main configuration, inside your HTTPS <VirtualHost *:443> (Debian: /etc/apache2/sites-available/; RHEL: /etc/httpd/conf.d/):
<Directory "/var/www/example.com/private">
AuthType Basic
AuthName "Private area"
AuthBasicProvider file
AuthUserFile "/etc/apache2/.htpasswd"
Require valid-user
</Directory>
Use <Location "/admin"> instead when the URL is produced by an application or a proxy rather than a folder on disk. Replace Require valid-user with Require user alice bob to allow only named users. Then test the configuration and reload:
sudo apache2ctl configtest && sudo systemctl reload apache2 # Debian, Ubuntu
sudo apachectl configtest && sudo systemctl reload httpd # RHEL, Fedora
If you cannot edit the main configuration, the same lines (without the <Directory> wrapper) work in a .htaccess file, provided the server allows AllowOverride AuthConfig for that folder. See the .htaccess guide for the costs.
Groups of users
A group file is plain text, one group per line, members separated by spaces:
# /etc/apache2/.htgroups
editors: alice
viewers: alice bob
sudo a2enmod authz_groupfile && sudo systemctl restart apache2 # Debian, Ubuntu only
<Directory "/var/www/example.com/drafts">
AuthType Basic
AuthName "Editors only"
AuthUserFile "/etc/apache2/.htpasswd"
AuthGroupFile "/etc/apache2/.htgroups"
Require group editors
</Directory>
Bob's password is correct, but he is not an editor, so he gets 401 and the error log records AH01631: user bob: authorization failure.
Password or trusted network
Several Require lines in one section mean "any of these". This lets the office network in without a password and asks everyone else to log in (the 2.4 replacement for the old Satisfy Any):
<Directory "/var/www/example.com/reports">
AuthType Basic
AuthName "Reports"
AuthUserFile "/etc/apache2/.htpasswd"
<RequireAny>
Require ip 192.0.2.0/24
Require valid-user
</RequireAny>
</Directory>
Use <RequireAll> instead to demand both. More combinations are in the access control guide.
Digest authentication: skip it
AuthType Digest (mod_auth_digest) sends a hash instead of the password. The Apache documentation now says it no longer meets its goal: an attacker in the middle can force the browser down to Basic, the MD5 hash is fast enough to brute-force with graphics cards, and a stolen htdigest file can be used to log in directly. Its advice is Basic authentication over an encrypted connection. The module has also had a run of security fixes: httpd 2.4.67 to 2.4.69 fixed several mod_auth_digest vulnerabilities (timing, replay and denial-of-service issues), so if you keep it, stay on the current release. If an old client needs Digest, this works:
sudo htdigest -c /etc/apache2/.htdigest "Staff area" carol
sudo a2enmod auth_digest && sudo systemctl restart apache2 # Debian, Ubuntu
<Location "/staff/">
AuthType Digest
AuthName "Staff area"
AuthDigestDomain "/staff/"
AuthDigestProvider file
AuthUserFile "/etc/apache2/.htdigest"
Require valid-user
</Location>
The realm given to htdigest must match AuthName exactly.
LDAP and Active Directory
To let staff log in with their directory account, use mod_authnz_ldap, which relies on mod_ldap for connections and caching:
sudo a2enmod authnz_ldap && sudo systemctl restart apache2 # Debian, Ubuntu (enables ldap too)
sudo dnf install mod_ldap && sudo systemctl restart httpd # RHEL, Fedora
An Active Directory example over LDAPS (port 636), allowing one group:
# Server config only: the CA that signed the domain controllers' certificates (PEM)
LDAPTrustedGlobalCert CA_BASE64 "/etc/ssl/certs/corp-ca.pem"
<Location "/intranet">
AuthType Basic
AuthName "Company login"
AuthBasicProvider ldap
AuthLDAPURL "ldaps://dc1.corp.example.com:636/dc=corp,dc=example,dc=com?sAMAccountName?sub?(objectClass=user)"
AuthLDAPBindDN "CN=svc-apache,OU=Service Accounts,DC=corp,DC=example,DC=com"
AuthLDAPBindPassword "exec:/bin/cat /etc/apache2/ldap-bind.secret"
Require ldap-group CN=Web Editors,OU=Groups,DC=corp,DC=example,DC=com
</Location>
- The URL format is
ldap[s]://host:port/basedn?attribute?scope?filter. The attribute is what users type (uidby default;sAMAccountNameoruserPrincipalNamein AD). For STARTTLS on port 389, writeldap://…followed by the keywordSTARTTLS. exec:(httpd 2.4.5 and later) reads the bind password from a program's first line of output, so it stays out of the config file. Make the secret file readable by root only; Apache reads it at startup.- Do not quote the group DN after
Require ldap-group. Other options:Require ldap-user,ldap-dn,ldap-attribute,ldap-filter. - To try a local file first and LDAP second, use
AuthBasicProvider file ldap. A user found in the file whose password is wrong is not passed on to LDAP.
Checking that it works
$ curl -sI https://www.example.com/private/ | grep -i -e '^HTTP' -e authenticate
HTTP/2 401
www-authenticate: Basic realm="Private area"
$ curl -s -o /dev/null -w '%{http_code}\n' -u alice https://www.example.com/private/
Enter host password for user 'alice':
200
Failed logins appear in the error log (date and process fields trimmed):
[auth_basic:error] [client 203.0.113.9:51234] AH01617: user alice: authentication failure for "/private/": Password Mismatch
[auth_basic:error] [client 203.0.113.9:51240] AH01618: user nobody not found: /private/
Common mistakes
- Running
htpasswd -con an existing file. It wipes all other users. Keep a backup. - The password file in the web root. The Debian and RHEL default configs deny files whose names start with
.ht, but a file namedpasswords.txtwould be served to anyone. - The rule is ignored. In
.htaccessthis meansAllowOverridelacksAuthConfig. In the main config, a later<Location>may override your<Directory>. - No lockout. mod_auth_basic does not block repeated guesses. Use long passwords and watch for AH01617 lines; see bots and floods for fail2ban.
- No logout. Browsers keep sending Basic credentials until they are closed. If you need logout buttons, sessions or two-factor login, use your application's own login or a single sign-on proxy instead.
Sources
- Authentication and Authorization, Apache HTTP Server 2.4 documentation
- htpasswd, Apache HTTP Server 2.4 documentation
- mod_auth_basic, Apache HTTP Server 2.4 documentation
- mod_auth_digest, Apache HTTP Server 2.4 documentation
- Apache HTTP Server 2.4 vulnerabilities, Apache HTTP Server Project
- mod_authn_socache, Apache HTTP Server 2.4 documentation
- mod_authz_core, Apache HTTP Server 2.4 documentation
- mod_authnz_ldap, Apache HTTP Server 2.4 documentation
- mod_ldap, Apache HTTP Server 2.4 documentation
- Upgrading to 2.4 from 2.2, Apache HTTP Server 2.4 documentation
- 00-base.conf (modules loaded by default), CentOS Stream httpd package