Security AWS

LLDAP Self-Hosted LDAP Identity Server on AWS User Guide

| Product: LLDAP

Overview

LLDAP (Light LDAP) is a lightweight, self-hosted authentication server that speaks LDAP and ships with a simple, friendly web admin UI for managing users and groups. It is an opinionated, easy alternative to OpenLDAP or Active Directory for small and mid-size self-hosted deployments, and acts as a single identity backbone for the many applications that already speak LDAP - Nextcloud, Gitea, Grafana, Jellyfin, Portainer and more. This cloudimg image runs LLDAP as a single hardened Rust service with an embedded SQLite datastore. The web admin UI is bound to the loopback address 127.0.0.1:17170 and fronted by an nginx reverse proxy that terminates TLS on port 443; the raw web port is never exposed off-box. The LDAP endpoint listens on port 3890 for downstream services and requires bind credentials, so a network probe cannot dump the directory. A unique admin password, JWT signing secret, key seed and self-signed TLS certificate are generated on the first boot of every instance, so no shared or default credential ships in the image. The directory database lives on a dedicated data volume mounted at /var/lib/lldap. Backed by 24/7 cloudimg support.

What is included:

  • LLDAP 0.6.3 (a single Rust binary) managed by systemd, serving an LDAP endpoint and a web admin UI
  • nginx on port 443 terminating TLS in front of the web admin UI, which is bound to the loopback address 127.0.0.1:17170
  • The LDAP endpoint on port 3890 for downstream services, requiring bind credentials (anonymous binds are refused)
  • A unique admin password, JWT secret, key seed and self-signed TLS certificate generated on first boot
  • The admin credential recorded in a root-only file
  • A dedicated data volume at /var/lib/lldap holding the SQLite directory database
  • lldap.service and nginx.service as systemd units, enabled and active
  • 24/7 cloudimg support

Prerequisites

An AWS account, an EC2 key pair in the target region, and a VPC with a public subnet. m5.large (2 vCPU / 8 GiB RAM) is the recommended instance type. Security group inbound: allow 22/tcp from your management network for SSH, 443/tcp for the web admin UI over TLS, and 3890/tcp for LDAP from the downstream services that will authenticate against the directory (restrict this to your VPC or application security group in production).

Step 1 - Launch the AMI

Subscribe to the listing in AWS Marketplace, choose Continue to Launch, and launch through the EC2 console or the AWS CLI. Select the m5.large instance type, your key pair, and a security group that opens ports 22, 443 and 3890.

aws ec2 run-instances \
  --image-id <ami-id> \
  --instance-type m5.large \
  --key-name <your-key-pair> \
  --security-group-ids <security-group-id> \
  --subnet-id <subnet-id> \
  --count 1

Step 2 - Connect to your instance

Connect over SSH as the default login user for your operating system variant.

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

Step 3 - Read your unique admin credential

The image generates a unique admin password on first boot and writes it to a root-only file. Read it:

sudo cat /root/lldap-credentials.txt

You will see the admin username and password plus the resolved web and LDAP URLs for this instance:

LLDAP_ADMIN_USERNAME=admin
LLDAP_ADMIN_PASSWORD=<LLDAP_ADMIN_PASSWORD>
lldap.web_url=https://<public-ip>/
lldap.ldap_url=ldap://<public-ip>:3890
lldap.base_dn=dc=example,dc=com
lldap.admin_bind_dn=cn=admin,ou=people,dc=example,dc=com

Step 4 - Confirm the services and ports

LLDAP and nginx run under systemd. The web admin UI is bound to loopback only; nginx serves it over TLS on 443, and LDAP listens on 3890:

systemctl is-active lldap.service nginx
/opt/lldap/lldap --version
sudo ss -tlnp | grep -E ':(443|3890|17170) '

Real output from a running instance:

active active
lldap 0.6.3
LISTEN 0.0.0.0:443
LISTEN 127.0.0.1:17170
LISTEN 0.0.0.0:3890

Note that 17170 is bound to 127.0.0.1 only - the raw web port is not reachable off-box. Only nginx on 443 fronts it.

Step 5 - Sign in to the web admin UI

Open the web admin UI in your browser at https://<public-ip>/. The TLS certificate is self-signed and unique to this instance, so your browser will show a warning the first time - accept it to proceed (or install your own certificate; see Step 9). Sign in as admin with the password from Step 3.

The LLDAP web admin login page served over TLS

After signing in you land on the Users list, which shows every account in the directory:

The LLDAP users list

Step 6 - Create users and groups

Click Create a user to add an account, giving it a user ID, email and display name. Open any user to edit their details or set a password:

Editing a user's details in the LLDAP web admin UI

Switch to the Groups tab to model teams. Click Create a group, then open a group to add members. Downstream applications can map group membership to roles and permissions:

The LLDAP groups view

Step 7 - Confirm authentication from the command line

The ldap-utils client tools ship in the image. Bind as the admin over LDAP and search the directory. Anonymous binds are refused, so downstream services must authenticate:

PW=$(sudo awk -F= '/^LLDAP_ADMIN_PASSWORD=/{print $2}' /root/lldap-credentials.txt)
ldapsearch -x -LLL -H ldap://127.0.0.1:3890 \
  -D 'cn=admin,ou=people,dc=example,dc=com' -w "$PW" \
  -b 'ou=people,dc=example,dc=com' '(objectclass=person)' cn uid mail

Real output (with two sample users created in the UI):

dn: uid=admin,ou=people,dc=example,dc=com
cn: Administrator
mail: admin@example.com
uid: admin

dn: uid=jsmith,ou=people,dc=example,dc=com
cn: Jordan Smith
mail: jsmith@example.com
uid: jsmith

An anonymous bind is refused - the command below is expected to fail, proving a network probe cannot read the directory without credentials:

ldapsearch -x -H ldap://127.0.0.1:3890 -b 'dc=example,dc=com' \
  || echo "Anonymous bind correctly refused."
ldap_bind: Inappropriate authentication (48)
    additional info: Anonymous bind not allowed
Anonymous bind correctly refused.

Step 8 - Connect a downstream application over LDAP

Point any LDAP-aware application (Nextcloud, Gitea, Grafana, Jellyfin and others) at this directory using these settings:

  • LDAP host / port: <public-ip> / 3890
  • Bind DN: cn=admin,ou=people,dc=example,dc=com (or a dedicated read-only service account you create in the UI)
  • Base DN: dc=example,dc=com
  • User search base: ou=people,dc=example,dc=com
  • Group search base: ou=groups,dc=example,dc=com

For production, create a dedicated service account in the web admin UI rather than binding as admin, and restrict port 3890 in the security group to the application's security group or your VPC CIDR.

Step 9 - Use your own domain and TLS certificate (optional)

The image ships with a self-signed certificate generated per instance at /etc/lldap/tls/cert.pem and /etc/lldap/tls/key.pem, referenced by the nginx site at /etc/nginx/sites-available/lldap.conf. To use a certificate for your own domain, replace those two files (for example with a certificate issued by AWS Certificate Manager terminated at a load balancer, or one obtained with certbot) and reload nginx:

sudo nginx -t && sudo systemctl reload nginx

Step 10 - Confirm the directory database is on the dedicated data volume

The SQLite directory database lives on its own EBS volume, separate from the OS disk and independently resizable:

findmnt /var/lib/lldap
ls -la /var/lib/lldap/users.db

You can snapshot this volume with EBS snapshots or AWS Backup to protect your directory.

Support

cloudimg provides 24/7 technical support for this product by email and live chat at support@cloudimg.co.uk. We can help with deployment, connecting downstream applications over LDAP, TLS termination with your own domain and certificate, user and group modelling, and backup planning for your directory database. For refunds, contact support@cloudimg.co.uk with your AWS account ID and subscription details.