Apache Directory Server on AWS User Guide
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
- Open the product page in AWS Marketplace and choose Continue to Subscribe
- Accept the terms, then choose Continue to Configuration
- Select the software version and the Region you want to deploy into, then choose Continue to Launch
- Choose Launch through EC2 for full control of networking and storage
- Pick an instance type.
m5.largeis the recommended minimum; scale up as your entry count and query concurrency grow, since ApacheDS is a JVM service and benefits from additional memory - Select your VPC, subnet and key pair
- Attach a security group that allows inbound TCP 22 from your management network. Do not add rules for 10389 or 10636
- 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.