This guide deploys Mox as a native mail server managed by systemd, while keeping Traefik v3 responsible for the public HTTPS routes. This is the cleanest pattern when Traefik already terminates TLS for your web stack, but you still need a real mail server that owns the mail protocols directly.

Mox is a good lightweight alternative to a larger stack such as Mailcow. It includes SMTP, submission, IMAP, webmail, admin pages, account pages, MTA-STS, autoconfig, DNS checks, and spam filtering in one service.

โ„น๏ธ TRAEFIK DOES NOT PROXY MAIL PROTOCOLS

Traefik is only used for HTTPS routes such as webmail, admin, autoconfig, autodiscover, and MTA-STS. SMTP and IMAP must be reachable directly on the host. Reverse-proxying the web UI alone is not a complete mail server setup.

Changelog

DateChange
2026-07-01DNS Notes: Added SPF, DKIM, DMARC, TLSRPT, MTA-STS, and SRV record pitfalls from a production setup.
2026-07-01Major Rework: Switched from Docker-only Mox to a native systemd deployment with direct mail ports, Traefik web routes, DNS, and migration notes.
2025-11-12Initial Version: Guide created for integrating Mox with the Traefik v3 stack.

1. Architecture

The target architecture looks like this:

Internet
  |
  | 25, 465, 587, 993
  v
Mox on the host via systemd

Internet
  |
  | 443
  v
Traefik -> Mox internal HTTP listeners

Mox runs as a host service under /home/mox. It binds the mail ports directly, then drops privileges to the mox user. Traefik reaches Mox over an internal Docker bridge address for the HTTPS-facing helper endpoints.

FunctionPublic hostnamePublic portHandler
SMTP deliverymail.your-domain.com25/tcpMox directly
Submissionmail.your-domain.com587/tcpMox directly
SMTPSmail.your-domain.com465/tcpMox directly
IMAPSmail.your-domain.com993/tcpMox directly
Account/admin/webmailmail.your-domain.com443/tcpTraefik -> Mox HTTP
Autoconfigautoconfig.your-domain.com443/tcpTraefik -> Mox HTTP
Autodiscoverautodiscover.your-domain.com443/tcpTraefik -> Mox HTTP
MTA-STSmta-sts.your-domain.com443/tcpTraefik -> Mox HTTP
โš ๏ธ PROVIDER SMTP BLOCKS

Many VPS providers block outbound SMTP by default. Hetzner Cloud, for example, blocks outgoing ports 25 and 465 until the account is eligible and the limit is lifted. Request this before relying on direct delivery, or configure a relayhost.

2. Prerequisites

You need:

  • A working Traefik v3 and CrowdSec stack.
  • A public IPv4 address. IPv6 is strongly recommended.
  • Control over DNS records for the mail domain.
  • Open firewall ports 25/tcp, 465/tcp, 587/tcp, and 993/tcp.
  • git, golang, jq, and openssl installed.
sudo apt update
sudo apt install -y git golang jq openssl

3. Create the Mox User and Build the Binary

Create a dedicated system user and build Mox from an explicit release tag.

sudo useradd --system --home-dir /home/mox --create-home --shell /usr/sbin/nologin mox
sudo install -d -o mox -g mox /home/mox/src

sudo -u mox git clone https://github.com/mjl-/mox.git /home/mox/src/mox
cd /home/mox/src/mox

# Replace this with the release tag you want to run.
sudo -u mox git checkout vX.Y.Z
sudo -u mox go build -trimpath -ldflags="-s -w" -o /home/mox/mox ./cmd/mox

sudo chown root:root /home/mox/mox
sudo chmod 0755 /home/mox/mox
๐Ÿ’ก USE RELEASE NOTES

Mox moves quickly. Check the upstream release notes before choosing or changing a release tag, especially on production mail systems.

4. Initial Mox Configuration

Run the quickstart in existing-webserver mode. Use -skipdial if DNS still points at an old server during a migration.

cd /home/mox

sudo /home/mox/mox quickstart \
  -hostname mail.your-domain.com \
  -adminemail postmaster@your-domain.com \
  -existing-webserver \
  -skipdial

This creates Mox configuration and data under /home/mox/config and /home/mox/data.

Now edit the generated config:

sudoedit /home/mox/config/mox.conf

For the internal listener, enable the HTTP services that Traefik will route to. In this example:

  • Account, admin, webmail, and web API use internal port 1080.
  • Autoconfig, autodiscover, and MTA-STS use internal port 81.
  • Forwarded: true tells Mox to respect the reverse proxy headers for rate limiting and secure cookies.
  • NonTLS: true is used for the internal listener because Traefik handles public HTTPS.

Conceptually, the internal listener should expose:

AccountHTTP    -> port 1080, Forwarded: true
AdminHTTP      -> port 1080, Forwarded: true
WebmailHTTP    -> port 1080, Forwarded: true
WebAPIHTTP     -> port 1080, Forwarded: true
AutoconfigHTTPS -> port 81, NonTLS: true
MTASTSHTTPS     -> port 81, NonTLS: true
WebserverHTTP   -> port 81

The public listener should bind your serverโ€™s real public IP addresses and enable SMTP, submission, SMTPS, and IMAPS.

โ„น๏ธ EXPLICIT PUBLIC IPS

Prefer explicit public IPs in the Mox public listener. This makes outgoing mail source addresses predictable and helps Mox validate that your DNS and reverse DNS are consistent.

For MTA-STS, keep the generated short MaxAge while testing. Once the domain is reachable, certificates work, and the DNS records are stable, raise it to a production value such as:

MTASTS:
	PolicyID: 20260701T110042
	Mode: enforce
	MaxAge: 720h0m0s
	MX:
		- mail.your-domain.com

Change PolicyID whenever you materially change the MTA-STS policy. The matching DNS record under _mta-sts.your-domain.com must use the same new ID so remote mail servers fetch the updated policy.

5. systemd Service

Create /etc/systemd/system/mox.service:

[Unit]
Description=mox mail server
After=network-online.target
Wants=network-online.target

[Service]
UMask=007
LimitNOFILE=65535
Type=simple
WorkingDirectory=/home/mox
ExecStart=/home/mox/mox serve
ExecStop=/home/mox/mox stop
Restart=always
RestartSec=5s
SyslogFacility=mail

# Mox starts as root to bind low ports, then drops privileges.
PrivateDevices=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/home/mox/config /home/mox/data
ProtectKernelTunables=yes
ProtectControlGroups=yes
CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_NET_BIND_SERVICE CAP_CHOWN CAP_FSETID CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_KILL
NoNewPrivileges=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
ProtectProc=invisible
RestrictNamespaces=yes
RestrictRealtime=yes
RemoveIPC=yes
ProtectHostname=yes
ProtectClock=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
MemoryDenyWriteExecute=yes
LockPersonality=yes
DevicePolicy=closed
SystemCallArchitectures=native
SystemCallFilter=@system-service

[Install]
WantedBy=multi-user.target

Enable it:

sudo systemctl daemon-reload
sudo systemctl enable --now mox
sudo systemctl status mox

6. Traefik File Provider Route

Because Mox is not a Docker container in this setup, use the Traefik file provider instead of Docker labels.

Create /opt/containers/traefik-stack/traefik/dynamic/mox.yml:

http:
  routers:
    mox-mail:
      rule: "Host(`mail.your-domain.com`)"
      entryPoints:
        - websecure
      service: mox-account
      tls:
        certResolver: tls_resolver
      middlewares:
        - security-headers@file
        - crowdsec-bouncer@docker

    mox-autoconfig:
      rule: "Host(`autoconfig.your-domain.com`) || Host(`autodiscover.your-domain.com`)"
      entryPoints:
        - websecure
      service: mox-web
      tls:
        certResolver: tls_resolver
      middlewares:
        - security-headers@file
        - crowdsec-bouncer@docker

    mox-mta-sts:
      rule: "Host(`mta-sts.your-domain.com`)"
      entryPoints:
        - websecure
      service: mox-web
      tls:
        certResolver: tls_resolver
      middlewares:
        - security-headers@file
        - crowdsec-bouncer@docker

  services:
    mox-account:
      loadBalancer:
        servers:
          - url: "http://172.18.0.1:1080"

    mox-web:
      loadBalancer:
        servers:
          - url: "http://172.18.0.1:81"

Replace 172.18.0.1 with the Docker bridge gateway address Traefik can use to reach the host.

docker network inspect proxy \
  --format '{{range .IPAM.Config}}{{println .Gateway}}{{end}}'

If UFW is enabled, allow only the Traefik Docker network to reach those host ports:

sudo ufw allow in on br-REPLACE_WITH_PROXY_BRIDGE_IFACE \
  from 172.18.0.0/16 to 172.18.0.1 port 1080 proto tcp

sudo ufw allow in on br-REPLACE_WITH_PROXY_BRIDGE_IFACE \
  from 172.18.0.0/16 to 172.18.0.1 port 81 proto tcp

Without these narrow bridge rules, Traefik may return 504 Gateway Timeout even though Mox is listening correctly.

7. TLS for Mail Protocols

Traefik can terminate HTTPS for web routes, but Mox still needs certificates for SMTP and IMAP TLS. You have two practical options:

  1. Let Mox manage its own ACME certificates for the mail host.
  2. Export the Traefik ACME certificate for the mail-related names and configure it in mox.conf.

For a Traefik-centered setup, exporting from acme.json keeps one certificate authority flow. The exact script depends on your resolver name and SAN layout, but the important result is that Mox has PEM files for:

/home/mox/tls/mail.your-domain.com-chain.crt.pem
/home/mox/tls/mail.your-domain.com.key.pem

Then reference them under the public listenerโ€™s TLS section:

TLS:
	KeyCerts:
		-
			CertFile: /home/mox/tls/mail.your-domain.com-chain.crt.pem
			KeyFile: /home/mox/tls/mail.your-domain.com.key.pem

Restart Mox after changing certificate files or config:

sudo systemctl restart mox

8. Firewall

Open the mail ports:

sudo ufw allow 25/tcp
sudo ufw allow 465/tcp
sudo ufw allow 587/tcp
sudo ufw allow 993/tcp

Keep the Mox internal HTTP ports restricted to Traefikโ€™s Docker bridge only. Do not expose 1080 or 81 publicly.

9. DNS Checklist

Mox has a built-in DNS record generator and checker. Use both:

sudo /home/mox/mox -config /home/mox/config/mox.conf config dnsrecords your-domain.com
sudo /home/mox/mox -config /home/mox/config/mox.conf config dnscheck your-domain.com

The generated output is authoritative for your installation. Do not copy the example values below blindly, but use them as a checklist for the record types your DNS provider should contain.

Core Mail Records

At minimum, configure the mail host, MX, and SPF:

mail.your-domain.com.       A      YOUR_IPV4
mail.your-domain.com.       AAAA   YOUR_IPV6
mail.your-domain.com.       TXT    "v=spf1 a -all"
your-domain.com.            MX     10 mail.your-domain.com.
your-domain.com.            TXT    "v=spf1 ip4:YOUR_IPV4 ip6:YOUR_IPV6 mx ~all"

Only publish one SPF TXT record per name. Two TXT records beginning with v=spf1 on the same name are invalid and Mox will report multiple spf txt records in dns.

If you do not use IPv6, omit the AAAA record and the ip6: mechanism. If you do use IPv6, make sure Mox binds to that same address and that reverse DNS exists for it.

DKIM

Add all DKIM selector records generated by Mox, for example:

2026a._domainkey.your-domain.com. TXT "v=DKIM1;h=sha256;p=..."
2026b._domainkey.your-domain.com. TXT "v=DKIM1;h=sha256;p=..."

When entering DKIM through a DNS provider web UI, combine the zonefile-style split strings into one long TXT value. Keep old DKIM selectors only if you still need to validate mail sent before the migration or from another system; Mox itself signs with the selectors configured in domains.conf.

DMARC and TLS Reporting

Mox can route technical reports into dedicated mailboxes on an existing account. A typical generated domain config looks like this:

DMARC:
	Localpart: dmarcreports
	Account: operator
	Mailbox: DMARC
TLSRPT:
	Localpart: tlsreports
	Account: operator
	Mailbox: TLSRPT

That means the following addresses do not have to be normal login accounts:

dmarcreports@your-domain.com
tlsreports@your-domain.com
tlsreports@mail.your-domain.com

They are technical report sinks. Mox delivers them to the configured account and mailbox.

Example records:

_dmarc.your-domain.com.             TXT "v=DMARC1;p=reject;rua=mailto:dmarcreports@your-domain.com!10m"
_smtp._tls.your-domain.com.         TXT "v=TLSRPTv1; rua=mailto:tlsreports@your-domain.com"
_smtp._tls.mail.your-domain.com.    TXT "v=TLSRPTv1; rua=mailto:tlsreports@mail.your-domain.com"

The !10m suffix in the DMARC rua value is a report size limit, not part of the mailbox name.

MTA-STS

MTA-STS needs both the HTTPS endpoint and the TXT policy ID:

mta-sts.your-domain.com.       CNAME mail.your-domain.com.
_mta-sts.your-domain.com.      TXT "v=STSv1; id=20260701T110042"

The TXT id must match the current policy ID in Mox. Whenever you change the MTA-STS policy, change both the Mox PolicyID and this DNS TXT value. Do not leave multiple _mta-sts TXT records behind; Mox will report multiple mta-sts records.

Autoconfig and SRV Records

For client autoconfiguration:

autoconfig.your-domain.com.          CNAME mail.your-domain.com.
autodiscover.your-domain.com.        CNAME mail.your-domain.com.
_autodiscover._tcp.your-domain.com.  SRV 0 1 443 mail.your-domain.com.
_imaps._tcp.your-domain.com.         SRV 0 1 993 mail.your-domain.com.
_submissions._tcp.your-domain.com.   SRV 0 1 465 mail.your-domain.com.

Mox may also generate optional SRV records that explicitly disable plaintext IMAP, plaintext submission, and POP3:

_imap._tcp.your-domain.com.          SRV 0 0 0 .
_submission._tcp.your-domain.com.    SRV 0 0 0 .
_pop3._tcp.your-domain.com.          SRV 0 0 0 .
_pop3s._tcp.your-domain.com.         SRV 0 0 0 .

These optional records are useful for a completely quiet dnscheck, but some DNS web interfaces do not accept . as a target. If your provider rejects them, leave them out; the secure IMAPS and submissions SRV records above are the important ones.

Reverse DNS

Set reverse DNS/PTR at your server provider, not at the domain DNS provider:

YOUR_IPV4 -> mail.your-domain.com
YOUR_IPV6 -> mail.your-domain.com

Forward and reverse DNS should agree: mail.your-domain.com must resolve back to the same public IP addresses.

Checking Authoritative DNS

Resolver caches can show stale values for a while. If dnscheck still reports an old DMARC, TLSRPT, SPF, or MTA-STS record after you changed it, query the authoritative nameserver directly:

dig +short @ns1.your-dns-provider.example TXT _dmarc.your-domain.com
dig +short @ns1.your-dns-provider.example TXT _smtp._tls.your-domain.com
dig +short @ns1.your-dns-provider.example TXT _mta-sts.your-domain.com

If the authoritative nameserver is correct but Mox still sees old records, wait for the previous TTL to expire.

โš ๏ธ DNSBL RESOLVER WARNINGS

Some cloud provider resolvers can trigger DNSBL โ€œopen resolverโ€ warnings, especially with Spamhaus checks. If you use DNSBLs in production, make sure your recursive resolver and DNSBL access pattern are acceptable for the provider.

10. Accounts and Passwords

Create accounts in the admin UI at:

https://mail.your-domain.com/admin/

Users can change their own account settings and password at:

https://mail.your-domain.com/

You can also set account passwords from the CLI:

printf '%s\n' 'NEW_PASSWORD' | \
  sudo /home/mox/mox -config /home/mox/config/mox.conf setaccountpassword user

Use the account name without the domain if that is how the account exists in Mox.

11. Client Configuration

For IMAP clients:

Server: mail.your-domain.com
Port: 993
TLS: IMAPS / implicit TLS
Username: full email address

For SMTP clients:

Server: mail.your-domain.com
Port: 465
TLS: implicit TLS
Username: full email address

For mbsync, Mox worked reliably with CRAM-MD5:

IMAPStore user@your-domain.com-remote
Host mail.your-domain.com
Port 993
User user@your-domain.com
PassCmd "pass user@your-domain.com"
AuthMechs CRAM-MD5
TLSType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

If you see this with AuthMechs LOGIN:

LOGIN unrecognized syntax/command: invalid escape char

switch the Mox account to AuthMechs CRAM-MD5.

12. Migrating from Mailcow

Do not blindly copy Mailcow/Dovecot files into Mox.

Mailcow installations may use Dovecot plugins such as mail_crypt and zlib. In that case, the files in the Mailcow vmail volume can be encrypted or compressed in a Dovecot-specific format. Importing those raw files into Mox produces broken messages with binary-looking headers.

Safer migration options:

  1. IMAP-to-IMAP migration with a tool such as imapsync.
  2. Local decrypted Maildir import from an existing mbsync cache that was synced through Dovecot/IMAP before the migration.
  3. Selective migration of only active mailboxes and folders.

For a lightweight move, migrate active accounts and skip stale test folders. Keep the old Mailcow server stopped but preserved for a short rollback window.

13. Verification

Web routes:

curl -I https://mail.your-domain.com/
curl -I https://autoconfig.your-domain.com/
curl -I https://mta-sts.your-domain.com/.well-known/mta-sts.txt

IMAPS:

openssl s_client -connect mail.your-domain.com:993 \
  -servername mail.your-domain.com -brief

SMTPS:

openssl s_client -connect mail.your-domain.com:465 \
  -servername mail.your-domain.com -brief

Submission:

openssl s_client -starttls smtp -connect mail.your-domain.com:587 \
  -servername mail.your-domain.com -brief

SMTP delivery:

nc -vz mail.your-domain.com 25

Finally, send mail to and from an external mailbox and check:

sudo journalctl -u mox -f
sudo /home/mox/mox -config /home/mox/config/mox.conf config dnscheck your-domain.com

14. Backups and Updates

Back up:

/home/mox/config
/home/mox/data
/home/mox/tls

For updates, build a new Mox binary from a chosen release tag, then restart:

cd /home/mox/src/mox
sudo -u mox git fetch --tags
sudo -u mox git checkout vNEW.VERSION
sudo -u mox go build -trimpath -ldflags="-s -w" -o /tmp/mox.new ./cmd/mox

sudo install -o root -g root -m 0755 /tmp/mox.new /home/mox/mox
sudo systemctl restart mox

Watch the logs after every update:

sudo journalctl -u mox -n 100 --no-pager

Conclusion

Mox works best as a real mail server with direct ownership of SMTP and IMAP. Traefik remains valuable for the browser-facing endpoints, but it should not hide the fact that mail delivery depends on DNS, reverse DNS, provider port policy, TLS on mail protocols, and careful migration of existing mail data.

๐Ÿ“šMOX DOCUMENTATION ๐Ÿ“ฆMOX GITHUB REPOSITORY