Deploying Mox Mail Server with systemd and Traefik: A Lightweight Mailcow Alternative

Table of Contents ๐
- Changelog
- 1. Architecture
- 2. Prerequisites
- 3. Create the Mox User and Build the Binary
- 4. Initial Mox Configuration
- 5. systemd Service
- 6. Traefik File Provider Route
- 7. TLS for Mail Protocols
- 8. Firewall
- 9. DNS Checklist
- 10. Accounts and Passwords
- 11. Client Configuration
- 12. Migrating from Mailcow
- 13. Verification
- 14. Backups and Updates
- Conclusion
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
| Date | Change |
|---|---|
| 2026-07-01 | DNS Notes: Added SPF, DKIM, DMARC, TLSRPT, MTA-STS, and SRV record pitfalls from a production setup. |
| 2026-07-01 | Major Rework: Switched from Docker-only Mox to a native systemd deployment with direct mail ports, Traefik web routes, DNS, and migration notes. |
| 2025-11-12 | Initial 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 listenersMox 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.
| Function | Public hostname | Public port | Handler |
|---|---|---|---|
| SMTP delivery | mail.your-domain.com | 25/tcp | Mox directly |
| Submission | mail.your-domain.com | 587/tcp | Mox directly |
| SMTPS | mail.your-domain.com | 465/tcp | Mox directly |
| IMAPS | mail.your-domain.com | 993/tcp | Mox directly |
| Account/admin/webmail | mail.your-domain.com | 443/tcp | Traefik -> Mox HTTP |
| Autoconfig | autoconfig.your-domain.com | 443/tcp | Traefik -> Mox HTTP |
| Autodiscover | autodiscover.your-domain.com | 443/tcp | Traefik -> Mox HTTP |
| MTA-STS | mta-sts.your-domain.com | 443/tcp | Traefik -> Mox HTTP |
| โ ๏ธ PROVIDER SMTP BLOCKS |
Many VPS providers block outbound SMTP by default. Hetzner Cloud, for example, blocks outgoing ports |
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, and993/tcp. git,golang,jq, andopensslinstalled.
sudo apt update
sudo apt install -y git golang jq openssl3. 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 \
-skipdialThis creates Mox configuration and data under /home/mox/config and /home/mox/data.
Now edit the generated config:
sudoedit /home/mox/config/mox.confFor 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: truetells Mox to respect the reverse proxy headers for rate limiting and secure cookies.NonTLS: trueis 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 81The 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.comChange 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.targetEnable it:
sudo systemctl daemon-reload
sudo systemctl enable --now mox
sudo systemctl status mox6. 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 tcpWithout 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:
- Let Mox manage its own ACME certificates for the mail host.
- 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.pemThen 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.pemRestart Mox after changing certificate files or config:
sudo systemctl restart mox8. Firewall
Open the mail ports:
sudo ufw allow 25/tcp
sudo ufw allow 465/tcp
sudo ufw allow 587/tcp
sudo ufw allow 993/tcpKeep 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.comThe 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: TLSRPTThat means the following addresses do not have to be normal login accounts:
dmarcreports@your-domain.com
tlsreports@your-domain.com
tlsreports@mail.your-domain.comThey 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.comForward 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.comIf 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 userUse 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 addressFor SMTP clients:
Server: mail.your-domain.com
Port: 465
TLS: implicit TLS
Username: full email addressFor 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.crtIf you see this with AuthMechs LOGIN:
LOGIN unrecognized syntax/command: invalid escape charswitch 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:
- IMAP-to-IMAP migration with a tool such as
imapsync. - Local decrypted Maildir import from an existing
mbsynccache that was synced through Dovecot/IMAP before the migration. - 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.txtIMAPS:
openssl s_client -connect mail.your-domain.com:993 \
-servername mail.your-domain.com -briefSMTPS:
openssl s_client -connect mail.your-domain.com:465 \
-servername mail.your-domain.com -briefSubmission:
openssl s_client -starttls smtp -connect mail.your-domain.com:587 \
-servername mail.your-domain.com -briefSMTP delivery:
nc -vz mail.your-domain.com 25Finally, 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.com14. Backups and Updates
Back up:
/home/mox/config
/home/mox/data
/home/mox/tlsFor 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 moxWatch the logs after every update:
sudo journalctl -u mox -n 100 --no-pagerConclusion
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.





