Security AWS

Apache Directory Server on AWS User Guide

| Product: Apache Directory Server on AWS

Overview

This image runs Apache Directory Server (ApacheDS) 2.0, the Apache Software Foundation's extensible, embeddable directory server. It is certified LDAP v3 compatible and also speaks Kerberos, which makes it a practical foundation for centralised identity, authentication and application directory data.

ApacheDS runs as a Java service on OpenJDK 17, under a dedicated unprivileged apacheds system account, managed by a systemd unit that starts it on boot. The directory instance - its DIT partitions, configuration, working directory and logs - lives on a dedicated EBS volume mounted at /var/lib/apacheds, separate from the operating system disk and independently resizable.

The LDAP listener on port 10389 and the LDAPS listener on port 10636 are bound to the loopback interface only. They are not reachable from the network, and the security group does not open them. This is a deliberate default: an unauthenticated directory exposed to the internet is a serious exposure. You reach the directory over an SSH tunnel, or you rebind it to a private interface behind your own security group and TLS termination when you are ready. Both are covered below.

A demonstration partition dc=cloudimg,dc=local is seeded with organizational units, two sample user entries and a group, so directory queries return real data the moment the instance is up.

ApacheDS bootstraps with a well-known upstream administrator password. This image does not leave it that way: a one-shot first-boot service generates a unique administrator password of at least 24 characters on every instance, applies it over LDAP, verifies it, and writes it to /root/apacheds-credentials.txt with mode 0600. The upstream default is rejected afterwards - this guide proves both directions below.

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
  • An SSH client on your workstation; an LDAP browser is useful but not required

You do not need to open ports 10389 or 10636 in the security group. The directory is reached through the SSH tunnel described later in this guide.

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, since ApacheDS is a JVM service and benefits from additional memory
  6. Select your VPC, subnet and key pair
  7. Attach a security group that allows inbound TCP 22 from your management network. Do not add rules for 10389 or 10636
  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=apacheds}]'

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>

First boot takes a few moments to generate and apply the administrator password. If the credentials file still shows the placeholder text, wait a few seconds and read it again.

Verifying the directory is running

Confirm the service is active and that both listeners are bound to loopback only:

systemctl is-active apacheds.service
systemctl status apacheds.service --no-pager | head -4

Real output from a deployed instance:

active
● apacheds.service - Apache Directory Server (ApacheDS 2.0.0.AM27)
     Loaded: loaded (/etc/systemd/system/apacheds.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-07-21 19:18:37 UTC; 31s ago
   Main PID: 2547 (java)

Check the listening sockets. Both appear as the IPv4-mapped form [::ffff:127.0.0.1], which is the loopback address:

ss -tln | grep -E '10389|10636'
LISTEN 0      50     [::ffff:127.0.0.1]:10636            *:*
LISTEN 0      50     [::ffff:127.0.0.1]:10389            *:*

Confirm the Java runtime:

java -version 2>&1 | head -1
openjdk version "17.0.19" 2026-04-21

Retrieving the administrator credentials

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

sudo cat /root/apacheds-credentials.txt

The file contains the generated password along with the bind DN, the demonstration base DN, the LDAP and LDAPS ports, and a reminder that the listeners are loopback only:

# Apache Directory Server (ApacheDS) - generated on first boot
# This admin password is unique to this VM. Store it somewhere safe.

APACHEDS_ADMIN_PASSWORD=<generated-on-first-boot>
APACHEDS_BIND_DN=uid=admin,ou=system
APACHEDS_DEMO_BASE_DN=dc=cloudimg,dc=local
APACHEDS_LDAP_HOST=127.0.0.1
APACHEDS_LDAP_PORT=10389
APACHEDS_LDAPS_PORT=10636
APACHEDS_PUBLIC_IP=<this-instance-public-ip>

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 '^APACHEDS_ADMIN_PASSWORD=' /root/apacheds-credentials.txt | cut -d= -f2-)
if [ -n "$PW" ]; then echo "administrator password loaded (${#PW} characters)"; else echo "not yet generated"; fi
administrator password loaded (32 characters)

Confirm the file is readable only by root:

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

Querying the directory

Anonymous rootDSE

The rootDSE is readable anonymously and is the quickest way to confirm the directory is answering and to see which naming contexts it serves:

ldapsearch -x -LLL -H ldap://127.0.0.1:10389 -s base -b '' '(objectclass=*)' namingContexts supportedLDAPVersion vendorName
dn:
vendorName: Apache Software Foundation
supportedLDAPVersion: 3
namingContexts: ou=system
namingContexts: dc=cloudimg,dc=local
namingContexts: ou=schema
namingContexts: ou=config

dc=cloudimg,dc=local is the seeded demonstration partition. ou=system, ou=schema and ou=config are the server's own partitions.

Authenticated search of the demonstration partition

Bind as the administrator and search the seeded partition. The password comes from $PW, so it is never echoed:

PW=$(grep '^APACHEDS_ADMIN_PASSWORD=' /root/apacheds-credentials.txt | cut -d= -f2-)
ldapsearch -x -LLL -H ldap://127.0.0.1:10389 -D 'uid=admin,ou=system' -w "$PW" -b 'dc=cloudimg,dc=local' '(objectclass=inetOrgPerson)' cn mail uid
dn: uid=alice,ou=people,dc=cloudimg,dc=local
mail: alice@cloudimg.local
cn: Alice Anderson
uid: alice

dn: uid=bob,ou=people,dc=cloudimg,dc=local
mail: bob@cloudimg.local
cn: Bob Brown
uid: bob

The seed also creates ou=people, ou=groups and a groupOfNames entry cn=engineers whose members are the two users above.

Confirming the administrator password is unique to your instance

ApacheDS ships a well-known upstream administrator password. On this image it is rotated on first boot, so it must no longer authenticate. This check proves both directions - the generated password is accepted, the upstream default is rejected - without printing either:

PW=$(grep '^APACHEDS_ADMIN_PASSWORD=' /root/apacheds-credentials.txt | cut -d= -f2-)
GOOD=$(ldapsearch -x -LLL -H ldap://127.0.0.1:10389 -D 'uid=admin,ou=system' -w "$PW" -s base -b '' '(objectclass=*)' 2>&1 || true)
STOCK=$(ldapsearch -x -LLL -H ldap://127.0.0.1:10389 -D 'uid=admin,ou=system' -w secret -s base -b '' '(objectclass=*)' 2>&1 || true)
echo "$GOOD" | grep -qi 'supportedLDAPVersion\|objectClass\|dn:' && echo "generated password: ACCEPTED" || echo "generated password: REJECTED"
echo "$STOCK" | grep -qi 'Invalid credentials\|result: 49' && echo "upstream default:   REJECTED" || echo "upstream default:   ACCEPTED"
generated password: ACCEPTED
upstream default:   REJECTED

If the second line ever reads ACCEPTED, the first boot rotation did not complete. Check systemctl status apache-directory-ds-firstboot.service and contact cloudimg support.

Checking the directory data volume

The instance lives on its own EBS volume, so directory growth never competes with the root filesystem:

df -h /var/lib/apacheds | tail -1
findmnt -no SOURCE,TARGET,FSTYPE /var/lib/apacheds
/dev/nvme1n1     30G  6.3M   28G   1% /var/lib/apacheds
/dev/nvme1n1 /var/lib/apacheds ext4

The mount is recorded in /etc/fstab by filesystem UUID, so it survives reboots and re-attaches correctly on any instance launched from this image. To grow it, expand the EBS volume in the AWS console and then extend the filesystem:

sudo resize2fs /dev/nvme1n1

Reaching the directory from your workstation

Because the listeners are loopback only, use an SSH tunnel to reach the directory from an LDAP browser such as Apache Directory Studio, or from ldapsearch on your own machine. Run this on your workstation, not on the instance:

ssh -i /path/to/your-key.pem -L 10389:127.0.0.1:10389 ubuntu@<public-ip>

Leave that session open. In another terminal on your workstation, the directory is now reachable at ldap://127.0.0.1:10389:

ldapsearch -x -LLL -H ldap://127.0.0.1:10389 \
  -D 'uid=admin,ou=system' -W \
  -b 'dc=cloudimg,dc=local' '(objectclass=*)'

In Apache Directory Studio, create a connection to host 127.0.0.1, port 10389, with bind DN uid=admin,ou=system and the password from the credentials file.

For LDAPS, tunnel port 10636 the same way.

Adding your own directory data

The demonstration partition is there to prove the directory works. For real use, import your own entries or replace the suffix entirely.

Write an LDIF file describing the entries you want, for example /tmp/my-entries.ldif:

dn: ou=staff,dc=cloudimg,dc=local
objectClass: organizationalUnit
objectClass: top
ou: staff

dn: uid=jsmith,ou=staff,dc=cloudimg,dc=local
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
cn: Jane Smith
sn: Smith
uid: jsmith
mail: jsmith@example.com

Then import it. This modifies the directory, so it is shown here rather than run as part of this guide:

PW=$(sudo grep '^APACHEDS_ADMIN_PASSWORD=' /root/apacheds-credentials.txt | cut -d= -f2-)
ldapadd -x -H ldap://127.0.0.1:10389 -D 'uid=admin,ou=system' -w "$PW" -f /tmp/my-entries.ldif

To modify an existing entry, write a changetype LDIF and apply it with ldapmodify:

dn: uid=jsmith,ou=staff,dc=cloudimg,dc=local
changetype: modify
replace: mail
mail: jane.smith@example.com
ldapmodify -x -H ldap://127.0.0.1:10389 -D 'uid=admin,ou=system' -w "$PW" -f /tmp/change.ldif

To remove an entry:

ldapdelete -x -H ldap://127.0.0.1:10389 -D 'uid=admin,ou=system' -w "$PW" 'uid=jsmith,ou=staff,dc=cloudimg,dc=local'

To use your own suffix instead of dc=cloudimg,dc=local, add a new partition through the ou=config partition using Apache Directory Studio's configuration editor, or ask cloudimg support to help you plan the partition and index layout.

Changing the administrator password

The password generated at first boot is unique to your instance. If you want to set your own, apply a modify against uid=admin,ou=system and then update the credentials file so the value on disk stays accurate:

cat > /tmp/newpw.ldif <<'LDIF'
dn: uid=admin,ou=system
changetype: modify
replace: userPassword
userPassword: <your-new-password>
LDIF

PW=$(sudo grep '^APACHEDS_ADMIN_PASSWORD=' /root/apacheds-credentials.txt | cut -d= -f2-)
ldapmodify -x -H ldap://127.0.0.1:10389 -D 'uid=admin,ou=system' -w "$PW" -f /tmp/newpw.ldif
shred -u /tmp/newpw.ldif

Then edit /root/apacheds-credentials.txt and replace the APACHEDS_ADMIN_PASSWORD= value.

Exposing the directory to your private network

Keeping the listeners on loopback is the safest default. If your applications need to reach the directory over the network, bind it to the instance's private address rather than to all interfaces, restrict access with a security group, and prefer LDAPS.

The bind addresses live in the server's own configuration partition. Edit them with Apache Directory Studio connected over the SSH tunnel: open the ou=config partition, find the LDAP and LDAPS transports, and change ads-transportaddress from 127.0.0.1 to the instance's private IP address. Then restart the service:

sudo systemctl restart apacheds.service

After that, add an inbound rule to the security group allowing TCP 10636 from the private CIDR your application servers sit in - and only that CIDR. Do not expose either port to 0.0.0.0/0, and do not expose plain LDAP on 10389 beyond loopback; use LDAPS on 10636 with a certificate your clients trust.

Backup and maintenance

The directory data lives entirely under /var/lib/apacheds. The two supported approaches are an EBS snapshot of the data volume, and an LDIF export.

For an LDIF export of the demonstration partition, run this on the instance and copy the file off:

PW=$(sudo grep '^APACHEDS_ADMIN_PASSWORD=' /root/apacheds-credentials.txt | cut -d= -f2-)
ldapsearch -x -LLL -H ldap://127.0.0.1:10389 -D 'uid=admin,ou=system' -w "$PW" \
  -b 'dc=cloudimg,dc=local' '(objectclass=*)' '*' > /tmp/apacheds-export.ldif

For a consistent volume-level backup, stop the service, take an EBS snapshot of the data volume in the AWS console or CLI, then start it again:

sudo systemctl stop apacheds.service
# take the EBS snapshot of the volume mounted at /var/lib/apacheds
sudo systemctl start apacheds.service

Service logs are available through the journal:

journalctl -u apacheds.service --no-pager -n 5

Operating system security updates are applied by unattended upgrades, which is enabled on the image. Restart the service after an OpenJDK update so the new runtime is picked up:

sudo systemctl restart apacheds.service

Troubleshooting

The service is not active. Check the unit status and the last few journal lines with the commands above. The most common cause is the data volume not being mounted; the unit declares RequiresMountsFor=/var/lib/apacheds and will not start without it. Confirm with findmnt /var/lib/apacheds.

The credentials file still shows placeholder text. First boot has not finished. Check systemctl status apache-directory-ds-firstboot.service. The service waits for the directory to accept LDAP before it rotates the password.

A bind returns Invalid credentials (49). Confirm you are using the value from /root/apacheds-credentials.txt on that specific instance. Every instance generates its own password; a password from another instance will not work.

ldapsearch: command not found on your workstation. Install your platform's OpenLDAP client utilities, or use Apache Directory Studio over the tunnel instead.

Nothing answers on 127.0.0.1:10389 through the tunnel. Confirm the SSH session carrying the -L forward is still open, and that the service is active on the instance.

Support

Every cloudimg deployment is backed by 24/7 support from cloudimg engineers by email and live chat, with a one-hour average response for critical issues. Support covers deployment and instance sizing, directory schema and partition design, LDIF import, rebinding the listeners to a private interface, LDAPS and TLS certificate configuration, access control, backup and restore of the directory volume, and ApacheDS upgrades and JVM tuning.

Apache, Apache Directory, Apache Directory Server and ApacheDS are trademarks of The Apache Software Foundation. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.