Apache James on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and operation of Apache James on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Apache James (Java Apache Mail Enterprise Server) is a long established open source mail server from the Apache Software Foundation. It is a modular mail platform rather than a fixed product: a full SMTP transfer agent for sending and receiving, IMAP mailbox access with STARTTLS and implicit TLS, Sieve filtering, and a programmable mailet container that lets you route, rewrite, filter and act on mail with ordinary Java logic.
The cloudimg image ships the free and open source, Apache 2.0 licensed James 3.9.0, in its single node distribution. That is the important detail: 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 the one JVM. There is no database server to operate, no search cluster and no message broker. Nothing ships with a known credential: the image contains no mailbox, no TLS private key and none of the upstream default passwords, and a unique TLS keystore and a unique postmaster password are generated on each VM at first boot, before the server accepts a connection.
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. It ships the free and open source Apache 2.0 licensed software, unmodified.

What is included:
- Apache James 3.9.0, the Apache 2.0 licensed single node
jpa-guicedistribution, verified at build time by SHA-512 against Apache's published checksum - SMTP on
25(inbound), ESMTP submission on465(implicit TLS) and587(STARTTLS), IMAP4 on143(STARTTLS) and993(implicit TLS) - Embedded Apache Derby 10.17.1 mailbox store, Apache Lucene 10.2.1 full text search and Apache ActiveMQ 6.1.6 mail queue, all in one JVM
- Sieve filtering and the programmable mailet container for custom routing and processing
- A unique TLS keystore and a unique postmaster mailbox password generated per VM on first boot, never baked into the image
- Authentication required for every relay — the upstream sample configuration that relays for anything on the local machine is removed
- The
webadminREST administration API bound to loopback only, with thejames-adminhelper for everyday operations - OpenJDK 21 headless runtime from the Ubuntu archive
apache-james.serviceandapache-james-firstboot.serviceas systemd units, enabled on boot- Ubuntu 24.04 LTS base with the latest security patches applied at build time and unattended security upgrades enabled
- Azure Linux Agent for seamless cloud integration and SSH key injection
Before you start: Azure blocks outbound port 25
This is the single most important thing to know before you deploy any mail server on Azure, so it is stated first rather than buried.
Microsoft blocks outbound TCP port 25 from Azure virtual machines. Per Microsoft's current guidance:
- Enterprise Agreement (EA) and Microsoft Customer Agreement for enterprise (MCA-E) subscriptions are not blocked.
- Enterprise Dev/Test subscriptions are blocked by default but can be exempted through the virtual network's Diagnose and solve problems → Cannot send email (SMTP-Port 25) self service flow.
- All other subscription types — pay as you go, MSDN, CSP, Free Trial, Azure Pass, Azure in Open and Education — are blocked with no exemption available.
What this means in practice for this image:
| Works on every Azure subscription | |
|---|---|
| Receiving mail on port 25 from other mail servers | Yes — inbound is completely unaffected |
| IMAP mailbox access on 143 and 993 | Yes |
| Authenticated submission from your mail clients on 587 and 465 | Yes |
| Sending by connecting directly to recipient MX servers on port 25 | Only on EA / MCA-E |
| Sending through an authenticated smart host relay on port 587 | Yes — Microsoft's own recommendation |
So as shipped this is a fully functional receiving and IMAP mail server on any subscription, and for outbound delivery to the public internet you configure a relay. This is not a limitation of Apache James or of this image — it is an Azure platform policy that applies to every VM based mail server. Do not expect direct to MX delivery to work unless you are on EA or MCA-E.
The measurement below was taken on a real Azure VM running this image, not quoted from documentation:

Prerequisites
- An Azure subscription with permission to deploy virtual machines
- An SSH key pair for VM access
- A network security group allowing your mail ports inbound (see below) and TCP 22 from your management address
- Recommended size:
Standard_B2s(2 vCPU, 4 GiB). The JVM heap is deliberately capped at 1 GiB so the server fits this size with headroom and without swap, which Azure image certification does not permit. For heavy mailbox volume, move up toStandard_B2msor larger.
Network security group rules
Open only what you actually 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 | IMAP4, STARTTLS | Your users' networks |
| 993 | TCP | IMAP4, implicit TLS | Your users' networks |
Port 8000 (the webadmin administration API) must never be opened. It is bound to loopback and has no authentication; you reach it over an SSH tunnel, which makes your SSH key the front door.
Deploying the image
Launch the image from the Azure Marketplace as you would any VM, choosing Standard_B2s or larger and supplying your SSH public key. The admin user is azureuser.
First boot runs apache-james-firstboot.service, which generates this VM's TLS keystore and postmaster password, creates the default mail domain and the postmaster mailbox, and starts the server. It typically completes in under a minute. Until it has finished, apache-james.service deliberately does not start — it is gated on a marker that first boot writes only after every per VM secret is on disk, so the server can never come up against the shipped placeholder configuration.
Connect over SSH:
ssh azureuser@<vm-ip>
Confirming the server is running
systemctl is-active apache-james.service
systemctl show -p ActiveState --value apache-james-firstboot.service
apache-james.service reports active. apache-james-firstboot.service reports inactive once it has completed — it is a one shot unit that disables itself after a successful run, which is exactly what you want to see.
Check the release actually running, and the ports it has published:
unzip -p /opt/james/james-server-jpa-app.jar META-INF/MANIFEST.MF | tr -d '\r' | grep -E '^(Implementation-Version|Main-Class):'
ss -lntH '( sport = :25 or sport = :143 or sport = :465 or sport = :587 or sport = :993 )' | awk '{print $4}' | sort -u
Implementation-Version: 3.9.0 and Main-Class: org.apache.james.JPAJamesServerMain confirm this is James 3.9.0 in its single node form, and all five mail ports are bound.
Ask the server itself whether it is healthy:
curl -fsS http://127.0.0.1:8000/healthcheck | head -c 300; echo
You get a JSON document beginning {"status":"healthy" with a checks array covering the Guice application lifecycle, mail reception and the event dead letter store.
Your per VM credentials
Every VM generates its own secrets at first boot. They are written to a file only root can read:
sudo stat -c '%A %U:%G %n' /root/apache-james-credentials.txt
sudo grep -E '^JAMES_(MAIL_DOMAIN|POSTMASTER_ADDRESS|WEBADMIN_URL)=' /root/apache-james-credentials.txt
The file is -rw------- root:root. It records:
| Key | What it is |
|---|---|
JAMES_MAIL_DOMAIN |
The default mail domain created for this VM |
JAMES_POSTMASTER_ADDRESS |
The one mailbox that exists, postmaster@ that domain |
JAMES_POSTMASTER_PASSWORD |
That mailbox's password, generated on this VM |
JAMES_KEYSTORE_SECRET |
The password protecting this VM's TLS keystore |
JAMES_DB_USER / JAMES_DB_PASSWORD |
The embedded datastore credentials, rotated per VM |
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-

These values exist only on this VM. Two VMs launched from this image mint completely different secrets, and none of the upstream Apache James sample passwords — including the widely published default TLS keystore password — will open anything here.
Proving mail actually flows
The image ships a self test that does a genuine end to end round trip and then checks that the things which must not work really do not:
sudo /usr/local/sbin/james-selftest.sh
It authenticates as the per VM postmaster, submits a message on port 587 with STARTTLS and SMTP AUTH carrying a random nonce, polls IMAP on 993 until the message is genuinely delivered, fetches it and asserts the exact nonce is in the body, then logs in again on 143 with STARTTLS. It then confirms an unauthenticated relay attempt is refused, a wrong submission password is refused, a wrong IMAP password is refused, and — as a positive control — that unauthenticated inbound mail to a local recipient is still accepted. It removes its own probe message, so it leaves nothing behind and is safe to run whenever you want reassurance.

The open relay line is the one to look for:
open-relay attempt REJECTED as required (RCPT TO refused 550 '5.7.1 Requested action not taken: relaying denied')
This server is not an open relay
An open relay — a mail server that will forward mail to any destination for anyone — is the single most serious defect a mail server can ship with. It gets your address space blacklisted within hours.
Apache James's own sample configuration sets <authorizedAddresses>127.0.0.0/8</authorizedAddresses>, which permits any process on the machine to relay to any destination with no authentication. That is a convenience default for a developer laptop and it is wrong for an internet facing server, so this image removes it entirely. The result is that SMTP AUTH is required before anything is relayed anywhere, from any source address including loopback.
Receiving mail is unaffected. James only rejects an unauthenticated sender when the MAIL FROM address is in one of your domains, which is anti spoofing, so ordinary inbound mail from other servers to your users is accepted normally. You can see both behaviours proven in the self test above.
You can confirm the setting is absent yourself:
sudo grep -c authorizedAddresses /var/lib/james/conf/smtpserver.xml || echo "0 - relay requires authentication"
Administering the server
Apache James's Guice server has no web interface. Administration is the webadmin REST API, which this image binds to 127.0.0.1:8000 only, because in this configuration that API has no authentication of its own. Your SSH key is the authenticated front door.
The image bundles james-admin, a small wrapper over that API so ordinary operations are one command:
james-admin
That prints the usage. The everyday commands:
james-admin health
james-admin domains
james-admin users
Adding your own domain and mailboxes
Add the domain you actually receive mail for, then create mailboxes in it:
sudo james-admin add-domain example.net
sudo james-admin add-user alice@example.net 'ChangeThisToAStrongPassword'
james-admin domains
james-admin users
add-user with no password argument generates a strong one and prints it.
Address length limit. This distribution stores domains and mailbox addresses in columns capped at 100 characters each. A mailbox address longer than that is rejected, and a domain longer than that is silently ignored. Ordinary addresses are nowhere near the limit, but it is worth knowing if you generate addresses programmatically.
james-adminchecks the length and tells you plainly rather than letting the API return an opaque error.
To change a password later, or to remove an account:
sudo james-admin set-password alice@example.net 'AnotherStrongPassword'
sudo james-admin remove-user alice@example.net
sudo james-admin remove-domain example.net
Reaching the API from your workstation
Open an SSH tunnel and the API is available locally, still without ever being exposed to the network:
ssh -L 8000:127.0.0.1:8000 azureuser@<vm-ip>
Then from your workstation, http://127.0.0.1:8000/domains, /users, /healthcheck and the rest of the webadmin API are reachable. Close the tunnel when you are done.
Connecting a mail client
Point any standards compliant mail client at the VM's public address:
| Setting | Value |
|---|---|
| Incoming (IMAP) | Your VM's address, port 993, SSL/TLS |
| Incoming (IMAP, alternative) | Your VM's address, port 143, STARTTLS |
| Outgoing (SMTP submission) | Your VM's address, port 587, STARTTLS |
| Outgoing (alternative) | Your VM's address, port 465, SSL/TLS |
| Username | The full mailbox address, for example alice@example.net |
| Password | The mailbox password you set |
| Authentication | Normal password |
The image generates a self signed certificate for the VM's own name, so your client will warn about the certificate on first connection until you install a real one — see the next section.
Installing a real TLS certificate
Replace the self signed keystore with one built from a certificate issued for the name your users actually connect to. Obtain the certificate however you normally do, then import the certificate and key into a PKCS12 keystore and point James at it.
Convert a PEM certificate and key into a PKCS12 keystore with the alias James expects:
sudo openssl pkcs12 -export \
-in /path/to/fullchain.pem \
-inkey /path/to/privkey.pem \
-name james \
-out /var/lib/james/conf/keystore.new \
-passout pass:YOUR_NEW_KEYSTORE_PASSWORD
Then update the keystore password recorded in each protocol configuration, swap the file in and restart:
sudo sed -i 's|<secret>.*</secret>|<secret>YOUR_NEW_KEYSTORE_PASSWORD</secret>|g' \
/var/lib/james/conf/smtpserver.xml /var/lib/james/conf/imapserver.xml
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
Keep the keystore password out of your shell history, and remember to renew before expiry.
Sending mail out through a relay
Because Azure blocks outbound port 25 on most subscriptions, configure an authenticated smart host on port 587. Apache James does this in mailetcontainer.xml: the relay processor's RemoteDelivery mailet takes a gateway, gatewayPort and credentials, which sends everything through your provider instead of connecting to recipient MX servers directly.
Add the gateway settings to the RemoteDelivery mailet:
<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>
Edit /var/lib/james/conf/mailetcontainer.xml, then restart:
sudo systemctl restart apache-james.service
sudo james-admin health
Your relay provider will also tell you which SPF, DKIM and DMARC DNS records to publish. Publish them — without them, most large providers will reject or quarantine your mail regardless of which server sent it.
DNS records for receiving mail
To receive mail for a domain you have added, publish:
- an MX record for the domain pointing at a hostname that resolves to this VM's public IP
- an A record for that hostname pointing at the VM's public IP
- a PTR (reverse DNS) record for the public IP pointing back at that hostname, configured on the Azure public IP resource
Give the VM a static public IP. A dynamic address will change across a stop and deallocate, and your MX record will silently stop resolving to your server.
Where everything lives
ls /opt/james
ls /var/lib/james
| Path | Contents |
|---|---|
/opt/james |
The Apache James distribution: application jar, dependency jars, and conf.template, the pristine placeholder configuration. Owned by root, read only to the service. |
/var/lib/james/conf |
The live configuration this VM is running |
/var/lib/james/var/store |
The embedded Derby mailbox database |
/var/lib/james/var/mail |
Mail repositories: error, relay denied, address error queues |
/var/lib/james/var/log |
james.log and its rotated archives |
/var/lib/james |
The Lucene full text search index, written directly into the working directory |
/root/apache-james-credentials.txt |
This VM's generated credentials, mode 0600 |
Logs and troubleshooting
sudo tail -30 /var/lib/james/var/log/james.log
sudo systemctl status apache-james.service --no-pager | head -20
If the server will not start, the JVM's own output goes to the journal:
sudo journalctl -u apache-james.service -n 60 --no-pager | tail -30
If apache-james.service reports inactive (dead) with condition failed, first boot has not completed. Check it:
sudo systemctl status apache-james-firstboot.service --no-pager | head -20
sudo tail -20 /var/log/cloudimg-firstboot.log
Backups
Everything stateful is under /var/lib/james. Stop the server for a consistent copy of the embedded database, archive the directory, then 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
Azure managed disk snapshots of the OS disk are the simplest whole machine option, and Azure Backup can schedule them.
Updating
Ubuntu security updates are applied automatically by unattended-upgrades, which is enabled in the image. Verify:
systemctl is-enabled unattended-upgrades.service
grep 'Unattended-Upgrade' /etc/apt/apt.conf.d/20auto-upgrades
For Apache James itself, cloudimg publishes refreshed images as upstream releases. Deploy the new image alongside, migrate mail with your usual IMAP tooling, then retire the old VM — this keeps the mailbox store and the application version in step.
Licensing
Apache James is free and open source software licensed under the Apache License 2.0, and is shipped here unmodified. The bundled runtime components — Apache Derby, Apache Lucene, Apache ActiveMQ, Netty, Jetty and Guice — are likewise permissively licensed. The Java runtime is Ubuntu's own openjdk-21-jre-headless package, installed from the Ubuntu archive. The full licence text ships in the image:
head -5 /opt/james/LICENSE
The cloudimg charge is for the packaging, hardening, testing and support of the image, not for the software itself.
Support
cloudimg provides 24/7 support for this image. Contact support through the Azure Marketplace listing or at cloudimg.co.uk.