This guide protects a directory served by Apache HTTP Server, authenticates users against an existing OpenLDAP directory, and then restricts access to members of an LDAP group. You will start with every valid LDAP user, add posixGroup authorization for the developers group, and encrypt Apache-to-OpenLDAP traffic with StartTLS.
Browser
|
| HTTPS + HTTP Basic Authentication
v
Apache HTTP Server
mod_auth_basic + mod_authnz_ldap + mod_ldap
|
| LDAP StartTLS or LDAPS
v
OpenLDAP ServerApache does not copy LDAP passwords into a local file. It searches OpenLDAP for the username entered in the browser, then verifies the submitted password through an LDAP bind against the returned user DN.
Part of the OpenLDAP tutorial series.
Complete these lessons first:
- Install and Configure OpenLDAP on the RHEL Family
- Manage OpenLDAP users and groups
- Configure OpenLDAP TLS with StartTLS
- OpenLDAP ACL configuration
Tested on: Rocky Linux 10.2; OpenLDAP 2.6.10; Apache HTTP Server 2.4.63 with
mod_ldapandmod_ssl; SELinux Enforcing.
How Apache authentication with OpenLDAP works
When a browser requests a protected URL, Apache and OpenLDAP cooperate in a fixed sequence:
- The browser requests a protected URL.
- Apache returns an HTTP Basic authentication challenge.
- The user enters an LDAP username and password.
- Apache searches OpenLDAP using the attribute configured in
AuthLDAPURL, normallyuid. - OpenLDAP returns the matching user DN when exactly one entry matches.
- Apache attempts an LDAP bind with that user DN and the submitted password.
- Apache applies the configured authorization rule such as
Require valid-userorRequire ldap-group. - Apache grants or denies access to the requested content.
Authentication answers whether the username and password are valid. Authorization answers whether that authenticated identity may access this directory.
| Module | Purpose |
|---|---|
mod_auth_basic |
Provides the browser login prompt |
mod_authnz_ldap |
Performs LDAP authentication and authorization |
mod_ldap |
Provides LDAP connections, caching, TLS, and connection pooling |
HTTP Basic authentication sends credentials on every request. Serve the protected website over HTTPS, and encrypt the separate LDAP connection between Apache and OpenLDAP.
Lab environment and prerequisites
The walkthrough uses the same directory layout as the rest of this OpenLDAP course:
| Component | Example |
|---|---|
| OpenLDAP server | ldap-server.example.com |
| Apache server | apache-server.example.com |
| Base DN | dc=example,dc=com |
| User search base | ou=people,dc=example,dc=com |
| Group search base | ou=groups,dc=example,dc=com |
| Test user | jdoe |
| Test group | developers |
| Apache bind account | uid=apache-reader,ou=service-accounts,dc=example,dc=com |
| Protected URL | https://apache-server.example.com/secure/ |
Before you edit Apache configuration, confirm all of the following:
- OpenLDAP is running and reachable from the Apache host on port 389 for StartTLS or port 636 for LDAPS.
- At least one POSIX user such as
jdoeexists underou=people. - The
developersposixGroupentry usesmemberUidif you plan to restrict access by group. - The Apache host trusts the LDAP server CA certificate.
- A dedicated read-only bind account can search users and groups, read the login attribute configured in
AuthLDAPURL, and read or compare the group-membership attribute used for authorization such asmemberUidormember. It does not need permission to readuserPassword.
User and group LDIF management stays in Manage OpenLDAP users and groups. TLS certificate creation stays in Configure OpenLDAP TLS with StartTLS. Service-account ACL design stays in OpenLDAP ACL examples.
Verify LDAP users and groups before configuring Apache
Start troubleshooting from the command line on the Apache host. Use the same search base, bind account, and username attribute that Apache will use later—the ldapsearch command documents -b, -D, and filter syntax for these checks.
Verify the LDAP user
Search for jdoe under the people subtree with StartTLS and the Apache reader account:
ldapsearch -x -ZZ -H ldap://ldap-server.example.com \
-D "uid=apache-reader,ou=service-accounts,dc=example,dc=com" -W \
-b "ou=people,dc=example,dc=com" -LLL "(uid=jdoe)" dn uid cnWhen prompted, enter the reader password you configured for apache-reader.
dn: uid=jdoe,ou=people,dc=example,dc=com
uid: jdoe
cn: John DoeApache cannot authenticate the username when the search returns no entry, multiple matching entries, or an entry whose required attributes the bind account cannot read. Apache requires the constructed user search to return one unique entry.
Verify the LDAP group
Display the developers group and confirm which membership attribute it uses:
ldapsearch -x -ZZ -H ldap://ldap-server.example.com \
-D "uid=apache-reader,ou=service-accounts,dc=example,dc=com" -W \
-b "ou=groups,dc=example,dc=com" -LLL "(cn=developers)" cn memberUiddn: cn=developers,ou=groups,dc=example,dc=com
cn: developers
memberUid: jdoeThis course creates RFC2307 groups with memberUid values:
objectClass: posixGroup
memberUid: jdoeA matching POSIX gidNumber on the user entry does not automatically make Apache treat that user as a group member. Require ldap-group checks the membership attribute on the LDAP group entry. For this example, cn=developers must contain memberUid: jdoe; a primary group ID alone is not enough.
DN-based groups use a different model:
objectClass: groupOfNames
member: uid=jdoe,ou=people,dc=example,dc=comApache group authorization must match the attribute your directory actually stores. memberUid, member, and uniqueMember are not interchangeable without changing AuthLDAPGroupAttribute and AuthLDAPGroupAttributeIsDN.
Install and enable Apache LDAP modules
RHEL family
Install Apache HTTP Server, the LDAP authentication modules, and the LDAP client tools used earlier in this guide. On RHEL-family hosts, package installs use sudo and dnf:
sudo dnf install -y httpd mod_ldap mod_ssl openldap-clientsEnable and start the web server with systemctl:
sudo systemctl enable --now httpdConfirm the LDAP and Basic authentication modules are loaded:
httpd -M | grep -E 'ldap|auth_basic|ssl'auth_basic_module (shared)
ssl_module (shared)
ldap_module (shared)
authnz_ldap_module (shared)Debian and Ubuntu
Install Apache, the LDAP utilities, and enable the required modules:
sudo apt updatesudo apt install -y apache2 ldap-utilssudo a2enmod ldap authnz_ldap sslsudo systemctl enable --now apache2Verify the modules are loaded:
apache2ctl -M | grep -E 'ldap|auth_basic|ssl'The Apache directives in the rest of this guide are portable. Paths and service names differ:
| RHEL family | Debian/Ubuntu |
|---|---|
/etc/httpd/conf.d/openldap-auth.conf |
/etc/apache2/conf-available/openldap-auth.conf |
httpd -M |
apache2ctl -M |
systemctl reload httpd |
systemctl reload apache2 |
/var/log/httpd/error_log |
/var/log/apache2/error.log |
openldap-clients |
ldap-utils |
On Debian and Ubuntu, enable the site configuration after creating the file:
sudo a2enconf openldap-authsudo apache2ctl configtestsudo systemctl reload apache2Create a protected Apache directory
Create a simple test directory so LDAP troubleshooting stays separate from application code:
sudo install -d -m 0755 /var/www/html/secureAdd a page that makes a successful login obvious:
echo 'LDAP authentication succeeded.' | sudo tee /var/www/html/secure/index.htmlA static page isolates Apache authentication from application frameworks. The same Directory settings can later protect a reverse proxy or web application.
Place the LDAP settings in a dedicated configuration file such as /etc/httpd/conf.d/openldap-auth.conf rather than .htaccess. Server-level configuration is easier to audit and keeps the bind password out of content directories.
Configure Apache for all valid LDAP users
Copy the lab CA certificate to a path Apache can read:
sudo cp /etc/openldap/certs/example-ldap-ca.crt /etc/httpd/conf/openldap-ca.crt
sudo chmod 0644 /etc/httpd/conf/openldap-ca.crtCreate /etc/httpd/conf.d/openldap-auth.conf with the minimum working LDAP authentication block. Place LDAPTrustedGlobalCert in server configuration context, not inside <Directory>:
LDAPTrustedGlobalCert CA_BASE64 /etc/httpd/conf/openldap-ca.crt
LDAPVerifyServerCert On
<Directory "/var/www/html/secure">
AuthType Basic
AuthName "OpenLDAP Login"
AuthBasicProvider ldap
AuthLDAPURL "ldap://ldap-server.example.com/ou=people,dc=example,dc=com?uid?sub?(objectClass=posixAccount)" STARTTLS
AuthLDAPBindDN "uid=apache-reader,ou=service-accounts,dc=example,dc=com"
AuthLDAPBindPassword "REPLACE_WITH_READER_PASSWORD"
Require valid-user
</Directory>Restrict ownership of the configuration file because it contains the service bind password. The Apache parent process reads configuration as root before dropping privileges, so the runtime apache account does not need read access to the file:
sudo chown root:root /etc/httpd/conf.d/openldap-auth.conf
sudo chmod 0600 /etc/httpd/conf.d/openldap-auth.confDo not use cn=admin,dc=example,dc=com as the Apache bind account. Create a read-only service identity that can search only the attributes Apache needs. For advanced secret management, Apache also supports an exec: value for AuthLDAPBindPassword that runs a controlled helper to return the password.
Explain every part of AuthLDAPURL
The LDAP URL itself ends after the search filter:
ldap://host:port/base-dn?attribute?scope?filterAppend STARTTLS after the quoted URL to upgrade the port 389 connection to TLS. Apache documents the full directive form as:
AuthLDAPURL url [NONE|SSL|TLS|STARTTLS]| Component | Example | Purpose |
|---|---|---|
| Server | ldap-server.example.com |
OpenLDAP host |
| Base DN | ou=people,dc=example,dc=com |
Where user searches begin |
| Attribute | uid |
Browser username mapped to this attribute |
| Scope | sub |
Search the complete subtree |
| Filter | (objectClass=posixAccount) |
Restrict matching entries |
| Transport | STARTTLS after the URL |
Upgrade the LDAP connection to TLS on port 389 |
Use uid rather than cn for the login attribute. uid is normally unique in POSIX deployments, while multiple entries may share similar common names.
Validate the Apache configuration
Check syntax before reloading:
sudo apachectl configtestSyntax OKReload Apache:
sudo systemctl reload httpdOn RHEL-family systems with SELinux Enforcing, allow httpd to open outbound LDAP connections:
sudo setsebool -P httpd_can_connect_ldap onWithout that boolean, authenticated requests can fail with HTTP 500 even when ldapsearch from the shell succeeds.
This guide assumes an HTTPS virtual host and a browser-trusted server certificate are already configured on the Apache host. LDAP StartTLS protects the Apache-to-OpenLDAP connection; it does not protect the browser-to-Apache Basic authentication credentials. If you still need to create the website certificate, see Create client and server certificates with OpenSSL.
Test an unauthenticated request over HTTPS:
curl --noproxy '*' -sI https://apache-server.example.com/secure/HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="OpenLDAP Login"Test a valid LDAP user without placing the password on the command line:
curl --noproxy '*' -sI -u jdoe https://apache-server.example.com/secure/curl prompts for the password interactively. A successful bind returns HTTP 200. A wrong password returns 401 again.
Use --noproxy '*' when a shell-wide https_proxy variable would otherwise send local hostnames through a proxy. Use -k only when you are deliberately testing with a self-signed website certificate in a lab.
Restrict access to an LDAP group
Require valid-user confirms that the account exists and the password is correct. It does not limit which LDAP users may access the page.
Authorize a posixGroup using memberUid
This is the main group example for this course because earlier lessons create posixGroup entries with memberUid values.
Example group entry:
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: developers
gidNumber: 10010
memberUid: jdoeReplace Require valid-user with group authorization settings that match memberUid:
AuthLDAPGroupAttribute memberUid
AuthLDAPGroupAttributeIsDN off
AuthzSendForbiddenOnFailure On
Require ldap-group cn=developers,ou=groups,dc=example,dc=commemberUid stores a username, not a full user DN. AuthLDAPGroupAttributeIsDN off tells Apache to compare the login name against that attribute. Without these directives, authentication often succeeds while group authorization fails because Apache defaults expect DN-valued member or uniqueMember attributes.
By default, Apache returns 401 Unauthorized even after a successful password bind when authorization fails, which lets the browser show the credentials dialog again. AuthzSendForbiddenOnFailure On changes that behavior so a valid password with failed group membership returns 403 Forbidden, which makes authentication and authorization easier to distinguish during testing.
AuthzSendForbiddenOnFailure On reveals that the supplied password was valid when group authorization fails. Leave the default Off in production when that distinction would disclose sensitive authentication information.
Reload Apache after the change, then test a valid LDAP user who is not listed in memberUid on cn=developers. With AuthzSendForbiddenOnFailure On, that account should receive HTTP 403 Forbidden after a successful password bind.
Authorize DN-based groups
For a groupOfNames entry that stores members in member, use:
AuthLDAPGroupAttribute member
AuthLDAPGroupAttributeIsDN on
Require ldap-group cn=app-developers,ou=groups,dc=example,dc=comFor a groupOfUniqueNames entry that stores members in uniqueMember, use:
AuthLDAPGroupAttribute uniqueMember
AuthLDAPGroupAttributeIsDN on
Require ldap-group cn=app-developers,ou=groups,dc=example,dc=comIn both cases, the membership value is the complete user DN. Check the actual group entry with ldapsearch before choosing the attribute.
Keep POSIX and DN-based examples separate. Do not combine memberUid settings with a groupOfNames entry in one unexplained configuration block.
Other authorization options
| Directive | Use |
|---|---|
Require valid-user |
Any authenticated LDAP user |
Require ldap-user |
One or more named users |
Require ldap-group |
Members of an LDAP group |
Require ldap-attribute |
User entry contains a particular attribute value |
Require ldap-filter |
User matches a more complex LDAP filter |
Secure Apache-to-OpenLDAP communication with TLS
Two connections need protection:
Browser -- HTTPS --> Apache -- LDAP TLS --> OpenLDAPEncrypting only the website does not encrypt LDAP traffic between Apache and OpenLDAP.
Option 1: LDAP with StartTLS
Upgrade a plain LDAP connection on port 389 by changing only the AuthLDAPURL line inside the existing <Directory> block:
AuthLDAPURL "ldap://ldap-server.example.com/ou=people,dc=example,dc=com?uid?sub?(objectClass=posixAccount)" STARTTLSKeep the previously configured LDAPTrustedGlobalCert and LDAPVerifyServerCert On directives at server scope, outside the <Directory> block.
Apache must trust the CA that signed the LDAP server certificate. Certificate creation and renewal stay in Configure OpenLDAP TLS with StartTLS.
Verify TLS independently before testing Apache:
ldapsearch -x -ZZ -H ldap://ldap-server.example.com \
-D "uid=apache-reader,ou=service-accounts,dc=example,dc=com" -W \
-b "ou=people,dc=example,dc=com" -LLL "(uid=jdoe)" dnOption 2: LDAPS
LDAPS negotiates TLS immediately on port 636. Change only the AuthLDAPURL line inside the existing <Directory> block:
AuthLDAPURL "ldaps://ldap-server.example.com:636/ou=people,dc=example,dc=com?uid?sub?(objectClass=posixAccount)"Keep the previously configured LDAPTrustedGlobalCert and LDAPVerifyServerCert On directives at server scope, outside the <Directory> block.
Do not append STARTTLS to an ldaps:// URL.
Use either StartTLS or LDAPS consistently. The URL scheme, port, and server listener must agree.
Do not disable certificate validation as a permanent fix. Correct the CA path, hostname, and certificate trust instead.
Test authentication and authorization completely
Use a small matrix to separate authentication from authorization:
| Test | Password | Group membership | Expected result |
|---|---|---|---|
jdoe |
Correct | Member of developers |
Access allowed |
jdoe |
Incorrect | Member | Authentication denied (401) |
| Valid LDAP user | Correct | Not a member | Authorization denied (403 with AuthzSendForbiddenOnFailure On) |
| Unknown user | Any | None | Authentication denied (401) |
| Anonymous request | None | None | Login requested (401) |
A 401 response usually means the user search or password bind failed. With AuthzSendForbiddenOnFailure On, a 403 response after a valid password means Require ldap-group or another authorization rule rejected the authenticated identity.
Test in the browser and with curl -u username over HTTPS. Run apachectl configtest and reload httpd after each meaningful configuration change.
Troubleshoot Apache OpenLDAP authentication
| Symptom | Likely cause | Fix |
|---|---|---|
configtest reports that AuthLDAPURL is an invalid command |
mod_authnz_ldap is not loaded |
Install or enable the LDAP modules, confirm with httpd -M or apache2ctl -M, then rerun the configuration test |
| Login always returns 401 | Search base, filter, bind DN, or password | Match ldapsearch parameters to AuthLDAPURL; confirm exactly one user entry; when the password bind itself fails, follow Fix OpenLDAP Error 49 |
Require valid-user works, Require ldap-group fails |
Wrong group attribute model | For posixGroup, set AuthLDAPGroupAttribute memberUid and AuthLDAPGroupAttributeIsDN off |
| HTTP 500 during login on RHEL | SELinux blocks LDAP connect | Run setsebool -P httpd_can_connect_ldap on |
| TLS verification fails | Wrong CA file or hostname mismatch | Fix LDAPTrustedGlobalCert, use the LDAP server FQDN from the certificate SAN |
| Authentication is slow | Broad search base or missing indexes | Narrow the base DN, index the login attribute, and tune filters with OpenLDAP indexing and performance |
| Password or group change is not reflected immediately | Apache LDAP cache still holds the earlier result | Reload Apache during testing or wait for LDAPCacheTTL / LDAPOpCacheTTL; tune cache values only when operationally necessary |
Review Apache logs after a failed login:
RHEL family: /var/log/httpd/error_log
Debian/Ubuntu: /var/log/apache2/error.logReview OpenLDAP logs on the directory server to distinguish failed searches from failed user binds. Authentication failures often appear as bind errors, while authorization failures happen after a successful bind.
mod_ldap caches successful LDAP search, bind, and group-comparison results. The default TTL for both the main LDAP cache and operation cache is 600 seconds, so a password change or newly added group membership may not appear immediately in repeated Apache tests.
Security and production recommendations
- Serve the protected website over HTTPS.
- Encrypt Apache-to-OpenLDAP traffic with StartTLS or LDAPS.
- Use a dedicated read-only bind account instead of the directory administrator.
- Restrict file permissions on Apache configuration that contains
AuthLDAPBindPassword. - Keep the user search base as narrow as practical and add an LDAP filter for the account type you support.
- Restrict access by group instead of allowing every LDAP user when the application requires it.
- Test certificate validation rather than bypassing it.
- Monitor Apache and OpenLDAP authentication failures.
- Add redundant LDAP servers in
AuthLDAPURLonly after the single-server configuration works.
References
- Apache mod_authnz_ldap — LDAP authentication and authorization directives
- Apache mod_ldap — LDAP connection, TLS, and cache settings
- Apache mod_authz_core —
AuthzSendForbiddenOnFailure - OpenLDAP 2.6 administrator guide — Access control — ACL ordering and password-bind rules
Summary
You configured Apache HTTP Server to authenticate users against OpenLDAP:
- Installed and verified
mod_ldap,mod_authnz_ldap, andmod_auth_basic. - Confirmed the LDAP user and group entries with
ldapsearchbefore editing Apache. - Configured
AuthLDAPURLto searchuidunderou=people. - Used a read-only
apache-readerbind account for the initial user search. - Started with
Require valid-user, then restricted access withmemberUidgroup settings andAuthzSendForbiddenOnFailure Onfor clearer authorization testing. - Protected both the website and the Apache-to-OpenLDAP connection with TLS.
- Distinguished authentication failures from authorization failures during HTTPS testing.
For the surrounding course, continue with OpenLDAP ACL examples, OpenLDAP indexing and performance, and Fix OpenLDAP Error 49 when password binds fail during testing.

