Introduction
Mox is a modern open-source email server. Mox's ambition is to replace a classic email server stack consisting of Postfix, Dovecot and Rspamd with an "all-in-one" package that makes running a mail server easier.
Note that Mox is relatively new (work started in 2021) which means it is not "battle-tested". However, it is a great option for anyone who is looking for an easy solution to setup their own mail server.
This tutorial shows how to install and configure a fully working email server with emphasis on a working setup featuring DNSSEC / DANE and SMTP MTA Strict Transport Security (MTA-STS).
Prerequisites
For this example, we will use hcloud
to create a new Hetzner Cloud server with dedicated vCPU and a new Hetzner Firewall. To follow the same steps, you need to install the hcloud
CLI on your local machine and obtain a Hetzner Cloud API token. If you want to create a server and firewall in a different way, you can do so too.
-
Server
- Debian 12
hcloud
installed and Hetzner Cloud API token in the Cloud Console to usehcloud
-
SSH key for root access
-
Domain, e.g.
example.com
You need to be able to add or change DNS records at your registrar or DNS provider.
To access the management UI of Mox, a WireGuard network is strongly recommended. The setup of WireGuard is out of scope of this tutorial. You will need to install WireGuard on your local machine for a direct connection or on your home router for a site-to-site connection. As an alternative you can connect via SSH port re-direction. For further help, see:
- WireGuard on Debian » "Step 2 - Alternative A - Manual Configuration"
- Step-by-step setup of WireGuard
Example terminology
Throughout this tutorial, the following example terms will be used:
- Domain:
example.com
- Subdomain for mail server:
mail.example.com
- Mail address of standard user:
holu@example.com
- Mail address of mail server owner:
postmaster@example.com
- Cloud server
IP addresses:- Public:
<203.0.113.1>
and<2001:db8:5678::1>
- Private LAN:
<198.51.100.1>
and<2001:db8:9abc::1>
<your_host>
- Public:
- WireGuard
- Port:
51820
- IP range:
10.51.0.0/16
- Port:
The mail server can send and deliver mails for other domain names. In this tutorial, we will use:
- Domain:
example.org
- Mail address of standard user:
holu2@example.org
Replace all mentions of these values with your actual values.
Step 1 - Basic firewall rules
Create a firewall called mailserver
with the label usage=email
and the label selector usage=email
and add the following rules:
The
hcloud
commands to setup the Firewall and the Firewall rules are shown below.
Description | Type | Source IPs | Protocol | Port |
---|---|---|---|---|
SSH Console Access | Incoming | 0.0.0.0/0 ::/0 | TCP | 22 |
ICMP | Incoming | 0.0.0.0/0 ::/0 | ICMP | |
WireGuard | Incoming | 0.0.0.0/0 ::/0 | UDP | 51820 |
HTTPS | Incoming | 0.0.0.0/0 ::/0 | TCP | 443 |
Mailbox - IMAP | Incoming | 0.0.0.0/0 ::/0 | TCP | 993 |
Mailbox - SMTP Submission | Incoming | 0.0.0.0/0 ::/0 | TCP | 465 |
Mail - Delivery | Incoming | 0.0.0.0/0 ::/0 | TCP | 25 |
About the rules:
Rule | Description |
---|---|
WireGuard | If you used a different port, change port 51820 according to your setup. |
HTTPS | MTA-STS requires a website, therefore the HTTPS port must be allowed. |
Mailbox | Beside the generic rules (SSH, ICMP, WireGuard), the access to the mailbox (IMAP, SMTP submission) is allowed only on TLS ports. POP3 is not supported by Mox. |
Mail - Delivery | Mail delivery to other mail servers is done over port 25. Note that Hetzner blocks port 25 by default. For more information, see the official FAQ entry (EN / DE). |
Fail2ban (and the dependency python3-systemd
) is only used for the SSH port. The mail ports are protected by Mox directly.
Optionally: To avoid excess log entries, change the default SSH port 25 (e.g. to 20025). You will need to uncomment the line "Port 20025" for sshd_config
in the cloud-init file from "Step 2".
With hcloud
, you can setup the Firewall and the Firewall rules with the following commands:
hcloud firewall create --name mailserver --label usage=email
hcloud firewall add-rule mailserver --description "SSH Console Access" --direction in --port 22 --protocol tcp --source-ips "0.0.0.0/0" --source-ips "::/0"
hcloud firewall add-rule mailserver --description "ICMP" --direction in --protocol icmp --source-ips "0.0.0.0/0" --source-ips "::/0"
hcloud firewall add-rule mailserver --description "WireGuard" --direction in --port 51820 --protocol udp --source-ips "0.0.0.0/0" --source-ips "::/0"
hcloud firewall add-rule mailserver --description "HTTPS" --direction in --port 443 --protocol tcp --source-ips "0.0.0.0/0" --source-ips "::/0"
hcloud firewall add-rule mailserver --description "Mailbox - IMAP" --direction in --port 993 --protocol tcp --source-ips "0.0.0.0/0" --source-ips "::/0"
hcloud firewall add-rule mailserver --description "Mailbox - SMTP Submission" --direction in --port 465 --protocol tcp --source-ips "0.0.0.0/0" --source-ips "::/0"
hcloud firewall add-rule mailserver --description "Mail - Delivery" --direction in --port 25 --protocol tcp --source-ips "0.0.0.0/0" --source-ips "::/0"
hcloud firewall apply-to-resource mailserver --type label_selector --label-selector usage=email
Step 2 - Create the server
The actual VPS server is configured with a Cloud Init file.
Save the content below locally as mox.cloud-init.yaml
. In fail2ban/jail.local
, replace the networks to ignore (in our case Hetzner Private Network and WireGuard network). Replace <your_host>
with a name of your choice.
#cloud-config
hostname: <your_host>
users:
- name: mox
groups: users
shell: /bin/bash
packages:
- dns-root-data
- unbound-anchor
- unbound
- wireguard
- jq
- fail2ban
- python3-systemd
package_update: true
package_upgrade: true
write_files:
- path: /etc/ssh/sshd_config.d/10-common.conf
content: |
SyslogFacility AUTH
LogLevel INFO
PermitRootLogin prohibit-password
StrictModes yes
PasswordAuthentication no
X11Forwarding no
# Port 20025
- path: /etc/fail2ban/jail.local
permissions: "0644"
content: |
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 198.51.100.0/24 10.51.0.0/16
backend = systemd
allowipv6 = auto
[sshd]
enabled = true
- path: /etc/fail2ban/fail2ban.local
permissions: "0644"
content: |
[DEFAULT]
loglevel = INFO
logtarget = SYSTEMD-JOURNAL
- path: /etc/unbound/unbound.conf.d/ede.conf
permission: "0755"
content: |
server:
ede: yes
val-log-level: 2
Now that you have the cloud init file, you can use it when you create the server:
In the command below, choose your own Hetzner location (e.g. --location fsn1
) and type (e.g. --type cpx21
). For a test installation, any type of cloud server will do. Also, replace <your_host>
with a server name of your choice and my_own_ssh_key_name
with the name of your SSH key.
hcloud server create --type <server_plan> --name <your_host> --image debian-12 --ssh-key my_own_ssh_key_name --location <location> --firewall mailserver --user-data-from-file mox.cloud-init.yaml --label usage=email
In a first-time setup you will receive a new Primary IPv4 and a new Primary IPv6, e.g. 203.0.113.1
and 2001:db8:5678::1
.
If these Primary IPs are good (see "Step 2.1"), you should protect the IP addresses and label them (e.g. mail-ip4
, mail-ip6
) in the Hetzner Cloud Console. Note that servers and Primary IPs are charged separately. If you remove the Primary IP from your server and delete the server, you will still get charged for the Primary IP until you disable protection and delete the Primary IP itself (see Primary IP » Pricing).
To re-create the server later with the same IPs, use:
hcloud server create --type <server_plan> --name <your_host> --image debian-12 --ssh-key my_own_ssh_key_name --location <location> --firewall mailserver --user-data-from-file cloud-init.yaml --label usage=email --primary-ipv4 mail-ip4 --primary-ipv6 mail-ip6
The server location must be the same location as on the first run as the Primary IPs are tied to it. You can change the server type.
In practice, a correct reverse DNS setting is required for mail servers:
hcloud server set-rdns --hostname mail.example.com --ip 203.0.113.1 <your_host>
hcloud server set-rdns --hostname mail.example.com --ip 2001:db8:5678::1 <your_host>
Advanced and out-of-scope: This setup demonstrates the installation of Mox in a basic, but still safe way. Consider hardening the server even more (e.g. root access, local firewall). If you consider a proxy, please be aware that the mail server needs the real IP of the connection.
Step 2.1 - Reputation of IP addresses
Sending and receiving emails depends on the reputation of your site. DNS blocking lists are used to fight spam mail. They block based on single IPs, IP ranges, domain names and further criteria.
Before you continue, verify that your new IP address does not appear on DNS blocking lists. You can use online tools such as https://multirbl.valli.org/lookup. Check primarily your IPv4, but also your IPv6 address.
The IP address should not appear on any block list. You may need to wait for a few hours for the propagation of your reverse DNS entries.
Exception: Some top level domains (e.g. mostly country domains with privacy regulation) are on the https://spfbl.net/en/dnsbl/ block list. This block list requires an email address in the DNS records which, due to regulation, are never there... So you can ignore this list.
If you have a burned IP, there are three options:
- Check if you can trigger a removal from the block list. The procedures are different for almost each list and not guaranteed to work in every case.
- Park the IP (or use it for no mail related stuff) for some days or weeks, then recheck
- Or create new Primary IPs and repeat the check. Make sure the new Primary IPs are in the same location as the Mox server. If the new Primary IPs are "good", you can turn off the Mox server, remove and delete its "bad" Primary IPs and assign the new "good" Primary IPs. After you assigned the new Primary IPs, you can switch the Mox server back on.
Caution: Do not continue with this tutorial until you have IPs with good reputation.
Step 2.2 - Basic DNS entries
We will use mail.example.com
as the name for our mail server instance.
Add A and AAAA DNS records for mail.example.com
. Wait for DNS propagation; check if you are able to ping your hostname from outside.
If you want, add A and AAAA DNS records for the hostname, e.g. <your_host>.example.com
.
Step 2.3 - DNSSEC
Our Cloud-init configuration installed "unbound" with a minimal configuration and with adaptions for Mox. Prior to enabling the DNSSEC/DANE setup for the mail server, ensure the functionality of the DNS resolver. There are several requirements to DNSSEC.
Your domain registrar must support it for your chosen TLD. Depending on the registrar / DNS provider, you need to enable DNSSEC for your domain. Also check if your DNS provider supports DNS records of type TLSA
.
Obviously you can use Mox without DNSSEC, DANE or MTA-STS, but the goal of this tutorial is to demonstrate a state-of-the-art mail server setup.
Check the basic DNSSEC support with delv mail.example.com
. The output should be like the examples below.
-
devl xmox.nl
as an example of a signed domain:; fully validated xmox.nl. 3600 IN A 84.22.96.237 xmox.nl. 3600 IN RRSIG A 13 2 3600 20240615023049 20240601022704 58294 xmox.nl. ENE/6GtRyJ95oL7jA9bayaVu2rZpks6kX4tM6kqkiZWHZPUc9/WFwLh9 NU2TMXgOz4rKkOf/WwlMJSnazjfREA==
-
An unsigned domain looks like this:
; unsigned answer hetzner.de. 60 IN A 213.133.116.44
For a DANE setup you need the RRSIG DNS record, and your local resolver needs to read the authoritative answer.
dig mail.example.com
and dig @127.0.0.1 mail.example.com
should show the same result. The line with flags: should include ad
.
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
Everything works? So now finally to the installation ...
Step 3 - Mox installation
Connect to your VPS as root. If you changed the port, make sure to specify it:
ssh -p 20025 root@<203.0.113.1>
Download the latest executable of Mox with:
cd /home/mox
wget https://beta.gobuilds.org/github.com/mjl-/mox@latest/linux-amd64-latest/dl -O mox
chmod +x ./mox
Mox is a single binary file, so that's all.
Use the quickstart
command to generate the setup:
su - mox
./mox quickstart postmaster@example.com
exit
Capture the output of mox quickstart
for future reference.
It contains the initial admin and postmaster password, which you can change at any time with mox setadminpassword
on the console.
Step 3.1 - Full DNS setup of main mail domain
The output of mox quickstart
also contains the required DNS records.
Add these records at your DNS provider/registrar.
We will need all the documented DNS entries.
The DNS DKIM must be added as a single TXT record (as mentioned in the quickstart output). Remove spaces and quotes, the record must start with v=DKIM1...
After you have done this tedious step, check again if DNSSEC/DANE works, with:
dig +dnssec +noall +answer +multi _25._tcp.mail.example.com. TLSA
The output should be similar to this:
_25._tcp.mail.example.com. 3600 IN TLSA 3 1 1 (
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
BBBBBBBBBBBBBBB )
_25._tcp.mail.example.com. 3600 IN TLSA 3 1 1 (
78888888888888888888888888888888888888888888
999999999999999 )
_25._tcp.mail.example.com. 3600 IN RRSIG TLSA 13 5 3600 (
20240901153149 20240121141946 48145 example.com.
wzkdfajkfjajsdflkjasdlfkjasdlfkjasldfjlasdfj
askjfdaklsdjflkasdjfklasdfjlkasdjfQQQQQQ )
If your output looks different, check your DNS entries and / or wait for DNS propagation.
Step 3.2 - Mox configuration
Change or check the following values in mox.conf
(e.g. with nano /home/mox/config/mox.conf
):
Value | Comment |
---|---|
Listeners » internal » IPs | Should only contain private IP addresses (Localhost, Hetzner private LAN, WireGuard) |
Listeners » public » IPs | Should contain two lines with the public IPv4 and IPv6 address |
Listeners » public » WebserverHTTP | Set Enabled to false (change it back later if you use this functionalty) |
Listeners » public » WebserverHTTPS | Set Enabled to false (change it back later if you use this functionalty) |
CheckUpdates | Enable to stay up to date (optional). |
Step 3.3 - Mox as a service
Again as root, execute these steps:
cd /home/mox
systemctl enable $PWD/mox.service
systemctl start mox.service
systemctl status mox.service
Verify that no error is shown in the status report.
Step 3.4 - Mox admin UI
Over WireGuard network, use http://198.51.100.1/admin/
or redirect over SSH http://localhost:8080/admin/
to access the admin page. The instructions to use SSH port redirections instead of a WireGuard VPN are contained in the quickstart output.
Go to Mox Admin
» Domain example.com
» Check DNS records
.
All entries must be green. MTA-STS with a MaxAge less than 1 day is no issue during testing but MUST be changed later for production use.
Use the admin pages to create accounts and mailboxes or use the CLI:
cd /home/mox
./mox config account add holu holu@example.com
./mox config address add admin@example.com holu
./mox config alias add info@example.com holu@example.com
./mox setaccountpassword holu
Step 4 - Day 2 operations
-
Mail client setup - Thunderbird
Using Thunderbird as mail client demonstrates the auto config setup. Add a new mail account with
holu@example.com
and all options should be auto-detected by Thunderbird, otherwise refer to the output of the quickstart.When configuring your email client, use the email address as username. If autoconfig/autodiscover does not work, use these settings: Protocol Host Port Listener Note Submission (SMTP) mail.example.com 465 public with TLS IMAP mail.example.com 993 public with TLS
-
Import and export
Check Import mailboxes to import mails, e.g. for a Dovecot migration.
-
Additional mail domains
Mox is able to serve other domains from this server. Just create a domain in the Admin UI or with the mox CLI:
cd /home/mox ./mox config domain add example.org postmaster ./mox config account add holu2 hol2u@example.org
You will need to setup the DNS entries for
example.org
, which are shown in the Admin UI for this additional mail domain.
-
Backup
As a starting point for a backup, use this script
#!/bin/bash MOXDIR=/home/mox MOXBACKUPDIR=${MOXDIR}/data/tmp/backup rm -rf ${MOXBACKUPDIR} /home/mox/mox backup ${MOXBACKUPDIR}/data /home/mox/mox verifydata ${MOXBACKUPDIR}/data cp -rp ${MOXDIR}/config/ ${MOXBACKUPDIR}/config
Now backup
MOXBACKUPDIR
to another server (e.g withrestic
or other backup tools).
Conclusion
You can use any of the many available mail server testing tools, such as https://internet.nl/mail/, to verify this setup and enjoy a 10/10 score.
While established mail server stacks offer a wider and robust range of features, Mox excels in its operator-friendliness. Setting up Mox securely and in accordance with best practices is significantly easier compared to traditional solutions. This reduced complexity makes Mox a good choice for a friend-and-family email server or for small businesses.