Keycloak LDAP integration fails silently with generic error messages. The admin console shows “Connection refused” or “Test authentication failed” without revealing the actual cause. This guide catalogs every Keycloak LDAP error with exact log messages, Active Directory sub-codes, and fix commands.
For initial LDAP setup instructions, see Keycloak User Federation with LDAP and Active Directory.
Quick Diagnostic: Which Error Are You Seeing?
| Admin Console / Log Message | Jump To |
|---|---|
Connection refused | Connection Errors |
LDAP: error code 49 | Bind / Authentication Errors |
SSLHandshakeException: PKIX path building failed | TLS / SSL Errors |
| Test Connection passes, Test Authentication fails | TLS / SSL Errors |
PartialResultException: Referral | Search and Sync Errors |
SizeLimitExceededException | Search and Sync Errors |
Sync shows 0 imported, 0 updated | Search and Sync Errors |
LDAP: error code 53 - WILL_NOT_PERFORM | Password Change Errors |
| Groups sync but clicking a group raises errors | Group Mapper Errors |
Connection Errors
Connection Refused
Causes (in order of likelihood):
- LDAP server is down — Verify the LDAP service is running
- Wrong port — LDAP uses 389, LDAPS uses 636, AD Global Catalog uses 3268/3269
- Firewall blocking — Check network path from Keycloak to LDAP server
- Wrong protocol/port combination —
ldaps://on port 389 orldap://on port 636
Debug commands:
# Test TCP connectivity to LDAP port
nc -zv ad.example.com 389
nc -zv ad.example.com 636
# From inside a Keycloak Docker container
docker exec -it keycloak-container bash -c "curl -v telnet://ad.example.com:636"
# DNS resolution check
nslookup ad.example.com
Port Confusion Reference
| Port | Protocol | Connection URL Prefix | Notes |
|---|---|---|---|
| 389 | LDAP (plaintext) or StartTLS | ldap:// | Never use plaintext in production |
| 636 | LDAPS (TLS from start) | ldaps:// | Requires CA cert in truststore |
| 3268 | AD Global Catalog | ldap:// | Read-only, limited attributes |
| 3269 | AD Global Catalog over SSL | ldaps:// | Read-only, limited attributes, encrypted |
Common mistake: Using ldaps://ad.example.com:389 (LDAPS protocol on plaintext port) or ldap://ad.example.com:636 (plaintext protocol on LDAPS port). These combinations will always fail.
LDAP Connection Has Been Closed
This happens when the LDAP server closes idle connections, typically during high-load sync operations. Causes include connection pool exhaustion, LDAP server timeout, or network interruption.
Fix: Increase connectionPoolingMaxSize in Keycloak LDAP provider settings and check the LDAP server’s max connections configuration.
Bind and Authentication Errors
Error Code 49 — Invalid Credentials
Error code 49 is the generic “authentication failed” code, but Active Directory includes a hex sub-code that reveals the exact cause:
| AD Sub-Code | Meaning | Fix |
|---|---|---|
data 525 | User not found | Verify bind DN exists in AD |
data 52e | Wrong password | Correct the bind credential |
data 530 | Logon not permitted at this time | Check AD time-of-day restrictions |
data 531 | Logon not permitted from this workstation | Check AD workstation restrictions |
data 532 | Password expired | Reset the service account password |
data 533 | Account disabled | Re-enable the account in AD |
data 701 | Account expired | Extend the account expiration date |
data 773 | User must reset password | Reset password and clear “must change” flag |
How to read the sub-code: Look for the full error in Keycloak server logs:
The data 52e part is the sub-code — this means “wrong password”.
Test your bind credentials before configuring Keycloak:
# Active Directory (full DN format)
ldapsearch -x -H ldaps://ad.example.com:636 \
-D "CN=keycloak-svc,CN=Users,DC=corp,DC=example,DC=com" \
-W \
-b "DC=corp,DC=example,DC=com" \
"(sAMAccountName=testuser)"
# Active Directory (UPN format — simpler)
ldapsearch -x -H ldaps://ad.example.com:636 \
-D "[email protected]" \
-W \
-b "DC=corp,DC=example,DC=com" \
"(sAMAccountName=testuser)"
# OpenLDAP
ldapsearch -x -H ldaps://ldap.example.com:636 \
-D "cn=admin,dc=example,dc=com" \
-W \
-b "ou=people,dc=example,dc=com" \
"(uid=testuser)"
Wrong Bind DN Format
The most common cause of error code 49 is using the wrong bind DN format:
Account Lockout Warning
Clicking Test Authentication in Keycloak admin console with wrong credentials counts as a failed login attempt in Active Directory. Repeated clicks can trigger the AD account lockout policy and lock out your bind service account.
Prevention: Always test credentials with ldapsearch on the command line before configuring Keycloak.
TLS and SSL Errors
PKIX Path Building Failed (Most Common TLS Error)
This means the LDAP server’s certificate (or its CA) is not trusted by Keycloak’s Java runtime.
Critical Keycloak gotcha: The admin console’s Test Connection button only tests TCP socket connectivity. It does NOT test the TLS handshake. So “Test Connection” passes, but “Test Authentication” fails with SSLHandshakeException.
Fix for Keycloak 22+ (Quarkus):
# Step 1: Extract the CA certificate from the LDAP server
openssl s_client -connect ad.example.com:636 -showcerts </dev/null 2>/dev/null \
| openssl x509 -outform PEM > /opt/keycloak/conf/truststores/ldap-ca.pem
# Step 2: Restart Keycloak — it auto-loads PEM files from conf/truststores/
bin/kc.sh start
Fix for older Keycloak (or JKS preference):
# Step 1: Extract the certificate
openssl s_client -connect ad.example.com:636 -showcerts </dev/null 2>/dev/null \
| openssl x509 -outform PEM > ldap-ca.pem
# Step 2: Import into a Java keystore
keytool -importcert -trustcacerts \
-alias ldap-ca \
-file ldap-ca.pem \
-keystore /opt/keycloak/conf/truststore.jks \
-storepass changeit \
-noprompt
# Step 3: Configure Keycloak to use the truststore
bin/kc.sh start \
--spi-truststore-file-file=/opt/keycloak/conf/truststore.jks \
--spi-truststore-file-password=changeit
Docker / Kubernetes:
# Docker: Mount the certificate and restart
docker run \
-v /path/to/ldap-ca.pem:/opt/keycloak/conf/truststores/ldap-ca.pem:ro \
quay.io/keycloak/keycloak:latest start
# Kubernetes: Create a secret and mount to conf/truststores/
kubectl create secret generic ldap-ca-cert \
--from-file=ldap-ca.pem=/path/to/ldap-ca.pem
SAN Hostname Mismatch
The Connection URL uses an IP address, but the certificate only has hostnames in the Subject Alternative Name (SAN) field.
Fix: Use the FQDN in the Connection URL, not the IP address:
Diagnosing TLS Issues
# View the full certificate chain
openssl s_client -connect ad.example.com:636 -showcerts </dev/null
# Check certificate expiration date
openssl s_client -connect ad.example.com:636 </dev/null 2>/dev/null \
| openssl x509 -noout -dates
# View SAN entries (verify hostname matches)
openssl s_client -connect ad.example.com:636 </dev/null 2>/dev/null \
| openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# Test StartTLS on port 389
openssl s_client -connect ad.example.com:389 -starttls ldap -showcerts </dev/null
StartTLS Disables Connection Pooling
Java’s JNDI implementation does not support connection pooling with StartTLS. If you use StartTLS (ldap:// on port 389 with TLS upgrade), Keycloak creates a new connection for every LDAP operation, causing severe performance degradation under load.
Fix: Use LDAPS (ldaps:// on port 636) instead of StartTLS if you need connection pooling.
Search and Sync Errors
PartialResultException — AD Referrals
This is an Active Directory multi-domain forest issue. When the search base spans multiple domains, AD returns referrals (pointers to other domain controllers) instead of results. Keycloak does not follow referrals by default.
Fix Option 1 — Use Global Catalog (recommended for multi-domain):
Note: Global Catalog returns a limited subset of attributes. Custom attributes and passwords are not available through Global Catalog.
Fix Option 2 — Narrow the search base:
SizeLimitExceededException
Active Directory has a default size limit of 1000 entries per search. If your directory has more than 1000 users, this error occurs unless pagination is enabled.
Fix: Enable Pagination in the Keycloak LDAP provider settings (Admin Console → User Federation → LDAP → Pagination = ON).
Sync Reports 0 Imported, 0 Updated
Causes:
- Users already exist in Keycloak — Users were created locally before LDAP federation was configured. They exist in Keycloak’s database but are not linked to the LDAP provider.
- Wrong
Username LDAP Attribute— Must match the directory type:
| Directory | Correct Username LDAP Attribute |
|---|---|
| Active Directory | sAMAccountName |
| OpenLDAP | uid |
| FreeIPA | uid |
- Custom User LDAP Filter too restrictive — Test your filter with
ldapsearchto verify it returns expected users.
TimeLimitExceededException
The search is taking too long. Add a Custom User LDAP Filter to narrow results, or increase the LDAP server’s search time limit.
Password Change Errors
WILL_NOT_PERFORM (Error Code 53)
Active Directory requires LDAPS for password change operations. Attempting to change a password over a non-SSL connection triggers this error.
Fix:
- Use
ldaps://(port 636) instead ofldap://(port 389) - Ensure the bind account has “Reset Password” permission on the target user objects
- Set Edit Mode to
WRITABLEin Keycloak LDAP settings
Insufficient Access Rights (Error Code 50)
The bind account does not have write permissions, but Keycloak’s Edit Mode is set to WRITABLE.
Fix: Either grant the bind account the required AD permissions (Write userAccountControl, Write pwdLastSet, Reset password) or set Edit Mode to READ_ONLY.
Group Mapper Errors
Wrong Group Object Classes
Groups sync but clicking a group in Keycloak admin raises errors. This happens when the Group Object Classes setting doesn’t match your directory:
| Directory | Correct Group Object Classes |
|---|---|
| Active Directory | group |
| OpenLDAP | groupOfNames |
| FreeIPA | groupofnames |
Empty Member Attribute Bug
LDAP groups with an empty member: attribute crash the group mapper in Keycloak versions before 22.0.3. Upgrade Keycloak or clean up empty groups in the LDAP directory.
Active Directory vs OpenLDAP vs FreeIPA
Configuration differs significantly across directory types. Using wrong values is the most common source of errors:
| Setting | Active Directory | OpenLDAP | FreeIPA |
|---|---|---|---|
| Vendor | Active Directory | Other | Red Hat Directory Server |
| Username Attribute | sAMAccountName | uid | uid |
| UUID Attribute | objectGUID | entryUUID | ipaUniqueID |
| User Object Classes | person, organizationalPerson, user | inetOrgPerson, organizationalPerson | inetOrgPerson, organizationalPerson, person |
| Users DN | CN=Users,DC=corp,DC=com | ou=people,dc=example,dc=com | cn=users,cn=accounts,dc=example,dc=com |
| Group Object Classes | group | groupOfNames | groupofnames |
| Pagination | Required (1000 limit) | Optional | Optional |
| Password changes | LDAPS required | Depends on config | LDAPS recommended |
AD-Specific Pitfalls
- Account lockout during testing — “Test Authentication” counts as a login attempt
- UPN vs DN for bind — AD accepts both
[email protected](UPN) and full DN - Referrals in multi-domain forests — Causes
PartialResultException(use Global Catalog port 3268/3269) - Password changes require LDAPS — Error code 53
WILL_NOT_PERFORMon non-SSL connections
OpenLDAP-Specific Pitfalls
- No built-in pagination — Disable pagination in Keycloak if you get
SizeLimitExceededExceptionon OpenLDAP entryUUIDavailability — May require theentryUUIDoverlay on older OpenLDAP versions- Plaintext passwords — OpenLDAP stores passwords in plaintext unless
ppolicyoverlay is configured
FreeIPA-Specific Pitfalls
- UUID attribute — Must manually change to
ipaUniqueID(Keycloak does not auto-detect) - Users DN structure — Note the
cn=accountslevel:cn=users,cn=accounts,dc=example,dc=com
Debug Logging
Enable Keycloak LDAP Trace Logging
# CLI startup parameter
bin/kc.sh start --log-level="INFO,org.keycloak.storage.ldap:TRACE"
# Environment variable
export KC_LOG_LEVEL="INFO,org.keycloak.storage.ldap:TRACE"
# In conf/keycloak.conf
log-level=INFO,org.keycloak.storage.ldap:TRACE
# Docker
docker run -e KC_LOG_LEVEL="INFO,org.keycloak.storage.ldap:TRACE" \
quay.io/keycloak/keycloak:latest start
Enable TLS/SSL Debug Output
# Full SSL debug (very verbose)
export JAVA_OPTS="-Djavax.net.debug=all"
# SSL handshake only (recommended for troubleshooting)
export JAVA_OPTS="-Djavax.net.debug=ssl:handshake"
# Docker
docker run \
-e KC_LOG_LEVEL="INFO,org.keycloak.storage.ldap:TRACE" \
-e JAVA_OPTS="-Djavax.net.debug=ssl:handshake" \
quay.io/keycloak/keycloak:latest start
Admin Console Gotcha: Save Before Testing
If you change the Connection URL or Bind Credential in the admin console and click “Test Authentication” without clicking “Save” first, Keycloak tests with the OLD saved values, not your new input. This causes false negatives and — if the old password is wrong — can lock out the bind account in Active Directory.
Always click Save before clicking Test Connection or Test Authentication.
Complete Debugging Workflow
When any LDAP error occurs, work through this sequence:
Step 1: Test network connectivity
nc -zv ad.example.com 636
Step 2: Test TLS/certificate chain
openssl s_client -connect ad.example.com:636 -showcerts </dev/null
Step 3: Test bind credentials
ldapsearch -x -H ldaps://ad.example.com:636 \
-D "CN=keycloak-svc,CN=Users,DC=corp,DC=example,DC=com" \
-W -b "DC=corp,DC=example,DC=com" "(sAMAccountName=testuser)"
Step 4: Enable debug logging and reproduce
bin/kc.sh start --log-level="INFO,org.keycloak.storage.ldap:TRACE"
Step 5: Check Keycloak events
Navigate to Admin Console → Events → Login Events → filter by REGISTER_ERROR or check server logs for org.keycloak.storage.ldap entries.
