Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Install Mox — a secure all-in-one email server

profile picture
Author
Robert Bill
Published
2024-06-12
Time to read
15 minutes reading time

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

  • 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:


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>
    Hostname: <your_host>
  • WireGuard
    • Port: 51820
    • IP range: 10.51.0.0/16

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.

Hetzner DNS doesn't support DNSSEC (DE).

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 with restic 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.

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2024 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more