slapd on Debian 13 "Trixie" — Installation & Maintenance Guide
Learn to install, configure, and secure slapd (OpenLDAP) on Debian 13 with modern MDB backend, OpenSSL TLS, and cn=config management—no legacy slapd.conf needed.
This guide walks through installing, configuring, populating, securing, and maintaining slapd — the OpenLDAP Standalone LDAP Daemon — on Debian 12/13.
Debian 13 ships OpenLDAP 2.6.10 (slapd 2.6.10+dfsg-1, source package openldap). Three things matter before you start:
- MDB is the only real database backend. The legacy BDB/HDB backends were removed in OpenLDAP 2.5; a fresh Debian install uses the LMDB (
mdb) backend exclusively. - TLS is now provided by OpenSSL, not GnuTLS. This is a Debian 13 packaging change affecting both
slapdand the client librarylibldap2. Server and client TLS configuration still uses the same directives (TLS_CACERT, etc.), but GnuTLS-specific debugging tools/priority strings are no longer relevant. - Configuration lives in
cn=config(OLC). The daemon is configured through an LDIF tree at/etc/ldap/slapd.d, edited over the LDAP protocol itself withldapmodify— not through a hand-editedslapd.conf(which is legacy and only used for one-way conversion).
Throughout this guide, the example domain is example.com, so the base DN is dc=example,dc=com and the server is ldap.example.com. Replace these with your own values.
1. Prerequisites and planning
- A Debian 13 (trixie) machine with root or
sudoaccess. - Decide your base DN from your DNS domain:
example.com→dc=example,dc=com. This is baked in during installation, so choose before you install. - Make sure the host has a stable FQDN (check
hostname -f; fix/etc/hostsor DNS if needed) — it matters for TLS certificates. - Know your ports:
- 389/tcp — LDAP (plain, used for StartTLS upgrades)
- 636/tcp — LDAPS (LDAP over TLS)
- A Unix socket
ldapi:///for local administration (no network exposure)
If a firewall is active, open the ports later (Section 8 has the commands).
2. Installation
2.1 Install packages
sudo apt update
sudo apt install slapd ldap-utils
slapd— the server.ldap-utils— client tools (ldapsearch,ldapadd,ldapmodify,ldappasswd,slappasswd, …).
During installation, debconf asks for an LDAP admin password. A base DN is also created automatically from your machine’s DNS domain.
2.2 Reconfigure with debconf
To set the base DN, organization name, and admin password properly, run the interactive configuration:
sudo dpkg-reconfigure slapd
Answer the prompts as follows:
| Prompt | Recommended answer |
|---|---|
| Omit OpenLDAP server configuration? | No |
| DNS domain name | example.com (forms the base DN) |
| Organization name | Example Organization |
| LDAP admin password | a strong password (twice) |
| Database backend | MDB |
| Remove database when slapd is purged? | No (keeps data if you ever remove/reinstall the package) |
| Move old database? | No (yes only if you re-run setup on a populated server) |
| Allow LDAPv2? | No (v2 is obsolete and insecure) |
2.3 Verify the install
systemctl status slapd --no-pager
sudo ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b cn=config -s base dn
ldapsearch -x -LLL -b dc=example,dc=com -s base dn
Expected results: slapd is active (running), the config query returns dn: cn=config, and the data query returns dn: dc=example,dc=com.
Why the two styles? The first uses SASL EXTERNAL over the Unix socket (ldapi:///) — as root, this authenticates you implicitly and lets you administer cn=config without a password. The second is a plain anonymous search of your data tree. This dual access pattern (-Y EXTERNAL -H ldapi:/// for config, -x -D cn=admin,... -W for data) is used throughout the rest of the guide.
3. How Debian lays out slapd
Knowing the file layout makes maintenance and recovery far easier:
| Path | Contents |
|---|---|
/etc/ldap/slapd.d/ |
The cn=config database as an LDIF tree (owned by openldap) |
/var/lib/ldap/ |
The MDB data files: data.mdb, lock.mdb |
/etc/default/slapd |
Startup options incl. SLAPD_SERVICES (which listeners to open) |
/etc/ldap/ldap.conf |
System-wide client defaults (base, URI, CA cert) |
/etc/ldap/schema/ |
Schema files (both .schema and .ldif forms) |
/usr/sbin/ |
slapcat, slapadd, slaptest, slapindex, slapmodify |
Key facts:
- Config DB is database
{0}(olcDatabase={-1}frontend,{0}config); your data DB is{1}mdb(olcDatabase={1}mdb,cn=config). - The admin account is
cn=admin,dc=example,dc=com; its password is stored (hashed) inolcRootPWunderolcDatabase={1}mdb,cn=config. - Never edit
/etc/ldap/slapd.dfiles by hand while slapd is running — the daemon rewrites them, and manual edits are a classic cause of corruption. Always change configuration throughldapmodifyagainstcn=config.
4. Post-install configuration
All configuration changes below follow the same pattern: write an LDIF file, apply it with ldapmodify or ldapadd over ldapi:/// as root.
4.1 Load extra schemas
The default install only has core. Most real deployments want cosine, nis (posixAccount/posixGroup), and inetorgperson:
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/cosine.ldif
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/nis.ldif
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/inetorgperson.ldif
Load in this order (nis needs cosine, inetorgperson needs cosine). If one is already loaded you’ll get “already exists” (type LDAP_ALREADY_EXISTS) — safe to ignore.
4.2 Change the admin password
Generate a salted SHA hash and put it in olcRootPW:
slappasswd -h {SSHA}
# -> {SSHA}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Create admin-pw.ldif:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRootPW
olcRootPW: {SSHA}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Apply:
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f admin-pw.ldif
Tip: because you can always authenticate as root via
ldapi:///, you can recover from a lost admin password — resetolcRootPWas above, no drama.
4.3 Add search indices
Indices keep searches fast as the directory grows. Create indices.ldif:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcDbIndex
olcDbIndex: objectClass eq
olcDbIndex: entryUUID eq
olcDbIndex: uid,cn,uidNumber,gidNumber,memberUid eq,pres
olcDbIndex: mail,sn,givenName eq,pres,sub
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f indices.ldif
On a live database, new indices are built in the background (use sudo slapindex -F /etc/ldap/slapd.d with slapd stopped if you want a full rebuild, then chown -R openldap:openldap /var/lib/ldap).
4.4 MDB maximum size
LMDB maps a sparse file of a fixed maximum size — by default 10 GiB (olcDbMaxSize: 10737418240). The file shows as 10G in ls but only consumes as much disk as actual data. If you expect a larger directory, raise it:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcDbMaxSize
olcDbMaxSize: 21474836480
Restart slapd after this change. If the map size is ever exceeded, writes fail until you increase it — see the troubleshooting table.
5. Populating the directory
5.1 Base structure
Create base.ldif:
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Example Organization
dc: example
dn: ou=people,dc=example,dc=com
objectClass: organizationalUnit
ou: people
dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
Load it (this time authenticating as the admin DN, because the data DB is not writable via EXTERNAL by default):
ldapadd -x -D cn=admin,dc=example,dc=com -W -f base.ldif
5.2 First user and group
Generate a password hash (slappasswd -h {SSHA}) and create user.ldif:
dn: uid=jdoe,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
uid: jdoe
cn: John Doe
sn: Doe
givenName: John
mail: jdoe@example.com
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/jdoe
loginShell: /bin/bash
userPassword: {SSHA}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
dn: cn=jdoe,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: jdoe
gidNumber: 10000
memberUid: jdoe
ldapadd -x -D cn=admin,dc=example,dc=com -W -f user.ldif
5.3 Test and manage passwords
# search as anonymous
ldapsearch -x -LLL -b dc=example,dc=com '(uid=jdoe)'
# authenticate as the user
ldapwhoami -x -D uid=jdoe,ou=people,dc=example,dc=com -W
# set/reset a user's password as admin (prompts for the new password)
ldappasswd -x -D cn=admin,dc=example,dc=com -W -S uid=jdoe,ou=people,dc=example,dc=com
6. TLS: StartTLS and LDAPS
LDAP traffic — including passwords — must never cross a network in cleartext. Two mechanisms exist: StartTLS (upgrade a port-389 connection) and LDAPS (TLS from the start on port 636). Configure both; they share the same certificates.
6.1 Obtain certificates
Option A — real certificate (recommended). Use your existing PKI or ACME/Let’s Encrypt for ldap.example.com. The key must be readable by the openldap user. If your key lives in /etc/letsencrypt/live/.../privkey.pem (root:ssl-cert, 0640), grant access:
sudo adduser openldap ssl-cert
and point slapd at the live files, or deploy a copy under /etc/ldap/ssl/:
sudo mkdir -p /etc/ldap/ssl
sudo cp /etc/letsencrypt/live/ldap.example.com/fullchain.pem /etc/ldap/ssl/slapd.crt
sudo cp /etc/letsencrypt/live/ldap.example.com/privkey.pem /etc/ldap/ssl/slapd.key
sudo chown root:openldap /etc/ldap/ssl/slapd.key
sudo chmod 640 /etc/ldap/ssl/slapd.key
Option B — self-signed (labs only).
cd /etc/ldap/ssl
sudo openssl req -x509 -newkey rsa:4096 -nodes -days 825 \
-keyout slapd.key -out slapd.crt -subj "/CN=ldap.example.com"
sudo chown root:openldap slapd.key && sudo chmod 640 slapd.key
For self-signed you skip the CA directive and clients must use TLS_REQCERT allow/never or import the cert — not acceptable for production.
6.2 Point slapd at the certificates
Create tls.ldif:
dn: cn=config
changetype: modify
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/ssl/slapd.crt
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/ssl/slapd.key
If you use a private CA, also add:
-
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/ssl/ca.crt
Apply and restart:
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f tls.ldif
sudo systemctl restart slapd
6.3 Enable LDAPS (port 636)
Edit /etc/default/slapd and set:
SLAPD_SERVICES="ldap:/// ldaps:/// ldapi:///"
sudo systemctl restart slapd
6.4 Configure clients
System-wide client defaults in /etc/ldap/ldap.conf:
BASE dc=example,dc=com
URI ldaps://ldap.example.com
TLS_CACERT /etc/ldap/ssl/ca.crt
6.5 Verify TLS
# StartTLS on 389 (-ZZ = require TLS, fail if it can't upgrade)
ldapsearch -x -ZZ -H ldap://ldap.example.com -b dc=example,dc=com dn
# LDAPS on 636
ldapsearch -x -H ldaps://ldap.example.com -b dc=example,dc=com dn
Debug TLS negotiation with ldapsearch -d 1 .... (Debian 13 note: since slapd/libldap now use OpenSSL, ignore older GnuTLS-oriented advice such as GnuTLS priority strings or gnutls-cli debugging.)
7. Access control (ACLs)
Access is controlled by olcAccess rules on the database, evaluated in numbered order, first match wins. Debian’s default policy allows anonymous read of everything except userPassword. A common hardening step is to drop anonymous access entirely. Example acl.ldif:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to attrs=userPassword
by self write
by anonymous auth
by * none
olcAccess: {1}to attrs=shadowLastChange
by self write
by * read
olcAccess: {2}to *
by self write
by users read
by * none
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f acl.ldif
Reading this policy:
by anonymous authis required — anonymous users must be able to authenticate (bind) even if they can read nothing else.- Users can read the whole tree and change their own password; nothing is exposed to unauthenticated clients.
- The built-in admin DN (
olcRootDN) bypasses ACLs entirely — another reason to keep its password strong and, ideally, restrict ports 389/636 at the firewall so the directory is not internet-facing.
Optional: password policy (ppolicy)
Enforce password quality, expiry, and lockout with the ppolicy overlay:
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/ppolicy.ldif
ppolicy.ldif:
dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: ppolicy
olcPPolicyDefault: cn=default,ou=policies,dc=example,dc=com
olcPPolicyHashCleartext: TRUE
dn: ou=policies,dc=example,dc=com
objectClass: organizationalUnit
ou: policies
dn: cn=default,ou=policies,dc=example,dc=com
objectClass: device
objectClass: pwdPolicyChecker
objectClass: pwdPolicy
cn: default
pwdMinLength: 12
pwdMaxAge: 7776000
pwdInHistory: 5
pwdCheckQuality: 2
pwdLockout: TRUE
pwdMaxFailure: 5
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f ppolicy.ldif
(pwdCheckQuality: 2 requires a cracklib-style checker; use 1 for length/history checks only.)
8. Firewall
Debian 13 uses nftables. If you use ufw as a frontend:
sudo ufw allow 389/tcp # LDAP + StartTLS
sudo ufw allow 636/tcp # LDAPS
Only allow these from networks that genuinely need the directory. Expose ldapi:///-based admin to local root only — it is not network reachable at all.
9. Maintenance
9.1 Service management and config checks
sudo systemctl restart slapd # apply /etc/default/slapd changes, etc.
sudo systemctl reload slapd # not used much; prefer restart
sudo slaptest -u -F /etc/ldap/slapd.d # validate config without touching data
Make slaptest a habit after major config changes but before a restart: it catches broken LDIF that would otherwise leave slapd refusing to start. If slapd ever fails to start after config changes, check journalctl -u slapd -e first — it names the offending config entry.
9.2 Backups (the most important section)
Back up both databases: config (-n 0) and data (-n 1). slapcat produces clean, portable LDIF:
sudo mkdir -p /var/backups/ldap
sudo slapcat -n 0 -l /var/backups/ldap/config.ldif
sudo slapcat -n 1 -l /var/backups/ldap/data.ldif
Automate daily via /etc/cron.d/slapd-backup:
0 2 * * * root /usr/sbin/slapcat -n 0 -l /var/backups/ldap/config-$(date +\%F).ldif && /usr/sbin/slapcat -n 1 -l /var/backups/ldap/data-$(date +\%F).ldif && find /var/backups/ldap -mtime +30 -delete
Also preserve (outside the host, in real backups): /etc/ldap/slapd.d/, /etc/default/slapd, /etc/ldap/ldap.conf, your certificates/keys, and the LDIF exports themselves.
A hot file-level copy of the MDB environment is possible with mdb_copy (package lmdb-utils):
sudo apt install lmdb-utils
sudo -u openldap mdb_copy /var/lib/ldap /var/backups/ldap/mdb-snapshot
Use LDIF (slapcat) as the canonical backup — it survives version and architecture changes — and mdb_copy/filesystem snapshots as a fast-recovery option.
9.3 Restore
sudo systemctl stop slapd
# --- config database (only if the config itself is lost/corrupt) ---
sudo mv /etc/ldap/slapd.d /etc/ldap/slapd.d.broken
sudo mkdir -p /etc/ldap/slapd.d
sudo slapadd -n 0 -F /etc/ldap/slapd.d -l /var/backups/ldap/config.ldif
# --- data database ---
sudo rm -f /var/lib/ldap/data.mdb /var/lib/ldap/lock.mdb
sudo slapadd -n 1 -F /etc/ldap/slapd.d -l /var/backups/ldap/data.ldif -q -w
# --- fix ownership, then start ---
sudo chown -R openldap:openldap /etc/ldap/slapd.d /var/lib/ldap
sudo systemctl start slapd
Notes:
-q= quick mode (no index updating during load),-w= write context CSN (important if you ever use replication).- Never run
slapadd/slapcatwhile slapd is running against the same database; always stop the daemon first (or restore into a clean alternate directory). - Ownership (
openldap:openldap) is the #1 reason a restored slapd fails to start.
9.4 Database health checks
# actual data size (sparse file; ls shows the max map size, du shows real usage)
sudo du -sh /var/lib/ldap/data.mdb
# detailed LMDB statistics
sudo apt install lmdb-utils
sudo mdb_stat -a -f /var/lib/ldap/data.mdb | head
# entry count
ldapsearch -x -LLL -b dc=example,dc=com -s base '(objectClass=*)' contextCSN
Watch the real usage against olcDbMaxSize. MDB never shrinks its file on delete — freed pages are reused internally, which is normal and fine.
9.5 Logging
Set the log level through cn=config:
dn: cn=config
changetype: modify
replace: olcLogLevel
olcLogLevel: stats
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f loglevel.ldif
Useful levels: none (quiet), stats (connections/operations/results — the daily driver), stats2, and for debugging acl, config, filter, packet (combine as stats acl config; any is extremely noisy). Read output with:
sudo journalctl -u slapd -f
To route slapd to its own syslog file, add a rsyslog filter (if $programname == 'slapd' then /var/log/slapd.log) — optional on modern single-journald hosts.
9.6 Monitoring
Enable the monitor backend (if not present) with monitor.ldif:
dn: olcDatabase=monitor,cn=config
objectClass: olcDatabaseConfig
olcDatabase: monitor
olcAccess: to *
by dn.exact="cn=admin,dc=example,dc=com" read
by * none
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f monitor.ldif
Then inspect live counters (connections, operations, waiters, per-database stats):
ldapsearch -x -D cn=admin,dc=example,dc=com -W -b cn=Monitor -s children
These entries are ideal inputs for a monitoring system (check the monitoring/watchdog entries under cn=Statistics,cn=Monitor and cn=Backends,cn=Monitor).
9.7 Upgrades and patches
sudo slapcat -n 0 -l /var/backups/ldap/pre-upgrade-config.ldif
sudo slapcat -n 1 -l /var/backups/ldap/pre-upgrade-data.ldif
sudo apt update && sudo apt full-upgrade
- Point releases (e.g.
2.6.10+dfsg-1→2.6.10+dfsg-1+deb13u1) are drop-in: the package restarts slapd automatically. Always take the Section 9.2 backup first. - Debian major release upgrades (trixie → forky+) historically require care: slapd on the new release may refuse to start with an old on-disk database. The robust, version-agnostic path is the slapcat/slapadd cycle from 9.2/9.3 — export on the old release, import on the new one.
- Track security announcements via
debian-security-announce; OpenLDAP CVEs are rare but do occur.unattended-upgradeswill apply them automatically if enabled.
9.8 Security checklist
- TLS configured and enforced; no cleartext binds on untrusted networks
- Anonymous read access removed (or deliberately justified)
- Strong admin password; admin DN used only for administration, not applications
- Per-application bind DNs with minimal ACLs instead of sharing admin
- Directory not exposed to the internet; firewall limits 389/636
- LDAPv2 disabled (default in Debian)
- Daily slapcat backups, tested restore (an untested backup is a hope, not a backup)
- Log level
statsand log retention/monitoring in place - Security updates applied
10. Troubleshooting quick table
| Symptom | Likely cause | Fix |
|---|---|---|
ldap_bind: Invalid credentials (49) |
Wrong DN or password; olcRootPW mismatch |
Verify the bind DN exists; reset olcRootPW via ldapi:/// (Section 4.2) |
No such object (32) |
Wrong base DN or suffix | Check with sudo ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(olcSuffix=*)' |
slapd won’t start after manual edits to /etc/ldap/slapd.d |
Corrupted/invalid config tree | sudo slaptest -u -F /etc/ldap/slapd.d; restore config DB from backup (9.3) |
conf(1): ... operations error |
Tools like slapadd/slapcat run while slapd is up, or wrong DB number |
Stop slapd first; use -n 0 for config, -n 1 for data |
Writes fail, log shows MDB MAP_FULL / Environment mapsize limit |
Directory grew past olcDbMaxSize |
Increase olcDbMaxSize (4.4) and restart; check for runaway entries |
| slapd won’t start after restore | File ownership/permissions | chown -R openldap:openldap /var/lib/ldap /etc/ldap/slapd.d |
ldap_start_tls: Connect error / certificate verify failed |
Key unreadable by openldap, wrong CA, hostname mismatch |
Check key perms (6.4.1), TLS_CACERT on clients, CN/SAN of the cert |
connect: connection refused on 636 |
ldaps:/// not in SLAPD_SERVICES |
Add it in /etc/default/slapd and restart (6.3) |
| Everything OK locally, fails remotely | Firewall | Open 389/636 (Section 8) |
General debugging order: journalctl -u slapd -e → sudo slaptest -u -F /etc/ldap/slapd.d → run the failing command with -d 1.
11. Cheat sheet
# --- queries ---
ldapsearch -x -LLL -b dc=example,dc=com '(objectClass=*)' # anonymous
ldapwhoami -x -D uid=jdoe,ou=people,dc=example,dc=com -W # test a bind
sudo ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b cn=config # inspect config
# --- modify ---
ldapadd -x -D cn=admin,dc=example,dc=com -W -f entries.ldif # add from LDIF
ldapmodify -x -D cn=admin,dc=example,dc=com -W -f change.ldif # modify data
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f config.ldif # modify cn=config
# --- users ---
ldappasswd -x -D cn=admin,dc=example,dc=com -W -S uid=jdoe,ou=people,dc=example,dc=com
slappasswd -h {SSHA} # make a hash
# --- service / maintenance ---
sudo systemctl restart slapd
sudo slaptest -u -F /etc/ldap/slapd.d # config check
sudo slapcat -n 1 -l data.ldif # export data
sudo slapcat -n 0 -l config.ldif # export config
sudo slapadd -n 1 -F /etc/ldap/slapd.d -l data.ldif -q -w # import (slapd stopped)
sudo slapindex -F /etc/ldap/slapd.d # rebuild indices (stopped)
sudo -u openldap mdb_copy /var/lib/ldap /backup/mdb-copy # hot file copy
References
- OpenLDAP 2.6 Administrator’s Guide — https://www.openldap.org/doc/admin26/
slapdpackage in Debian 13 (trixie) — https://packages.debian.org/trixie/slapd- Debian 13 “trixie” release notes (OpenSSL switch for slapd/libldap2) — https://www.debian.org/News/2025/20250809
- Local man pages on an installed system:
man slapd,slapd-mdb(5),slapd-config(5),slapo-ppolicy(5),ldapmodify(1),slapcat(8),slapadd(8)
Madalin
AI integrator🚀 Senior Architect | SRE & Database Expert | AI Orchestrator 👋 Building the future at the speed of thought. ⚡️ I don't just write code; I architect high-performance, bulletproof ecosystems. With a foundation in Systems Engineering and a mastery of Go and TypeScript, I bridge the gap between heavy-duty backend reliability and seamless, high-conversion frontends.
Continue the conversation
If this article reflects the challenges your organisation is navigating, explore more practical guidance across Madalin.