Security AWS

OpenLDAP on AWS User Guide

| Product: OpenLDAP on AWS

Overview

This image runs OpenLDAP 2.6, the widely deployed open source implementation of the Lightweight Directory Access Protocol. Its standalone daemon, slapd, stores users, groups and organisational data in a hierarchical directory and answers standard LDAP queries, so your applications, operating systems and network services can share one authoritative identity source.

slapd runs as a systemd service that starts on boot, using the fast memory mapped mdb backend. The directory database lives on a dedicated EBS volume mounted at /var/lib/ldap, separate from the operating system disk and independently resizable.

The directory serves plain LDAP on port 389 (with StartTLS) and LDAP over TLS (LDAPS) on port 636. Unlike a loopback only appliance, slapd binds on all interfaces so downstream services can reach it. This is powerful, and it is also a responsibility: an LDAP endpoint reachable from the internet is a serious exposure. Restrict the 389 and 636 security group rules to your own VPC CIDR rather than 0.0.0.0/0, and prefer LDAPS on 636 for any bind that carries credentials. Both are covered below.

The directory is seeded with a base dc=example,dc=com, the organisational units ou=people and ou=groups, and a demonstration user uid=demo, so an authenticated search returns real data the moment the instance is up. The directory administrator binds as cn=admin,dc=example,dc=com.

The image ships with no usable administrator credential. A one-shot first-boot service generates a unique administrator password of at least 24 characters on every instance, applies it to the slapd rootdn, regenerates the self-signed TLS certificate per instance, and writes the result to /root/openldap-credentials.txt with mode 0600. No two instances launched from this image share a directory password or a TLS key. This guide proves the password authenticates below.

OpenLDAP slapd running under systemd, serving LDAP on port 389 and LDAPS on port 636

Prerequisites

Before you deploy this image you need:

  • An Amazon Web Services account where you can launch EC2 instances
  • IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
  • An EC2 key pair in the target Region for SSH access to the instance
  • A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network only, and ports 389 and 636 from the private CIDR of the services that will bind to the directory
  • An SSH client on your workstation; an LDAP browser is useful but not required

Launching from the AWS Marketplace

  1. Open the product page in AWS Marketplace and choose Continue to Subscribe
  2. Accept the terms, then choose Continue to Configuration
  3. Select the software version and the Region you want to deploy into, then choose Continue to Launch
  4. Choose Launch through EC2 for full control of networking and storage
  5. Pick an instance type. m5.large is the recommended minimum; scale up as your entry count and query concurrency grow
  6. Select your VPC, subnet and key pair
  7. Attach a security group that allows inbound TCP 22 from your management network. Add TCP 389 and TCP 636 only from the private CIDR of the services that will bind to the directory, never from 0.0.0.0/0
  8. Launch, and wait for the instance to reach the 2/2 checks passed state

Launching with the AWS CLI

Substitute your own AMI id, key pair, security group and subnet:

aws ec2 run-instances \
  --image-id <ami-id> \
  --instance-type m5.large \
  --key-name <your-key-pair> \
  --security-group-ids <your-sg-id> \
  --subnet-id <your-subnet-id> \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=openldap}]'

Connecting to your instance

Connect over SSH on port 22 as the default login user for the operating system variant you launched:

OS variant SSH login user
Ubuntu 24.04 LTS ubuntu
ssh -i /path/to/your-key.pem ubuntu@<public-ip>

Verifying the directory is running

Confirm the service is active and reporting the installed version:

systemctl is-active slapd.service
slapd -VVV 2>&1 | head -1

Real output from a deployed instance:

active
@(#) $OpenLDAP: slapd 2.6.10+dfsg-0ubuntu0.24.04.1 (Sep 23 2025) $

Check the listening sockets. slapd binds LDAP on 389 and LDAPS on 636:

ss -tln | grep -E ':389 |:636 '
LISTEN 0      2048         0.0.0.0:389       0.0.0.0:*
LISTEN 0      2048         0.0.0.0:636       0.0.0.0:*
LISTEN 0      2048            [::]:389          [::]:*
LISTEN 0      2048            [::]:636          [::]:*

Retrieving the administrator credentials

The per instance administrator password is written to a root-only file on first boot. View it with:

sudo cat /root/openldap-credentials.txt

The file contains the generated password along with the admin bind DN, the base DN, and the resolved LDAP and LDAPS URIs for this instance:

# OpenLDAP on Ubuntu 24.04 LTS (cloudimg AWS Marketplace image)
# These credentials are unique to THIS VM, generated on first boot. Store them safely.

OPENLDAP_ADMIN_DN=cn=admin,dc=example,dc=com
OPENLDAP_ADMIN_PASSWORD=<generated-on-first-boot>
openldap.base_dn=dc=example,dc=com
openldap.ldap_uri=ldap://<this-instance-public-ip>:389
openldap.ldaps_uri=ldaps://<this-instance-public-ip>:636

Load the password into a shell variable rather than printing it, so it never lands in your shell history or terminal scrollback. The commands in the rest of this guide use $PW:

PW=$(grep '^OPENLDAP_ADMIN_PASSWORD=' /root/openldap-credentials.txt | cut -d= -f2-)
if [ -n "$PW" ]; then echo "administrator password loaded (${#PW} characters)"; else echo "not yet generated"; fi
administrator password loaded (24 characters)

Confirm the file is readable only by root:

stat -c '%a %U:%G' /root/openldap-credentials.txt
600 root:root

Querying the directory

Bind as the administrator and confirm your identity with ldapwhoami. The password comes from $PW, so it is never echoed:

PW=$(grep '^OPENLDAP_ADMIN_PASSWORD=' /root/openldap-credentials.txt | cut -d= -f2-)
ldapwhoami -x -H ldap://127.0.0.1 -D 'cn=admin,dc=example,dc=com' -w "$PW"
dn:cn=admin,dc=example,dc=com

Search the seeded directory. This returns the base entry, both organisational units and the demonstration user:

PW=$(grep '^OPENLDAP_ADMIN_PASSWORD=' /root/openldap-credentials.txt | cut -d= -f2-)
ldapsearch -x -LLL -H ldap://127.0.0.1 -D 'cn=admin,dc=example,dc=com' -w "$PW" -b 'dc=example,dc=com' '(objectclass=*)' dn
dn: dc=example,dc=com
dn: ou=people,dc=example,dc=com
dn: ou=groups,dc=example,dc=com
dn: uid=demo,ou=people,dc=example,dc=com

An authenticated bind and search over LDAP and LDAPS returning the seeded directory entries

Confirming the LDAPS listener works

Bind over TLS on port 636. The LDAPTLS_REQCERT=never setting accepts the per instance self-signed certificate; for production, install your own CA-signed certificate as described later and drop that setting:

PW=$(grep '^OPENLDAP_ADMIN_PASSWORD=' /root/openldap-credentials.txt | cut -d= -f2-)
LDAPTLS_REQCERT=never ldapwhoami -x -H ldaps://127.0.0.1:636 -D 'cn=admin,dc=example,dc=com' -w "$PW"
dn:cn=admin,dc=example,dc=com

Confirming the administrator password is unique to your instance

The image ships no usable administrator credential; the password is generated on first boot. This check proves that the generated password is accepted and that a wrong password is rejected with LDAP result 49, without printing either:

PW=$(grep '^OPENLDAP_ADMIN_PASSWORD=' /root/openldap-credentials.txt | cut -d= -f2-)
GOOD=$(ldapwhoami -x -H ldap://127.0.0.1 -D 'cn=admin,dc=example,dc=com' -w "$PW" 2>&1 || true)
BAD=$(ldapwhoami -x -H ldap://127.0.0.1 -D 'cn=admin,dc=example,dc=com' -w 'wrong-password' 2>&1 || true)
echo "$GOOD" | grep -qi 'dn:cn=admin' && echo "generated password: ACCEPTED" || echo "generated password: REJECTED"
echo "$BAD" | grep -qi 'Invalid credentials\|(49)' && echo "wrong password: REJECTED (result 49)" || echo "wrong password: ACCEPTED"
generated password: ACCEPTED
wrong password: REJECTED (result 49)

Checking the directory data volume

The mdb directory database lives on a dedicated EBS volume mounted at /var/lib/ldap, so it persists across restarts and is independently resizable:

findmnt -no SOURCE,TARGET,FSTYPE /var/lib/ldap
ls -1 /var/lib/ldap/data.mdb
/dev/nvme1n1 /var/lib/ldap ext4
/var/lib/ldap/data.mdb

Restricting the LDAP ports to your network

slapd binds on all interfaces so downstream services can reach it, but you should never leave 389 and 636 open to the internet. Two layers of control:

  1. Security group — the primary control. In the EC2 console, edit the instance's security group so the inbound rules for TCP 389 and TCP 636 allow only the private CIDR of the services that bind to the directory (for example 10.0.0.0/16), never 0.0.0.0/0. Keep port 22 restricted to your management network.
  2. Prefer LDAPS — for any bind that carries credentials, use ldaps://<host>:636 (or StartTLS on 389) so the bind DN and password are encrypted in transit. A plaintext ldap:// bind over an untrusted network exposes the credentials.

Anonymous read of directory entries other than userPassword is permitted by default, which is standard LDAP behaviour. If you require authenticated reads, tighten the access controls (olcAccess) on the mdb database to your policy.

Installing your own TLS certificate

The image ships a self-signed certificate regenerated per instance. For production, replace it with a certificate signed by your own CA. Copy your certificate, key and CA chain into /etc/ldap/certs/, set ownership to root:openldap with mode 0640 on the key, then point the olcTLS* attributes on cn=config at them:

# Run as root. Adjust the paths to your own cert/key/CA files.
install -o root -g openldap -m 0640 /path/to/your.key /etc/ldap/certs/ldap.key
install -o root -g openldap -m 0644 /path/to/your.crt /etc/ldap/certs/ldap.crt
ldapmodify -Y EXTERNAL -H ldapi:/// <<'LDIF'
dn: cn=config
changetype: modify
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/certs/ldap.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/certs/ldap.key
LDIF
systemctl restart slapd

Adding your own directory data

Replace the demonstration entries with your own organisational structure. Write an LDIF file and add it as the administrator. The commands below are run once and change the directory, so they are shown for reference rather than executed automatically:

# my-org.ldif
dn: ou=engineering,dc=example,dc=com
objectClass: organizationalUnit
ou: engineering

dn: uid=jsmith,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
uid: jsmith
cn: Jane Smith
sn: Smith
mail: jsmith@example.com
# Run as root; $PW is your administrator password from the credentials file.
ldapadd -x -H ldap://127.0.0.1 -D 'cn=admin,dc=example,dc=com' -w "$PW" -f my-org.ldif

To use your own domain, set the base DN when you plan your directory; changing the suffix of a populated directory is a re-provisioning exercise best done on a fresh instance.

Changing the administrator password

To rotate the administrator password yourself, generate a new hash with slappasswd and replace olcRootPW. This is a one-off administrative change, shown for reference:

# Run as root.
NEWHASH=$(slappasswd -s 'your-new-password')
ldapmodify -Y EXTERNAL -H ldapi:/// <<LDIF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRootPW
olcRootPW: ${NEWHASH}
LDIF

Backup and maintenance

  • Back up the directory by taking an EBS snapshot of the volume mounted at /var/lib/ldap, or by exporting the DIT to LDIF with slapcat -b dc=example,dc=com -l backup.ldif (run as root while slapd is stopped for a consistent export, or online for a point-in-time export).
  • Back up the configuration with slapcat -b cn=config -l config.ldif to capture your olcAccess rules and TLS settings.
  • Apply operating system updates with sudo apt update && sudo apt upgrade; unattended-upgrades is enabled for security patches.
  • Resize the directory volume by growing the EBS volume in the console, then sudo resize2fs /dev/nvme1n1 on the instance.

Troubleshooting

  • ldap_bind: Invalid credentials (49) — the password does not match. Re-read it with sudo cat /root/openldap-credentials.txt; on a brand new instance, wait for first boot to complete (the credentials file still shows the placeholder text until it does).
  • A remote client cannot reach the directory — confirm the security group opens 389 or 636 from the client's CIDR, and that the client is inside the VPC or reaching it over a private path. Check the listeners with ss -tln | grep -E ':389 |:636 '.
  • LDAPS certificate errors — the shipped certificate is self-signed. Either install your own CA-signed certificate (above) or, for testing only, set LDAPTLS_REQCERT=never on the client.
  • Service will not start — inspect journalctl -u slapd --no-pager | tail -40 and confirm /var/lib/ldap is mounted with findmnt /var/lib/ldap.

Support

Every cloudimg deployment is backed by our engineers, available around the clock by email and live chat. Support covers deployment and sizing guidance, directory schema and DIT design, LDIF import, hardening the LDAP and LDAPS listeners, TLS certificate configuration, access control configuration, replication setup, backup and restore of the directory database, and OpenLDAP upgrades and troubleshooting. Critical issues receive a one-hour average response time. Contact support@cloudimg.co.uk.

OpenLDAP is a registered trademark of the OpenLDAP Foundation. cloudimg is not affiliated with the OpenLDAP Foundation. All product and company names are trademarks or registered trademarks of their respective holders.