•
Madalin
Development enhanced by AI

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.

A woman in pixel art style installs components in a small datacenter with server racks, network switches, and firewalls for LDAP directory server setup.

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:

  1. 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.
  2. TLS is now provided by OpenSSL, not GnuTLS. This is a Debian 13 packaging change affecting both slapd and the client library libldap2. Server and client TLS configuration still uses the same directives (TLS_CACERT, etc.), but GnuTLS-specific debugging tools/priority strings are no longer relevant.
  3. 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 with ldapmodify — not through a hand-edited slapd.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 sudo access.
  • 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/hosts or 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) in olcRootPW under olcDatabase={1}mdb,cn=config.
  • Never edit /etc/ldap/slapd.d files by hand while slapd is running — the daemon rewrites them, and manual edits are a classic cause of corruption. Always change configuration through ldapmodify against cn=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 — reset olcRootPW as 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 auth is 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/slapcat while 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-upgrades will 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 stats and 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

Madalin

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.