Streaming & Messaging AWS

Apache James 3.9.1 on AWS User Guide

| Product: Apache James 3.9.1 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and running the Apache James 3.9.1 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg Mail Servers and Newsletters listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Apache James (Java Apache Mail Enterprise Server) is the open source mail server of the Apache Software Foundation: an SMTP transfer agent for sending and receiving, IMAP mailbox access, Sieve filtering, and a programmable mailet container for routing, rewriting and filtering mail. This image ships the Apache 2.0 licensed 3.9.1 release in its single node distribution, so the mailbox store (JPA over embedded Apache Derby), the full text search index (embedded Apache Lucene) and the mail queue (embedded Apache ActiveMQ) all run inside one JVM. There is no database server, search cluster or message broker to operate.

The cloudimg image adds:

  • No credential ships in the image. First boot generates this instance's TLS keystore, its postmaster mailbox password, its datastore credentials and its admin API password, and writes them to /root/apache-james-credentials.txt (root only). None of the upstream sample passwords opens anything, and James does not start until first boot has finished.
  • Authentication only over TLS. Submission on 587 (STARTTLS) and 465 (implicit TLS) requires SMTP AUTH, and the image adds a small hook so an AUTH sent before STARTTLS is refused even with the right password (upstream only hides the AUTH offer). IMAP on 143 refuses LOGIN and AUTHENTICATE until STARTTLS; 993 is implicit TLS. Inbound mail on 25 is offered STARTTLS.
  • Never an open relay. The upstream setting that lets anything on the machine relay without authentication is removed. The build proves it with a trap listener: an unauthenticated relay attempt is refused and nothing arrives, while an authenticated one does.
  • A locked admin surface. The webadmin REST API listens on 127.0.0.1:8000 only and requires this instance's password. JMX is switched off, because James cannot keep its JMX RMI port on loopback. The sudo james-admin helper covers everyday tasks and never takes a password on its command line.
  • Walled off from instance metadata. The James JVM cannot reach the EC2 instance metadata service, and the AMI requires IMDSv2.
  • Verified and current. James 3.9.1 is checked against Apache's SHA-512 and release signature at build time. It replaces 3.9.0 for its ActiveMQ, Netty, BouncyCastle, Jackson, jsoup and Logback security fixes (see Release notes and known advisories). Ubuntu 24.04 LTS is fully patched at build time with unattended security upgrades on, and OpenJDK 21 comes from the Ubuntu archive.

Apache James and Apache are trademarks of The Apache Software Foundation. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by The Apache Software Foundation.

Before you start: EC2 limits outbound port 25

Amazon EC2 restricts outbound traffic on TCP port 25 from instances by default. The build of this image measured it: port 25 to a public mail exchanger was blocked while port 587 was reachable. You can ask AWS to lift the restriction for your account with the request to remove email sending limitations form.

Works with the default EC2 restriction
Receiving mail on port 25 from other mail servers Yes, inbound is unaffected
IMAP mailbox access on 143 and 993 Yes
Authenticated submission from your mail clients on 587 and 465 Yes
Sending directly to recipient mail servers on port 25 Only after AWS lifts the limit
Sending through an authenticated smart host on port 587 Yes (see Sending mail out through a relay)

Launching the instance

  1. Subscribe to the listing on AWS Marketplace and choose Apache James 3.9.1 on Ubuntu 24.04 as the software version.
  2. Use m5.large (2 vCPU, 8 GiB) or larger. The JVM heap is capped at 1 GiB, which leaves room for the operating system and page cache with no swap.
  3. Choose your key pair. The AMI requires IMDSv2.
  4. Use a security group that opens only what you intend to serve:
Port Protocol Purpose Open to
22 TCP SSH administration Your management address only
25 TCP Inbound SMTP from other mail servers The internet, if you receive external mail
587 TCP Authenticated submission, STARTTLS Your users' networks
465 TCP Authenticated submission, implicit TLS Your users' networks
143 TCP IMAP, STARTTLS Your users' networks
993 TCP IMAP, implicit TLS Your users' networks

Never open port 8000. The webadmin API is bound to loopback and reached over an SSH tunnel. POP3, LMTP, ManageSieve and JMX are switched off.

First boot runs apache-james-firstboot.service: it generates this instance's secrets and TLS keystore, creates the default mail domain and the postmaster mailbox, and starts James. It usually finishes in under a minute.

Connecting to your instance

Operating system SSH user
Ubuntu 24.04 ubuntu
ssh -i /path/to/your-key.pem ubuntu@<public-ip>

Confirming the server is running

systemctl is-active apache-james.service
systemctl show -p Result --value apache-james-firstboot.service

apache-james.service reports active and the first boot unit reports success.

Check the exact release and the listening ports:

unzip -p /opt/james/james-server-jpa-app.jar META-INF/MANIFEST.MF | tr -d '\r' | grep -E '^(Implementation-Version|Main-Class):'
sudo ss -lntp | awk 'NR>1 && /java/ {print $4}' | sort -u

You see Implementation-Version: 3.9.1 and Main-Class: org.apache.james.JPAJamesServerMain. Ports 25, 143, 465, 587 and 993 listen on all addresses. Only two java listeners are on loopback: 127.0.0.1:8000 (webadmin) and one ephemeral port, the embedded ActiveMQ broker's internal transport.

Ask James whether it is healthy (the helper sends the admin password for you):

sudo james-admin health

The answer is a JSON document with "status": "healthy".

Your per instance credentials

sudo stat -c '%A %U:%G %n' /root/apache-james-credentials.txt
sudo grep -E '^JAMES_(MAIL_DOMAIN|POSTMASTER_ADDRESS|WEBADMIN_URL|SUBMISSION_ENDPOINT|IMAPS_ENDPOINT)=' /root/apache-james-credentials.txt

The file is -rw------- root:root and records:

Key What it is
JAMES_MAIL_DOMAIN The default mail domain created for this instance (its EC2 host name)
JAMES_POSTMASTER_ADDRESS The only mailbox that exists, postmaster@ that domain
JAMES_POSTMASTER_PASSWORD That mailbox's password, generated on this instance
JAMES_WEBADMIN_PASSWORD The webadmin API password, sent as the Password header
JAMES_KEYSTORE_SECRET The password protecting this instance's TLS keystore
JAMES_DB_USER / JAMES_DB_PASSWORD The embedded datastore credentials
JAMES_SUBMISSION_ENDPOINT / JAMES_IMAPS_ENDPOINT Where to point a mail client

To read the postmaster password when you need it:

sudo grep -E '^JAMES_POSTMASTER_PASSWORD=' /root/apache-james-credentials.txt | cut -d= -f2-

Two instances launched from this AMI never share any of these values.

Proving mail actually flows

The image ships a self test that does a real round trip and checks that the things which must not work really do not:

sudo /usr/local/sbin/james-selftest.sh

It checks the authenticated health endpoint and that webadmin refuses a missing or wrong password with 401. It then sends AUTH with the right password on 587 before STARTTLS (refused with 530) and after it (235), submits a message carrying a random nonce, and reads it back over IMAPS 993. It confirms IMAP 143 refuses LOGIN before STARTTLS and accepts it after, refuses an unauthenticated relay to an external domain, refuses wrong passwords on 587 and 993, and still accepts inbound mail on 25 to a local mailbox. It removes its probe message and ends with:

selftest: open-relay attempt refused (550 5.7.1 Requested action not taken: relaying denied)
...
SELFTEST_OK: webadmin auth, AUTH and IMAP login only after TLS, real delivery and retrieval, relay refusal, credential refusals and inbound reception all verified

Authentication only after TLS

Before STARTTLS, port 587 does not offer AUTH:

{ sleep 1; printf 'EHLO check.example\r\n'; sleep 1; printf 'QUIT\r\n'; } | nc -w 5 127.0.0.1 587 | tr -d '\r'
220 Apache James SMTP Server ready
250-ip-172-31-5-9 Hello check.example [127.0.0.1])
250-PIPELINING
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-SIZE 52428800
250 STARTTLS
221 2.0.0 ip-172-31-5-9 Service closing transmission channel

There is STARTTLS and no AUTH line. After STARTTLS the same port offers AUTH LOGIN PLAIN:

printf 'EHLO check.example\r\nQUIT\r\n' | timeout 10 openssl s_client -quiet -starttls smtp -connect 127.0.0.1:587 2>/dev/null | tr -d '\r' | grep -E '^250'

Upstream James only hides the AUTH offer before TLS; a client that sent AUTH anyway was accepted. This image adds RequireTlsForAuthHook to the two submission listeners, so such an attempt gets 530 5.7.0 Must issue a STARTTLS command first. Its source is in the image:

grep -c RequireTlsForAuthHook /var/lib/james/conf/smtpserver.xml
ls /usr/share/doc/cloudimg/RequireTlsForAuthHook.java /opt/james/extensions-jars/

This server is not an open relay

Apache James's sample configuration sets <authorizedAddresses>127.0.0.0/8</authorizedAddresses>, which lets any process on the machine relay to any destination without authentication. This image removes it, so SMTP AUTH is required before anything is relayed anywhere, loopback included. Receiving is unaffected: mail from other servers to your own mailboxes is accepted.

sudo grep -c authorizedAddresses /var/lib/james/conf/smtpserver.xml || echo "0: relaying requires authentication"

An authenticated user also cannot send with someone else's address as the sender: James answers 503 5.7.1 Incorrect Authentication for Specified Email Address.

Administering the server

James has no web interface. Administration is the webadmin REST API on 127.0.0.1:8000, which requires this instance's password in a Password header. Without it the API answers 401:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/domains

The bundled james-admin helper reads the password from the credentials file and sends it for you. Run it with sudo:

sudo james-admin
sudo james-admin domains
sudo james-admin users

Adding your own domain and mailboxes

Add the domain you receive mail for, then create mailboxes in it. add-user generates a strong password and prints it once. Passwords are never taken on the command line (sudo logs command lines):

sudo james-admin add-domain example.net
sudo james-admin add-user alice@example.net
sudo james-admin users
sudo james-admin remove-user alice@example.net
sudo james-admin remove-domain example.net
domain example.net added
mailbox alice@example.net created
password (shown once, store it now): <generated-password>

To set a password of your own, pipe it on standard input (or run the command in a terminal and type it at the prompt):

sudo james-admin set-password alice@example.net

Address length limit. This distribution stores domains and mailbox addresses in columns of at most 100 characters. james-admin checks the length and says so plainly.

Reaching the API from your workstation

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

Then send the password as a header from a file rather than on the command line, for example curl -H @webadmin.hdr http://127.0.0.1:8000/domains, where webadmin.hdr contains Password: <the JAMES_WEBADMIN_PASSWORD value>. The webadmin API reference lists every route.

Why JMX is off

James's JMX registry can be bound to loopback, but it exports its RMI server object on a second, random port on every interface, and the JDK's own agent does the same. JMX also carries James's JMX advisories (CVE-2023-26269, CVE-2023-51518), so this image disables it (jmx.enabled=false in /var/lib/james/conf/jmx.properties). As a result james-cli does not connect; use james-admin or the webadmin API instead.

grep -E '^jmx\.enabled' /var/lib/james/conf/jmx.properties

Connecting a mail client

Setting Value
Incoming (IMAP) Your instance's address, port 993, SSL/TLS
Incoming (IMAP, alternative) Your instance's address, port 143, STARTTLS
Outgoing (SMTP submission) Your instance's address, port 587, STARTTLS
Outgoing (alternative) Your instance's address, port 465, SSL/TLS
Username The full mailbox address, for example alice@example.net
Password The mailbox password

The image generates a self signed certificate for the instance's own name, so clients warn on first connection until you install a real one.

Installing a real TLS certificate

Build a PKCS12 keystore with the alias james from the certificate and key issued for the name your users connect to. Keep the new keystore password in a root-only file rather than on the command line:

sudo install -m 0600 /dev/null /root/keystore.pass
sudo sh -c 'openssl rand -hex 24 > /root/keystore.pass'
sudo openssl pkcs12 -export -in /path/to/fullchain.pem -inkey /path/to/privkey.pem \
  -name james -out /var/lib/james/conf/keystore.new -passout file:/root/keystore.pass

Put that password into the <secret> elements of /var/lib/james/conf/smtpserver.xml and imapserver.xml with an editor, then swap the keystore in and restart:

sudo mv /var/lib/james/conf/keystore.new /var/lib/james/conf/keystore
sudo chown james:james /var/lib/james/conf/keystore && sudo chmod 0640 /var/lib/james/conf/keystore
sudo systemctl restart apache-james.service

Sending mail out through a relay

Because EC2 limits outbound port 25, send through an authenticated smart host on 587 (Amazon SES or your mail provider). Add the gateway settings to the RemoteDelivery mailet in /var/lib/james/conf/mailetcontainer.xml:

<mailet match="All" class="RemoteDelivery">
    <outgoingQueue>outgoing</outgoingQueue>
    <delayTime>5000, 100000, 500000</delayTime>
    <maxRetries>3</maxRetries>
    <maxDnsProblemRetries>0</maxDnsProblemRetries>
    <deliveryThreads>10</deliveryThreads>
    <sendpartial>true</sendpartial>
    <bounceProcessor>bounces</bounceProcessor>
    <gateway>smtp.your-relay-provider.net</gateway>
    <gatewayPort>587</gatewayPort>
    <gatewayUsername>your-relay-username</gatewayUsername>
    <gatewayPassword>your-relay-password</gatewayPassword>
    <startTLS>true</startTLS>
</mailet>

Then restart and check:

sudo systemctl restart apache-james.service
sudo james-admin health

Publish the SPF, DKIM and DMARC records your relay provider gives you; without them most large mailbox providers reject or quarantine your mail.

DNS records for receiving mail

For each domain you add, publish an MX record pointing at a host name whose A record is the instance's public address. Use an Elastic IP so the address survives a stop and start, and set its reverse DNS (PTR) record to that host name in the EC2 console (Elastic IPs, Update reverse DNS). Many receiving servers check it.

Where everything lives

ls /opt/james
sudo ls /var/lib/james /var/lib/james/var
Path Contents
/opt/james The Apache James distribution and conf.template, the inert placeholder configuration. Owned by root, read only to the service.
/opt/james/extensions-jars The cloudimg AUTH-after-TLS hook
/var/lib/james/conf The live configuration
/var/lib/james/var/store The embedded Derby mailbox database and the ActiveMQ queue store
/var/lib/james/var/mail Mail repositories (errors, relay denied, address errors)
/var/lib/james/var/log james.log and its archives
/var/lib/james The Lucene search index
/root/apache-james-credentials.txt This instance's generated credentials, mode 0600

Logs and troubleshooting

sudo tail -20 /var/lib/james/var/log/james.log
sudo journalctl -u apache-james.service -n 30 --no-pager

If apache-james.service shows a failed start condition, first boot has not completed:

sudo systemctl status apache-james-firstboot.service --no-pager | head -12
sudo tail -20 /var/log/cloudimg-firstboot.log

Backups

Everything stateful is under /var/lib/james. For a consistent copy, stop James, archive the directory and start it again:

sudo systemctl stop apache-james.service
sudo tar -czf /root/james-backup-$(date +%F).tar.gz -C /var/lib james
sudo systemctl start apache-james.service

EBS snapshots of the root volume (or AWS Backup) are the simplest whole-instance option.

Updating

Ubuntu security updates, including the OpenJDK runtime, are applied by unattended-upgrades:

systemctl is-enabled unattended-upgrades.service
grep 'Unattended-Upgrade' /etc/apt/apt.conf.d/20auto-upgrades

For Apache James itself, cloudimg publishes a new version of the listing for each upstream release. Launch the new version alongside, move mail across with your usual IMAP tooling, then retire the old instance.

Release notes and known advisories

3.9.1 replaces 3.9.0. The 3.9.1 release upgrades the libraries James ships and fixes these advisories: ActiveMQ 6.1.6 to 6.2.7 (CVE-2026-34197, CVE-2026-42588, CVE-2026-45505, CVE-2026-41044, CVE-2026-40466, CVE-2026-39304, CVE-2026-33227, CVE-2026-49270, CVE-2026-41043), BouncyCastle 1.81 to 1.85 (CVE-2026-8763, CVE-2025-14813, CVE-2026-13506, CVE-2026-0636, CVE-2026-5588), Netty 4.1.126 to 4.1.135 (CVE-2025-67735, CVE-2026-44249, CVE-2026-50010, CVE-2026-45416, CVE-2026-42583 and others), jsoup 1.20.1 to 1.23.2 (CVE-2026-75140, CVE-2026-71497), Logback 1.5.18 to 1.5.38 (CVE-2025-11226, CVE-2026-10532, CVE-2026-9828, CVE-2026-1225), Jackson 2.19.1 to 2.22 (CVE-2026-18401, CVE-2026-68494, CVE-2026-59888, CVE-2026-59889), commons-configuration2 2.15.0 (CVE-2026-45205) and Jetty 12.1.10. It also turns webadmin password authentication on by default.

Advisories still present in the 3.9.1 libraries. A vulnerability scan of this AMI may list the following. None is reachable in this configuration:

Advisory Library Why it is not reachable here
CVE-2026-75595, CVE-2026-75596 Netty 4.1.135 handler (SNI) James does not use Netty's SNI handler
CVE-2026-59901 Netty codec (Bzip2 decoder) Not used by the mail protocols
CVE-2026-73508 Netty DNS codec James resolves DNS with dnsjava
CVE-2026-55851, CVE-2026-59919 Netty HAProxy decoder Used only when proxyRequired is enabled; it is off
CVE-2026-59898, CVE-2026-56746, CVE-2026-55831, CVE-2026-59903, CVE-2026-59921, CVE-2026-56745, CVE-2026-55833, CVE-2026-59899, CVE-2026-56819, CVE-2026-59900 Netty HTTP, HTTP/2, SPDY, CORS and WebSocket codecs James serves no HTTP over Netty; webadmin runs on Jetty on 127.0.0.1
CVE-2026-89425, CVE-2026-89407, CVE-2026-91777, CVE-2026-91776 Jackson 2.22 JSON is parsed only by the loopback, password protected webadmin API

These clear when Apache James ships the fixed libraries; cloudimg then publishes a new version.

Licensing

Apache James is free and open source software under the Apache License 2.0, shipped unmodified apart from configuration. The cloudimg AUTH-after-TLS hook is a separate, small extension whose source is in /usr/share/doc/cloudimg/. The bundled libraries (Apache Derby, Lucene and ActiveMQ, Netty, Jetty, Guice, Logback, BouncyCastle) are permissively licensed, and the Java runtime is Ubuntu's openjdk-21-jre-headless package.

head -3 /opt/james/LICENSE

Support

cloudimg supports this image through the AWS Marketplace listing and at cloudimg.co.uk. The other products in this listing are on the Mail Servers and Newsletters hub.