# Bookstack Ldap Issues

## The Problem

BookStack LDAP login fails with one of these errors:

```
This information does not correspond to any account.
```

```
ldap_bind(): Unable to bind to server: Can't contact LDAP server
```

Or login silently fails — you enter correct LDAP credentials but BookStack rejects them without a useful error message.

## The Cause

BookStack's LDAP integration has several configuration points that must all be correct for authentication to work. The most common causes:

1. **User filter placeholder syntax** — `{user}` vs `${user}` confusion
2. **Docker Compose variable escaping** — the `$` in `${user}` gets eaten by Docker's env interpolation
3. **TLS/SSL certificate validation** — self-signed certs fail PHP's LDAP TLS check
4. **Wrong bind DN or base DN** — the service account can't search or the search starts in the wrong place
5. **SELinux blocking** — on RHEL/Rocky, SELinux silently blocks outbound LDAP connections

## The Fix

### Method 1: Fix the User Filter (Most Common)

The `LDAP_USER_FILTER` variable must contain the correct placeholder syntax.

**In a `.env` file:**
```env
LDAP_USER_FILTER=(&(sAMAccountName=${user}))
```

**In `docker-compose.yml` environment section** — use double `$$` to escape Docker's variable interpolation:
```yaml
environment:
  LDAP_USER_FILTER: "(&(sAMAccountName=$${user}))"
```

Docker Compose treats `$` as a variable reference. A single `$` results in BookStack receiving a literal string without the placeholder, so the filter never matches any user.

**Verify what BookStack actually sees:**
```bash
docker exec bookstack printenv | grep LDAP_USER_FILTER
# Should show: LDAP_USER_FILTER=(&(sAMAccountName=${user}))
# NOT: LDAP_USER_FILTER=(&(sAMAccountName=))
```

For **OpenLDAP**, use `uid` instead of `sAMAccountName`:
```env
LDAP_USER_FILTER=(&(uid=${user}))
```

### Method 2: Fix TLS/SSL Certificate Issues

If you see `Can't contact LDAP server` when using LDAPS or StartTLS:

**Option A: Use StartTLS instead of LDAPS (recommended):**
```env
LDAP_SERVER=ldap.example.com:389
LDAP_START_TLS=true
LDAP_TLS_INSECURE=false
```

**Option B: Skip certificate validation (testing only):**
```env
LDAP_SERVER=ldaps://ldap.example.com:636
LDAP_TLS_INSECURE=true
```

**Option C: Mount your CA certificate into the container:**
```yaml
volumes:
  - ./my-ca.crt:/usr/local/share/ca-certificates/my-ca.crt:ro
```

Then rebuild the certificate store inside the container:
```bash
docker exec bookstack update-ca-certificates
docker compose restart bookstack
```

### Method 3: Fix Network Connectivity

Test that the BookStack container can reach your LDAP server:

```bash
# Test from inside the container
docker exec bookstack bash -c "apt-get update && apt-get install -y ldap-utils && \
  ldapsearch -x -H ldap://ldap.example.com:389 -b '' -s base objectclass=*"
```

If this fails, check:
- Firewall rules between the Docker network and your LDAP server
- DNS resolution (use IP address in `LDAP_SERVER` to rule out DNS issues)
- Correct port (389 for LDAP, 636 for LDAPS)

**On RHEL/Rocky with SELinux:**
```bash
# Check if SELinux is blocking
getenforce
# If "Enforcing", allow httpd to connect to LDAP:
sudo setsebool -P httpd_can_network_connect on
```

### Method 4: Verify Bind Credentials and Base DN

Test the service account credentials independently:

```bash
# Test bind with ldapsearch
ldapsearch -x -H ldap://ldap.example.com:389 \
  -D "cn=bookstack,cn=users,dc=example,dc=com" \
  -w "your-service-password" \
  -b "dc=example,dc=com" \
  "(sAMAccountName=testuser)"
```

This should return the test user's attributes. If it returns nothing:
- **"Invalid credentials"** → wrong `LDAP_DN` or `LDAP_PASS`
- **Empty result** → wrong `LDAP_BASE_DN` or the service account lacks search permissions
- **"No such object"** → the `LDAP_BASE_DN` doesn't exist in your directory

**Common Active Directory base DNs:**
```env
# Users are typically in:
LDAP_BASE_DN=cn=Users,dc=example,dc=com
# Note the capital "U" — Active Directory uses "Users" not "users"
```

### Method 5: Enable Debug Logging

Turn on BookStack's debug mode to see detailed LDAP errors:

```env
APP_DEBUG=true
LDAP_DUMP_USER_DETAILS=true
```

Restart BookStack, attempt a login, and check the logs:

```bash
docker compose logs bookstack | grep -i ldap
```

`LDAP_DUMP_USER_DETAILS=true` logs the attributes returned by LDAP for matched users — useful for verifying that email and display name attributes map correctly.

**Disable debug mode after troubleshooting** — it exposes sensitive information in error pages.

## Complete Working Configuration

| Variable | OpenLDAP Example | Active Directory Example |
|----------|-----------------|--------------------------|
| `AUTH_METHOD` | `ldap` | `ldap` |
| `LDAP_SERVER` | `ldap.example.com:389` | `dc01.corp.example.com:389` |
| `LDAP_BASE_DN` | `dc=example,dc=com` | `cn=Users,dc=corp,dc=example,dc=com` |
| `LDAP_DN` | `cn=bookstack,ou=services,dc=example,dc=com` | `cn=bookstack,cn=Users,dc=corp,dc=example,dc=com` |
| `LDAP_PASS` | `service-password` | `service-password` |
| `LDAP_USER_FILTER` | `(&(uid=${user}))` | `(&(sAMAccountName=${user}))` |
| `LDAP_ID_ATTRIBUTE` | `uid` | `objectGUID` |
| `LDAP_EMAIL_ATTRIBUTE` | `mail` | `mail` |
| `LDAP_DISPLAY_NAME_ATTRIBUTE` | `cn` | `displayName` |
| `LDAP_START_TLS` | `true` | `true` |
| `LDAP_USER_TO_GROUPS` | `false` | `false` |

## Prevention

- Always test LDAP connectivity with `ldapsearch` before configuring BookStack
- Use double `$$` for the user filter placeholder when setting it in `docker-compose.yml`
- Start with `LDAP_USER_TO_GROUPS=false` — enable group sync only after basic login works
- Keep `APP_DEBUG=false` in production
- Document your LDAP_BASE_DN — it's the most frequently wrong setting

## Related

- [How to Self-Host BookStack](/apps/bookstack)
- [Best Self-Hosted Note-Taking Apps](/best/note-taking)
- [BookStack vs Wiki.js](/compare/bookstack-vs-wiki-js)
- [Security Basics for Self-Hosting](/foundations/security-hardening)
