..

Hosting My Own Mox Mail Server

Intro

I have wanted to distance my self as much as possible from Big Tech (Google, Microsoft, etc.), so I have been using mailbox.org as my mail host for the past few years. No major complaints from me.

Recently, I was reading some articles on Hacker News and stumbled upon Mox [1, 2] - a modern, secure, all-in-one email server.

I have always been afraid of hosting my own email server, due to all of the horror stories I’ve read about it online. Primarily about how Outlook, Gmail and other bigger providers might silently reject my emails and how hard it is to keep up with security etc.

From what I could gather from the comments and the website, it looks like Mox solves most of the complexity issues and makes it easier to setup a complete email-suite - so I decided to give it a go!

(I was also attracted to the brutalist/minimalist design choices in the webmail/admin pages tbh)

Setup

BEWARE: I was very confused doing my first run of this. I thought that you HAD to use the subdomain mail.example.com as the actual domain of your email, like so: example@mail.example.com.

Luckily, this is not the case! You can just use the subdomain to connect to IMAP and SMTP, and still get an email with your domain without a subdomain like so: example@example.com.

You just need to add the appropriate DNS entries, which I will do later in this post.

You can also use your subdomain if you want to.

Setting up Debian Server

I spun up a new VPS on Hetzner

I then ran my trusty Ansible Debian Server setup script: ansible-playbook -i hosts setup_debian.yml

setup_debian.yml:

---
- name: Setup Debian server, secure SSH, and change SSH port
  hosts: all
  become: yes
  vars:
    github_keys_url: https://github.com/nicholas-mn.keys
    new_user: user
    new_ssh_port: 1234

  tasks:
    - name: Create new user
      user:
        name: "{{ new_user }}"
        groups: sudo
        shell: /bin/bash
        create_home: yes

    - name: Install SSH keys for new user
      authorized_key:
        user: "{{ new_user }}"
        state: present
        key: "https://github.com/nicholas-mn.keys"

    - name: Secure SSH configuration and change port
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
      loop:
        - { regexp: '^#?Port', line: 'Port {{ new_ssh_port }}' }
        - { regexp: '^PermitRootLogin', line: 'PermitRootLogin no' }
        - { regexp: '^PasswordAuthentication', line: 'PasswordAuthentication no' }
        - { regexp: '^X11Forwarding', line: 'X11Forwarding no' }
        - { regexp: '^MaxAuthTries', line: 'MaxAuthTries 3' }
      notify: Restart SSH

    - name: Ensure public key authentication is enabled
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PubkeyAuthentication"
        line: "PubkeyAuthentication yes"
      notify: Restart SSH

    - name: Allow new user to use sudo without password
      lineinfile:
        path: /etc/sudoers
        state: present
        regexp: '^{{ new_user }}'
        line: '{{ new_user }} ALL=(ALL) NOPASSWD: ALL'
        validate: 'visudo -cf %s'

  handlers:
    - name: Restart SSH
      service:
        name: ssh
        state: restarted

hosts:

[mail.nich.dk]
88.198.127.102 ansible_user=root

I then made sure the hostname is correct.

  • sudo nano /etc/hostname
    • Changed the content to mail.nich.dk, which is where users will access IMAP and SMTP
  • sudo nano /etc/hosts
    • Changed first entry 127.0.1.1 oldhostname to 88.198.127.102 mail.nich.dk <- so it routes back to itself. It’s apparently for internal mail or something? Don’t take my word for it, I am completely new to mail hosting. Replace with your own public IP and domain (Your SMTP and IMAP subdomain, not your actual domain - or you can, if you plan to use it as the email domain as well).

I then installed Docker:

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

# Add user to Docker group
sudo groupadd docker
sudo usermod -aG docker $USER

Lastly I restarted the machine: sudo systemctl reboot

Setting up Mox

Fix DNSSEC errors

  • sudo apt install unbound dns-root-data

Create the Mox user:

  • sudo su <- switch to root
  • useradd -d $PWD mox <- will create mox user with the current directory as their home directory

Create the directory, which will contain all of the data and the docker compose file:

mkdir mox
cd mox
mkdir config data web
nano docker-compose.yml

docker-compose.yml:

# Before launching mox, run the quickstart to create config files for running as
# user the mox user (create it on the host system first, e.g. "useradd -d $PWD mox"):
#
#	mkdir config data web
# 	docker-compose run mox mox quickstart you@yourdomain.example $(id -u mox)
#
# note: if you are running quickstart on a different machine than you will deploy
# mox to, use the "quickstart -hostname ..." flag.
#
# After following the quickstart instructions you can start mox:
#
# 	docker-compose up
#

services:
  mox:
    # Replace "latest" with the version you want to run, see https://r.xmox.nl/r/mox/.
    # Include the @sha256:... digest to ensure you get the listed image.
    image: r.xmox.nl/mox:latest
    environment:
      - MOX_DOCKER=yes # Quickstart won't try to write systemd service file.
    # Mox needs host networking because it needs access to the IPs of the
    # machine, and the IPs of incoming connections for spam filtering.
    network_mode: 'host'
    volumes:
      - ./config:/mox/config
      - ./data:/mox/data
      # web is optional but recommended to bind in, useful for serving static files with
      # the webserver.
      - ./web:/mox/web
    working_dir: /mox
    restart: always
    healthcheck:
      test: netstat -nlt | grep ':25 '
      interval: 1s
      timeout: 1s
      retries: 10

Run the quickstart:

  • docker compose run mox mox quickstart example@nich.dk $(id -u mox) <- replace with your own domain

MAKE SURE YOU READ THE OUTPUT CAREFULLY!! There is very important info. You will see various errors and important info here. The most important information that you will see is your admin and account login information. Save this somewhere!!

Paste the DNS entries shown in the output into your DNS Management Host (Like Cloudflare). I manage my domain and DNS with Simply.com.

BEWARE: I overwrote my original DNS entries my first time around, oops! - see if there is an option in your DNS provider to add the entries without resetting DNS.

Wait for DNS to kick in - it took less than an hour for me.

Start the docker compose container: docker compose up -d

You can see logs with docker compose logs

Then login on devices. I use Thunderbird on desktop and mobile.

Profit?

You can read more documentation here

Webmail

You can also access the built-in webmail by forwarding the remote port to your device like so:

Mox webmail

What now?

You and I should probably read this as recommended by Mox: https://explained-from-first-principles.com/email/

We should probably also set up some backup solution, so we won’t lose our emails in case of server (or human) failure.

I will be running Mox for a while and do some testing. If it proves reliable, then I’ll switch over to it as my primary email solution.

See you around!