Configure Apache LDAP Authentication with OpenLDAP

Configure Apache HTTP Server to authenticate and authorize users with OpenLDAP using mod_authnz_ldap, group restrictions, TLS, and practical troubleshooting.

Published

Updated

Read time 14 min read

Reviewed byDeepak Prasad

Apache HTTP Server authenticating users against OpenLDAP with mod_authnz_ldap

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.

text
Browser
   |
   | HTTPS + HTTP Basic Authentication
   v
Apache HTTP Server
mod_auth_basic + mod_authnz_ldap + mod_ldap
   |
   | LDAP StartTLS or LDAPS
   v
OpenLDAP Server

Apache 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:

Tested on: Rocky Linux 10.2; OpenLDAP 2.6.10; Apache HTTP Server 2.4.63 with mod_ldap and mod_ssl; SELinux Enforcing.

IMPORTANT
This article configures Apache on a separate application host. It does not install OpenLDAP, create the directory suffix, or issue new TLS certificates. Prepare users, groups, TLS, and ACLs on the LDAP server first, then return here for Apache integration.

How Apache authentication with OpenLDAP works

When a browser requests a protected URL, Apache and OpenLDAP cooperate in a fixed sequence:

  1. The browser requests a protected URL.
  2. Apache returns an HTTP Basic authentication challenge.
  3. The user enters an LDAP username and password.
  4. Apache searches OpenLDAP using the attribute configured in AuthLDAPURL, normally uid.
  5. OpenLDAP returns the matching user DN when exactly one entry matches.
  6. Apache attempts an LDAP bind with that user DN and the submitted password.
  7. Apache applies the configured authorization rule such as Require valid-user or Require ldap-group.
  8. 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 jdoe exists under ou=people.
  • The developers posixGroup entry uses memberUid if 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 as memberUid or member. It does not need permission to read userPassword.

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:

bash
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 cn

When prompted, enter the reader password you configured for apache-reader.

output
dn: uid=jdoe,ou=people,dc=example,dc=com
uid: jdoe
cn: John Doe

Apache 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:

bash
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 memberUid
output
dn: cn=developers,ou=groups,dc=example,dc=com
cn: developers
memberUid: jdoe

This course creates RFC2307 groups with memberUid values:

ldif
objectClass: posixGroup
memberUid: jdoe

A 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:

ldif
objectClass: groupOfNames
member: uid=jdoe,ou=people,dc=example,dc=com

Apache 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:

bash
sudo dnf install -y httpd mod_ldap mod_ssl openldap-clients

Enable and start the web server with systemctl:

bash
sudo systemctl enable --now httpd

Confirm the LDAP and Basic authentication modules are loaded:

bash
httpd -M | grep -E 'ldap|auth_basic|ssl'
output
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:

bash
sudo apt update
bash
sudo apt install -y apache2 ldap-utils
bash
sudo a2enmod ldap authnz_ldap ssl
bash
sudo systemctl enable --now apache2

Verify the modules are loaded:

bash
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:

bash
sudo a2enconf openldap-auth
bash
sudo apache2ctl configtest
bash
sudo systemctl reload apache2

Create a protected Apache directory

Create a simple test directory so LDAP troubleshooting stays separate from application code:

bash
sudo install -d -m 0755 /var/www/html/secure

Add a page that makes a successful login obvious:

bash
echo 'LDAP authentication succeeded.' | sudo tee /var/www/html/secure/index.html

A 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:

bash
sudo cp /etc/openldap/certs/example-ldap-ca.crt /etc/httpd/conf/openldap-ca.crt
sudo chmod 0644 /etc/httpd/conf/openldap-ca.crt

Create /etc/httpd/conf.d/openldap-auth.conf with the minimum working LDAP authentication block. Place LDAPTrustedGlobalCert in server configuration context, not inside <Directory>:

apache
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:

bash
sudo chown root:root /etc/httpd/conf.d/openldap-auth.conf
sudo chmod 0600 /etc/httpd/conf.d/openldap-auth.conf

Do 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:

text
ldap://host:port/base-dn?attribute?scope?filter

Append STARTTLS after the quoted URL to upgrade the port 389 connection to TLS. Apache documents the full directive form as:

text
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:

bash
sudo apachectl configtest
output
Syntax OK

Reload Apache:

bash
sudo systemctl reload httpd

On RHEL-family systems with SELinux Enforcing, allow httpd to open outbound LDAP connections:

bash
sudo setsebool -P httpd_can_connect_ldap on

Without 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:

bash
curl --noproxy '*' -sI https://apache-server.example.com/secure/
output
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="OpenLDAP Login"

Test a valid LDAP user without placing the password on the command line:

bash
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:

ldif
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: developers
gidNumber: 10010
memberUid: jdoe

Replace Require valid-user with group authorization settings that match memberUid:

apache
AuthLDAPGroupAttribute memberUid
AuthLDAPGroupAttributeIsDN off
AuthzSendForbiddenOnFailure On

Require ldap-group cn=developers,ou=groups,dc=example,dc=com

memberUid 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.

IMPORTANT
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:

apache
AuthLDAPGroupAttribute member
AuthLDAPGroupAttributeIsDN on

Require ldap-group cn=app-developers,ou=groups,dc=example,dc=com

For a groupOfUniqueNames entry that stores members in uniqueMember, use:

apache
AuthLDAPGroupAttribute uniqueMember
AuthLDAPGroupAttributeIsDN on

Require ldap-group cn=app-developers,ou=groups,dc=example,dc=com

In 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:

text
Browser  -- HTTPS -->  Apache  -- LDAP TLS -->  OpenLDAP

Encrypting 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:

apache
AuthLDAPURL "ldap://ldap-server.example.com/ou=people,dc=example,dc=com?uid?sub?(objectClass=posixAccount)" STARTTLS

Keep 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:

bash
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

Option 2: LDAPS

LDAPS negotiates TLS immediately on port 636. Change only the AuthLDAPURL line inside the existing <Directory> block:

apache
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:

text
RHEL family: /var/log/httpd/error_log
Debian/Ubuntu: /var/log/apache2/error.log

Review 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 AuthLDAPURL only after the single-server configuration works.

References


Summary

You configured Apache HTTP Server to authenticate users against OpenLDAP:

  1. Installed and verified mod_ldap, mod_authnz_ldap, and mod_auth_basic.
  2. Confirmed the LDAP user and group entries with ldapsearch before editing Apache.
  3. Configured AuthLDAPURL to search uid under ou=people.
  4. Used a read-only apache-reader bind account for the initial user search.
  5. Started with Require valid-user, then restricted access with memberUid group settings and AuthzSendForbiddenOnFailure On for clearer authorization testing.
  6. Protected both the website and the Apache-to-OpenLDAP connection with TLS.
  7. 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.


Frequently Asked Questions

1. Which Apache modules provide LDAP authentication?

mod_auth_basic supplies the browser login prompt, mod_authnz_ldap performs LDAP authentication and authorization, and mod_ldap manages LDAP connections, TLS, and connection pooling.

2. Why does Require valid-user work but Require ldap-group fails?

Authentication succeeded but authorization failed. POSIX groups store usernames in memberUid, so set AuthLDAPGroupAttribute memberUid and AuthLDAPGroupAttributeIsDN off. DN-based groups need member or uniqueMember with AuthLDAPGroupAttributeIsDN on.

3. Should Apache use StartTLS or LDAPS to reach OpenLDAP?

Either encrypts LDAP traffic between Apache and OpenLDAP. Use an ldap:// URL followed by the STARTTLS parameter on port 389, or an ldaps:// URL on port 636, and trust the LDAP CA with LDAPTrustedGlobalCert in server configuration context.

4. Does Apache store LDAP passwords locally?

Apache does not write end-user LDAP passwords to a local password file or its configuration. However, mod_ldap may temporarily cache successful authentication data, including the submitted password, in memory according to LDAPCacheTTL. The service bind password used for the initial directory search is the only password stored explicitly in Apache configuration.

5. Why does Apache return 401 for a valid LDAP user?

Check the user search base, uid attribute, LDAP filter, bind DN, bind password, TLS trust, and whether the search returns exactly one entry. A wrong password also returns 401.

6. Why does Apache return 500 during LDAP login on RHEL?

On SELinux Enforcing systems, httpd may be blocked from connecting to LDAP port 389 until httpd_can_connect_ldap is enabled. Also verify DNS, firewall, and LDAP server reachability.

7. Can Apache authenticate against anonymous LDAP searches?

Only when the directory ACLs permit anonymous read of the attributes Apache needs. Most production directories deny anonymous search, so configure AuthLDAPBindDN with a read-only service account.

8. Must the protected website use HTTPS?

Yes for production. HTTP Basic authentication sends credentials in Base64 on every request. TLS on the website protects the username and password between the browser and Apache; LDAP TLS protects traffic between Apache and OpenLDAP.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …