paint-brush
Stick It To The Man and Self-Host Your Own Email Serverby@theselfhoster
385 reads
385 reads

Stick It To The Man and Self-Host Your Own Email Server

by George PapadakisFebruary 2nd, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Self-hosting email for small to medium companies, business owners, non-profit organizations and individuals. There is no real limit to who may self-host, but privacy conscious groups will benefit directly and see immediate results. Small to medium companies will get a fully functioning system that adheres to all current encryption and security protocols, especially the PCI specification.
featured image - Stick It To The Man and Self-Host Your Own Email Server
George Papadakis HackerNoon profile picture

Who is this for

Self-hosting email for small to medium companies, business owners, non-profit organizations and individuals. There is no real limit to who may self-host, but privacy conscious groups will benefit directly and see immediate results. Small to medium companies will get a fully functioning system that adheres to all current encryption and security protocols, especially the PCI specification.

Why self-hosting

There are tons of reasons why self-hosting is beneficial, here are some of them:


  • Privacy
  • Enhanced security
  • Customization
  • Lower costs

Privacy

Hosting at the major known providers is free, at first glance. A second look at their business practices will give you an indication that nothing is free, since YOU are the product. Your information is being analysed, categorized, fed into AI, given to advertisers and other 3rd parties.

Enhanced security

The major players have to be very compatible, to make sure everyone has access. In a self-hosting environment that does not apply. You can set your own rules, enforce strict limits that wouldn’t be applicable to general hosting.

Customization

Self-hosting allows you to do a lot more, like use custom configuration options, run experimental code, test new features and provide a more bespoke environment for your hosting needs.

Lower costs

Self-hosting can be achieved with open source tools, a huge saving over commercial control panels and enterprise solutions.

Prerequisites

  • A new server, dedicated or virtual, with internet access
  • A RHEL-based Linux distribution: Alma, Rocky, Oracle or Fedora Server
  • Root access

Server hostname

Please make sure your server has a fully qualified domain name (FQDN) and the /etc/hosts file has the IP of the server point to the FQDN and not the short name. This is a requirement for some tools like OpenDKIM, which will fail to work without an FQDN.


To test if your FQDN is properly setup, run:

# hostname -f
server.example.tld

If you only see the server name and not the FQDN then use the systemd hostnamectl command to set your FQDN:

# hostnamectl set-hostname server.example.tld

Step 1 - Required packages

The first step is to install some basic packages, use your package manager:


dnf for newer versions of EL8/EL9:

# dnf install php php-cli php-pdo php-mbstring epel-release unzip wget


yum for EL7:

# yum install php php-cli php-pdo php-mbstring epel-release unzip wget


for Fedora the packages are slightly different:

# dnf install php php-cli php-pdo php-mbstring unzip wget

Step 2 - Install Aetolos

In the second step we’ll install the Aetolos virtual hosting control panel, which only generates configuration files and does not make any other modifications to the system:

# wget -O /root/master.zip 'https://gitlab.com/noumenia/aetolos/-/archive/master/aetolos-master.zip'
# unzip /root/master.zip -d /root/
# mv /root/aetolos-master /root/aetolos
# rm -rf /root/master.zip

The latest development code can be downloaded from the git repository:

git clone 'https://gitlab.com/noumenia/aetolos.git'

Step 3 - Enable services

Now it is time to choose what services the server will provide. Run Aetolos with the status parameter to get an idea of all the available services:


# ~/aetolos/aetolos --status
+---------+--------------+---------+------------+--------------+
| Enabled | Module       | SystemD | Repository | Dependencies |
+---------+--------------+---------+------------+--------------+
| [ ]     | apache       |         |            |              |
| [ ]     | clamav       |         | epel       | postfix      |
| [ ]     | dehydrated   |         |            |              |
| [ ]     | dovecot      |         |            |              |
| [ ]     | haproxy      |         |            |              |
| [ ]     | mariadb      |         |            |              |
| [ ]     | mtasts       |         |            | apache       |
| [ ]     | nsd          |         | epel       |              |
| [ ]     | opendkim     |         | epel       | postfix      |
| [ ]     | opendmarc    |         | epel       | postfix      |
| [✓]     | php          |         |            |              |
| [ ]     | postfix      |         |            |              |
| [ ]     | postgrey     |         | epel       | postfix      |
| [ ]     | spamassassin |         |            | postfix      |
| [✓]     | virtualhost  |         |            |              |
+---------+--------------+---------+------------+--------------+

As this is going to be a self-hosted email server, we are going to enable the services that give us a complete set of features for email hosting:


  • Apache, a web server for responding on ACME requests

  • Dehydrated, a bash script for Let’s Encrypt certificates

  • Dovecot, a POP3/IMAP mail server

  • OpenDKIM, a DKIM validator and signer

  • OpenDMARC, a DMARC validator

  • Postfix, an SMTP server

  • Spamassassin, spam protection


We will not enable ClamAV because it requires at least 4GB of memory, the reader may opt to enable it based on the resources available on the server.


Let’s enable these services with Aetolos:

# ~/aetolos/aetolos --enable=apache
# ~/aetolos/aetolos --enable=dehydrated
# ~/aetolos/aetolos --enable=dovecot
# ~/aetolos/aetolos --enable=opendkim
# ~/aetolos/aetolos --enable=opendmarc
# ~/aetolos/aetolos --enable=postfix
# ~/aetolos/aetolos --enable=spamassassin


Now we may re-run Aetolos with the status parameter to see the following table:

# ~/aetolos/aetolos --status
+---------+--------------+---------+------------+--------------+
| Enabled | Module       | SystemD | Repository | Dependencies |
+---------+--------------+---------+------------+--------------+
| [✓]     | apache       | dead    |            |              |
| [ ]     | clamav       |         | epel       | postfix      |
| [✓]     | dehydrated   |         |            |              |
| [✓]     | dovecot      | dead    |            |              |
| [ ]     | haproxy      |         |            |              |
| [ ]     | mariadb      |         |            |              |
| [ ]     | mtasts       |         |            | apache       |
| [ ]     | nsd          |         | epel       |              |
| [✓]     | opendkim     | dead    | epel       | postfix      |
| [✓]     | opendmarc    | dead    | epel       | postfix      |
| [✓]     | php          | dead    |            |              |
| [✓]     | postfix      | dead    |            |              |
| [ ]     | postgrey     |         | epel       | postfix      |
| [✓]     | spamassassin | dead    |            | postfix      |
| [✓]     | virtualhost  |         |            |              |
+---------+--------------+---------+------------+--------------+


Note the SystemD column which shows most services as dead. This is not an issue, because we have not configured/started those services, yet.

Step 4 - Setup

Finally, we may run Aetolos setup to install the required RPM packages and generate all the configuration files. We prefer to see the verbose output with the extra --verbose parameter.


You will notice two warnings, the first warning is about the dehydrated script, which tells us to specify the registration email address for Let’s Encrypt and the second warning is about generating the DH parameters prime number, this procedure takes quite some time (10 minutes on average), so please take the time to read the rest of this article and prepare for what is to come.


# ~/aetolos/aetolos --verbose --setup
[DEBUG] Check running system
[DEBUG] Detected: AlmaLinux 8.7 (Stone Smilodon)
[DEBUG] Check system memory
[DEBUG] Detected: 460MiB
[DEBUG] Check CPU cores
[DEBUG] Detected: 2
[DEBUG] Check proxy
[DEBUG] Loading Aetolos configuration
[DEBUG] Starting operating system setup
[DEBUG] Checking repository dependencies
[DEBUG] Checking package dependencies
[DEBUG] Checking SELinux requirements
[DEBUG] SELinux: enable dovecot_home_etc_aetolos.pp
[DEBUG] SELinux: enable postfix_socket_clamav_aetolos.pp
[DEBUG] SELinux: enable spamd_resolv_aetolos.pp
[DEBUG] Checking certificate requirements
[DEBUG] Checking ACME client
[DEBUG] Downloading ACME client: dehydrated 0.7.1
[DEBUG] Installing ACME client under: /root/dehydrated/
[WARNING] ACME registration email set to: postmaster
[WARNING] Please change the ACME registration email by executing: '/root/aetolos/aetolos --verbose --module=dehydrated --registration-email=new-email'
[DEBUG] Checking module dependencies
[DEBUG] Save configuration: dehydrated
[DEBUG] Writing to file: /root/dehydrated/config
[DEBUG] Writing to file: /root/dehydrated/hook.sh
[DEBUG] Writing to file: /etc/pki/letsencrypt/domains.txt
[DEBUG] Writing to file: /etc/cron.daily/dehydrated.cron
[DEBUG] Save configuration: dovecot
[WARNING] Generating DH parameters. This is going to take a long time...
[DEBUG] Writing to file: /etc/dovecot/dovecot.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/10-auth.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/10-mail.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/10-master.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/10-ssl.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/15-lda.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/15-mailboxes.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/20-imap.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/20-pop3.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/90-acl.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/90-quota.conf
[DEBUG] Writing to file: /etc/dovecot/conf.d/auth-passwdfile.conf.ext
[DEBUG] Save configuration: opendkim
[DEBUG] Writing to file: /etc/sysconfig/opendkim
[DEBUG] Writing to file: /etc/opendkim.conf
[DEBUG] Save configuration: opendmarc
[DEBUG] Writing to file: /etc/cron.weekly/publicsuffix.cron
[DEBUG] Writing to file: /etc/opendmarc.conf
[DEBUG] Save configuration: php
[DEBUG] Writing to file: /etc/systemd/system/php-fpm.service.d/override.conf
[DEBUG] Writing to file: /etc/php.ini
[DEBUG] Writing to file: /etc/php.d/10-opcache.ini
[DEBUG] Save configuration: postfix
[DEBUG] Writing to file: /etc/postfix/virtual_mailbox_domains
[DEBUG] Writing to file: /etc/postfix/transport
[DEBUG] Writing to file: /etc/postfix/smtp_header_checks
[DEBUG] Writing to file: /etc/postfix/mime_header_checks
[DEBUG] Override template file: /root/aetolos/modules/el8/postfix/templates.d/maincf.tpl
[DEBUG] Writing to file: /etc/postfix/main.cf
[DEBUG] Writing to file: /etc/postfix/master.cf
[DEBUG] Save configuration: spamassassin
[DEBUG] Writing to file: /etc/mail/spamassassin/init.pre
[DEBUG] Writing to file: /etc/sysconfig/spamass-milter
[DEBUG] Writing to file: /etc/mail/spamassassin/local.cf
[DEBUG] Save configuration: virtualhost
[DEBUG] Writing to file: /etc/tmpfiles.d/hometmp.conf


As per the warning, we add an email address for Let’s Encrypt:

# ~/aetolos/aetolos --verbose --module=dehydrated [email protected]

Add virtual hosts

Now we are ready to add as many virtual hosts as we like (or as many as the server can handle). Before running any commands, we may take a second to think which virtual hosts will have a prefix subdomain, sometimes we may have a domain like example.tld with a www subdomain (www.example.tld), other times we may not want to use a prefix because we want our email addresses only at the top domain ([email protected] and not [email protected]).


As an example, we will add three virtual hosts, two of them without a subdomain:

# ~/aetolos/aetolos --verbose --module=virtualhost --add-virtualhost=example1.tld --no-prefix
[DEBUG] Verify domain: example1.tld
[DEBUG] Add virtual host: example1.tld
# ~/aetolos/aetolos --verbose --module=virtualhost --add-virtualhost=example2.tld --no-prefix
[DEBUG] Verify domain: example2.tld
[DEBUG] Add virtual host: example2.tld
# ~/aetolos/aetolos --verbose --module=virtualhost --add-virtualhost=example3.tld
[DEBUG] Verify domain: example3.tld
[DEBUG] Add virtual host: example3.tld


We can now list all virtual hosts:

# ~/aetolos/aetolos --module=virtualhost --list-virtualhosts
+----+---------------------+--------------+-------------+----------------+--------+--------------+
| ID | Timestamp           | Virtual host | System user | MariaDB prefix | Parked | Prefix alias |
+----+---------------------+--------------+-------------+----------------+--------+--------------+
| 1  | 2023-01-10 08:51:36 | example1.tld | example1tld | example1       | [ ]    |              |
| 2  | 2023-01-10 08:51:40 | example2.tld | example2tld | example2       | [ ]    |              |
| 3  | 2023-01-10 08:51:45 | example3.tld | example3tld | example3       | [ ]    | www          |
+----+---------------------+--------------+-------------+----------------+--------+--------------+


Each virtual host will “live” isolated within its own home directory, with its own user/group ownership, so lets take a look at our /home:

# ls -la /home
total 0
drwxr-xr-x.  5 root        root         63 Jan 20 10:51 .
dr-xr-xr-x. 17 root        root        224 Dec 21  2021 ..
drwx--x--x.  8 example1tld example1tld 159 Jan 10 08:51 example1tld
drwx--x--x.  8 example2tld example2tld 159 Jan 10 08:51 example2tld
drwx--x--x.  8 example3tld example3tld 159 Jan 10 08:51 example3tld

Setup

After we made modifications with the Aetolos command, we need to generate new configuration files for all the relevant services (Apache, Dovecot, Postfix, etc). Thus, we execute Aetolos setup one last time.

# ~/aetolos/aetolos --setup

TLS certificates

At this point all services will use the same self-signed certificate, thus it is now a good time to run dehydrated manually to gather all virtual host certificates. Since Aetolos uses the ACME protocol with domain validation via HTTP port 80, we first start our Apache server to service those requests.

# systemctl start httpd
# ~/dehydrated/dehydrated --cron --config /root/dehydrated/config --keep-going
# systemctl stop httpd
# ~/aetolos/aetolos --setup

Services

We may now enable and start all services.

# systemctl --now enable httpd [email protected] [email protected] [email protected] dovecot opendkim opendmarc postfix spamassassin
Created symlink /etc/systemd/system/multi-user.target.wants/[email protected] → /usr/lib/systemd/system/[email protected].
Created symlink /etc/systemd/system/multi-user.target.wants/[email protected] → /usr/lib/systemd/system/[email protected].
Created symlink /etc/systemd/system/multi-user.target.wants/[email protected] → /usr/lib/systemd/system/[email protected].

You will notice that each virtual host has its own Apache service, this is a feature provided by systemd and allows us to define the virtual hosts into separate systemd services. The --now parameter will start all services.

DNS setup

For optimal deliverability we need to setup at least SPF and DKIM records, optionally we may also setup DMARC. While the setup of these TXT records falls outside the scope of this guide, we should point out that the DKIM key is stored under /etc/opendkim/keys/default.txt. Aetolos has wiki pages with additional help:

Manage email addresses

Add new email

First create a text file with the password, it is better security to not pass the password as a command-line parameter. Then add the new email address, optionally set a quota in bytes:

# ~/aetolos/aetolos --module=dovecot --virtualhost=example1.tld --add-email=sales --password-file='password.txt' --quota=5000000000

Change password

To change the password of an existing email address, is pretty straight forward:

~/aetolos/aetolos --module=dovecot --virtualhost=example1.tld --modify-email=sales --password-file='password.txt'

Change quota

To change the quota use any value above zero in bytes, while a zero value will remove the quota limit:

~/aetolos/aetolos --module=dovecot --virtualhost=example1.tld --modify-email=sales --quota=0

Remove email

It is equally easy to remove an email address, just keep in mind that all stored emails and related files will be deleted:

~/aetolos/aetolos --module=dovecot --virtualhost=example1.tld --remove-email=info

List email addresses

It is possible to list all email addresses associated with a virtual host:

# ~/aetolos/aetolos --module=dovecot --virtualhost=example1.tld --list-emails
+-------+-------+-------+----------------------+
| Email | Quota | Usage | Last password change |
+-------+-------+-------+----------------------+
| sales | 0     |       | 10/10/23             |
+-------+-------+-------+----------------------+

The architecture

There should be special mention about the architecture used for storing the emails within dovecot. For security reasons, each virtual host is isolated within unix accounts stored under their own /home directory. Email accounts and their passwords are under the subdirectory etc and mail is stored under the subdirectory mail.

/home/example1tld                               -> virtual host home
/home/example1tld/etc                           -> virtual host + parked domains
/home/example1tld/etc/example1.tld/forwarders   -> forwarder addresses
/home/example1tld/etc/example1.tld/passwd       -> email addresses
/home/example1tld/etc/example1.tld/shadow       -> passwords
/home/example1tld/mail/example1.tld             -> email storage

Aetolos

At this point Aetolos has configured the server without any custom modifications, all changes are done on the default configuration files. We may delete Aetolos from the server and continue modifications manually or we may keep Aetolos in case we want to add more virtual hosts and/or email addresses in the future.

Credits

Article photo by Manuel Geissinger from pexels