Running your own mail and groupware server can be challenging. I recently had to re-create my own setup from the ground and describe the steps in a blog post series. This blog post is #2 of the series and covers the initial mail server setup.
This post is all about setting up an initial mail server. Read about the background to this setup and the decisions I took in the first post, My own mail & groupware server, part 1: what, why, how?
Getting a server
The first thing of hosting your own mail server is to answer a simple question: where? Do you have an internet connection at home with fast uploads and maybe even a fixed IPv4? Or is cloud the only option? And if you take the cloud, will it be a virtual server or a root server?
My home connection doesn’t really allow for a larger server setup, so cloud is the only option. Cloud always means someone else’s computer, never forget that! But if you pick a hardware machine it is at least harder to access/copy that machine without your knowledge compared to a virtual instance.
My mail setup always run on root servers, and for the last years I picked Hetzner as the hoster. Their server auction often has appealing things on sale, so that you can get something “decent enough” for around $30 per months.
So, for my new server I got one from the server auction again:
The server was up quickly. The next step was to get it provisioned properly. Mailu requires docker compose. And since Docker is not properly supported on CentOS 8 I decided to got with CentOS 7. This I rebooted the server into security mode and started Hetzner’s custom installer to install centos77 minimal. RAID 1 was already configured, I just altered the partition sizes and moved most of the storage to the custom mount point
Besides the basic provisioning I added rDNS entries for IPv4 and IPv6 – don’t forget those, they are important for many spam filters!
Speaking of DNS, the domain someone wants to use for mail needs to be set up in DNS as well. At least the following things should be done:
- create an A entry named
@for your server IPv4
- create an AAAA entry named
@for your IPv6
- create an A entry with your server’s host name for the server IPv4
- create an AAAA entry with your server’s host name the for server IPv6
- create a MX entry pointing to A entry for server host name (not a CNAME!)
- add a CAA entry for letsencrpyt:
0 issue "letsencrypt.org"
Many of those entries were still there from my previous setup, but I had to adjust the IP addresses to the new server and add the new host – host name “lisa”, named after the Simpsons.
Basic user and SSH
After setting up a new server, the next step usually is to add a new user:
adduser liquidat creates it,
usermod -aG wheel liquidat adds my user to the sudo group, and additionally I set the group wheel to NOPASSWD via visudo.
Also, copy the authorized keys from the root user to the new user –
cp -r /root/.ssh /home/liquidat/ – and correct their ownership:
chown -R liquidat:liquidat /home/liquidat/.ssh
Just to be sure the “right” ssh keys are there, copy your usual set over:
ssh-copy-id email@example.com . Personally, afterwards I removed all besides the currently most trusted (ssh-ed25519…).
Last but not least deactivate root login to ssh:
- Also, in
Port 2222(we want to use port 22 for the git server later on)
- Restart sshd:
systemctl restart sshd
One of the most important steps for me (YMMV) is an encrypted hard drive. In my personal risk assessment this impedes many possible hardware attacks from certain actors. Of course certain risks remain.
As mentioned, most of the storage is setup in a partition on
/data. So it has to be encrypted properly, formatted and made available again. Note that this requires you to log into the machine after every reboot and actively decrypt the partition. If your server ever goes down, all services on it are down until you decrypted the partition!
- Umount existing mount:
sudo umount /data/
- Set up crypted device:
sudo cryptsetup luksFormat /dev/md3
- Get passphrase via
pwgen -y 50
- Decrypt the device:
sudo cryptsetup luksOpen /dev/md3 verysecret
- Create a file system on it:
sudo mkfs.ext4 /dev/mapper/verysecret
- Mount it:
sudo mount /dev/mapper/verysecret /data
I can only recommend to verify the decryption afterwards:
- Reboot server
- Decrypt storage:
sudo cryptsetup luksOpen /dev/md3 verysecret
- Mount storage:
sudo mount /dev/mapper/verysecret /data
Next I had to install Docker. Personally not my first choice to run containers, I would rather use Podman or even get my hands dirty with Kubernetes. But alas, time was short and pressure was high.
To get Docker onto the machine:
- Remove CentOS’ Docker packages:
sudo yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
- Install tooling to easier add third party repos:
sudo yum install -y yum-utils
- Add Docker’s third party repo for CentOS:
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
- Install Docker:
sudo yum install -y docker-ce docker-ce-cli containerd.io
- We don’t want autostarts of Docker since the device is still encrypted:
sudo systemctl disable docker
- Get Docker up:
sudo systemctl start docker
- Create Docker group:
sudo groupadd docker
- Add user to it:
sudo usermod -aG docker $USER
- Load new group immediately:
- Get compose:
sudo curl -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
- Make compose executable:
sudo chmod +x /usr/local/bin/docker-compose
I will always wonder why they never managed to get compose out there as a package. Not that hard, I’d say?!
But anyhow, the stage was set now: the server was up and running, a user was ready to do work, the device was properly secured, I was ready to set up the mail server!
Getting Mailu up and running is really a matter of minutes. I must admit I was impressed – especially since I knew how much time my own setup ate over the years. Basically you use a config file generator from them which will generate a
docker-compose.yml, and then start it. That’s really all!
sudo mkdir /data/mailu
- create config file via
- make sure to add all kinds of subdomains you will be using
- in my case: don’t activate webmail or caldav, I will be using Nextcloud for that
- download config files via wget as instructed: one
mailu.envcontaining all the entered variables
- verify that
ANTIVIRUSis indeed defined and not commented out in
mailu.env(thanks to dhoppe for that)
docker-compose -p mailu up -d
And that’s it, really! My mail server setup was already running, after minutes. Next I added an admin user:
- create a password for the admin user via
- create admin password:
docker-compose -p mailu exec admin flask mailu admin postmaster bayz.de $PASSWORD
- log in to admin interface:
https://lisa.bayz.de/admin/, login is
The Mailu admin interface is nothing spectacular, but does it’s job:
After this, I did some housework: not strictly necessary, but helpful:
- Add abuse alias to postmaster
- Add admin alias to postmaster (needed for RUA, dmarc aggregated reports)
- Generate DNS entries for SPF, DKIM, etc and add them to your DNS domain entries
- Add other users or even domains at will; all domains entered must be present in the
And that’s it! I was able to send myself mail via some freemail accounts. And with a classic mail client (Thunderbird, or something on the phone) I could also send mails. It all just worked!
Get word out there
However, I still had to get word out there that there is a new mail server and that it will be sending valid mails.
For example, I registered the new mail server at the DNS whitelist, DSWL: many spam filters check against that.
Next, I let Microsoft know of the new machine and registered it at postmater.live.
Last but not least I checked in with Google’s postmaster service.
Verifying and testing the setup
Now it was time for serious testing. I already said mail is hard, right? You better not do mistakes in your configuration, otherwise your mail is marked as spam quickly. So how about some online tests to check how good my new server scored against various spam filters? Here is a list of online checks of all kinds, including services to which someone can send mails to get them analyzed:
- Send a mail and get it tested against Spamassassin, SPF, DKIM, DMARC, rDNS, HELO:
- Send a mail and get it tested against SPF, DKIM, Spamassassin, including full message log:
- Live connection details of sending mails to server:
- Various dedicated test tools (DMARC, DKIM, SPF, DNS, SMTP TLS, open relay):
- Another set of dedicated test tools for SMTP Banner, Mail test, Blacklist lookup, abuse, open relay, imap banner, etc.:
- Check your DNS setup – this was the only one which correctly marked as red that I had my DNS MX entry pointed at a CNAME:
All these tests were green. And should always be! As a small private mail server I cannot afford it to have even the tiniest error. If you decide to setup something like this: do not proceed in your mail setup if some test shows something like “9/10” or other inferior results. Fix them all! I cannot stress this enough.
Having said that, you will realize that indeed this setup is not perfect: first and foremost, we will not be accepting mails via IPv6. Thus services testing delivery in IPv6 will report problems. Second, DANE is not working out of the box with Mailu. In the long term I hope that I will be able to update this guide to include both functions properly.
So the mail server was up and running. I was already able to use it with IMAP clients. And given my story leading to this setup you cannot believe how relieved I was once everything worked again and mails were coming in.
I knew that there was still a lot to do – and I will post more posts about the other steps in other blog posts – but the most important task was accomplished.
I’d like to thank the Mailu team for their awesome work on this piece of code – it is really great and I highly appreciate the ease of use and the simple admin capabilities.