How to Build Email Server with Exim on Alma Linux 9
Tags: email, exim, almalinux
Published: 2024-04-20 07:59:22

## Cloud Provider Consideration
Before we continue, be mindful when choosing the cloud provider in which your email server will be launched. Not all cloud providers open the email-sending port, e.g., 25, hence blocking your email-sending capabilities. You will need to request them to open the port, and they will ask for some information before they can approve your request. Some providers I have known like these close their email-sending port:
- Digital Ocean
- Vultr
- Linode
- AWS

You can host your email server at Contabo or Kamatera or even build it on your on-premise server.

## Server Consideration
Installing from a new server is highly recommended, as it will avoid any unnecessary dependency conflict.


## Screen Installation

First, we will install the `screen` package. The package is helpful if we get disconnected during the build session, we can reconnect to the session and continue what we left off.
```bash
sudo dnf install -y epel-release
sudo dnf install -y screen
```

Now, start a screen session.
```bash
screen -U -S email-server-build
```
Later, when you get disconnected from the ssh session, run this command to resume the session:
```bash
screen -R
```
To list active screen sessions, run this command:
```bash
screen -ls
```
To terminate current screen session, run:
```bash
exit
```

## Setup Hostname
We will set up a hostname to identify the server uniquely.
```bash
hostnamectl set-hostname <server_name>
```
For example:
```bash
hostnamectl set-hostname mail.domain.com
```
Then, verify the change by running this command:
```bash
hostname
```
The output should look like this:
```
mail.domain.com
```
Next, update the hosts file:
```bash
vim /etc/hosts
```
Change an entry like this:
```bash
x.x.x.x vmi123456.server.net vmi123456
```
Note that, `x.x.x.x` is the server's IP address.
To this:
```bash
x.x.x.x vmi123456.server.net vmi123456 mail.domain.com
```

Reboot the server to apply the changes.
```bash
shutdown -r now
```

## System Update and Dependencies Installation

We need to update the existing packages.
```bash
sudo dnf update
```

Then, install vim as our text editor. You can install your own preferred text editor, e.g. nano. And, install `tar` and `socat` as they are required by `acme.sh`.
```bash
sudo dnf install vim tar socat -y
```

## Acme.sh Installation

Next, we will install `acme.sh`, a command-line tool for managing SSL/TLS certificates. I prefer `acme.sh` over `certbot`, as it does not depend on the OS version. For more details about `acme.sh`, check its GitHub repo [here](https://github.com/acmesh-official/acme.sh).

Next, install `acme.sh`.
```bash
curl https://get.acme.sh | sh -s email=my@example.com
```
Note that change `my@example.com` accordingly.
Create an alias for the acme.sh command so we can run `acme.sh` directly without specifying its full path.
```bash
echo 'alias acme.sh="/root/.acme.sh/acme.sh"' >> ~/.bash_aliases
source ~/.bash_aliases
```
Before we issue an SSL certificate, we must configure the DNS record properly. I will use `mail.domain.com` in this tutorial, and its A and MX records has already been configured. Refer to the `DNS Record Configuration` section at the end of this article to get more details.

After that, let us start issuing a staging SSL certificate. This certificate cannot be used on production servers and is used primarily to test certificate generation. Note that generating a live LetsEncrypt certificate is limited to 50 per week, for more info check [this documentation](https://letsencrypt.org/docs/rate-limits/#:~:text=You%20can%20create%20a%20maximum,produced%20in%20each%20certificate%20request.). If it works, we can continue issuing a live SSL certificate.

Before we issue the SSL certificate, let us create a folder to store the generated certificates.
```bash
mkdir -p /etc/pki/tls/certs/staging
mkdir -p /etc/pki/tls/certs/live
```

To issue a staging SSL certificate, run this command:
```bash
acme.sh --issue \
    -d "mail.domain.com" \
    --cert-home /etc/pki/tls/certs/staging \
    --standalone \
    --debug \
    --staging
```
Some notes:
- `-d`: the domain name we want to issue the certificate for
- `--cert-home`: the folder path where we want the generated certificate files to be stored.
- `--standalone`: this assumes we do not have a web server capable of serving web files, e.g. apache or nginx
- `debug`: display all information. Useful for tracing issues
- `staging`: generate a staging certificate.

If things go well, you will see an output like this:
```bash
[Sun Mar 24 04:08:19 UTC 2024] Your cert is in: /etc/pki/tls/certs/staging/mail.domain.com_ecc/mail.domain.com.cer
[Sun Mar 24 04:08:19 UTC 2024] Your cert key is in: /etc/pki/tls/certs/staging/mail.domain.com_ecc/mail.domain.com.key
[Sun Mar 24 04:08:19 UTC 2024] The intermediate CA cert is in: /etc/pki/tls/certs/staging/mail.domain.com_ecc/ca.cer
[Sun Mar 24 04:08:19 UTC 2024] And the full chain certs is there: /etc/pki/tls/certs/staging/mail.domain.com_ecc/fullchain.cer
[Sun Mar 24 04:08:19 UTC 2024] _on_issue_success
```

Now, let us issue a live certificate:
```bash
acme.sh --issue \
    -d "mail.domain.com" \
    --cert-home /etc/pki/tls/certs/live \
    --standalone \
    --debug
```
This time we removed the `--staging` parameter, and changed the certificate home folder.

The output will look like this:
```bash
[Sun Mar 24 04:35:12 UTC 2024] Your cert is in: /etc/pki/tls/certs/live/mail.mail.domain.com_ecc/mail.mail.domain.com.cer
[Sun Mar 24 04:35:12 UTC 2024] Your cert key is in: /etc/pki/tls/certs/live/mail.mail.domain.com_ecc/mail.mail.domain.com.key
[Sun Mar 24 04:35:12 UTC 2024] The intermediate CA cert is in: /etc/pki/tls/certs/live/mail.mail.domain.com_ecc/ca.cer
[Sun Mar 24 04:35:12 UTC 2024] And the full chain certs is there: /etc/pki/tls/certs/live/mail.mail.domain.com_ecc/fullchain.cer
[Sun Mar 24 04:35:12 UTC 2024] _on_issue_success
```
Now, we need to change the future SSL generation configured via crontab. Run this command:
```bash
EDITOR=vim crontab -e
```
Replace the following cron:
```bash
11 16 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
```
With:
```bash
11 16 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh --cert-home /etc/pki/tls/certs/live --standalone > /dev/null
```

## Configure Firewall
We will use firewalld to protect our email server. First, install the firewalld package.
```bash
sudo dnf install -y firewalld
```
Then, enable and start firewalld on server startup.
```bash
sudo systemctl enable firewalld
sudo systemctl start firewalld
```

Now, configure firewall rules.
```bash
sudo firewall-cmd --zone=public --permanent --add-service=http
sudo firewall-cmd --zone=public --permanent --add-service=https
sudo firewall-cmd --zone=public --add-service=pop3 --permanent
sudo firewall-cmd --zone=public --add-service=pop3s --permanent
sudo firewall-cmd --zone=public --add-service=smtp --permanent
sudo firewall-cmd --zone=public --add-service=smtps --permanent
sudo firewall-cmd --zone=public --add-service=imap --permanent
sudo firewall-cmd --zone=public --add-service=imaps --permanent
sudo firewall-cmd --reload
```
Validate the result by checking the configured firewall rules.
```bash
sudo firewall-cmd --zone=public --list-all
```
The output should look like this:

![Firewall Rule List](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7chddpnrar8iv16xvpr4.png)

## Exim Installation and Configuration
We will install Exim, a mail transfer agent used to deliver and receive emails.
```bash
sudo dnf install -y exim
```
Before configuring exim, we need to back up the existing config file.
```bash
cp -p /etc/exim/exim.conf{,.orig}
```
This will copy `exim.conf` to `exim.conf.orig`.

After that, we can safely configure exim.
```bash
vim /etc/exim/exim.conf
```
Adjust the existing exim configuration so they look like these:
```
primary_hostname = mail.domain.com
domainlist local_domains = @ : domain.com
tls_advertise_hosts = *
tls_certificate = /etc/pki/tls/certs/live/mail.domain.com_ecc/mail.domain.com.cer
tls_privatekey = /etc/pki/tls/certs/live/mail.domain.com_ecc/mail.domain.com.key
auth_advertise_hosts = *
```

Next, find `ROUTERS CONFIGURATION` section, and then find `localuser:` block.
![localuser block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h6ed2rmft7lnh1r2ebag.png)
Adjust the whole block so it will look like these:
```
localuser:
  driver = redirect
  check_local_user
  file_transport = local_delivery
  reply_transport = local_delivery_autoreply
  data = /home/${local_part_data}/Maildir
```

Next, find the `TRANSPORTS CONFIGURATION` section and then find `local_delivery:` block.

![local_delivery block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ctu7lsrqltxtx69674m0.png)

Adjust the whole block so it will look like this:
```
local_delivery:
  driver = appendfile
  directory = $home/Maildir
  maildir_format
  maildir_use_size_file
  delivery_date_add
  envelope_to_add
  return_path_add
```
And add this block right after the `local_delivery:` block.
```
local_delivery_autoreply:
   driver = autoreply
   headers = Content-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: base64
   to = ${sender_address}
   from = ${$local_part}@${$domain}
   debug_print = "T: auto reply for $local_part@$domain"
   once_file_size = 500K
   log = /var/log/exim/sieve_autoreply.log

```
The autoreply transport will generate a new email message as an automatic reply to the incoming message. It is useful to generate an automatic email when we are on a vacation or out-of-office. We will utilize this later in Roundcube. For more details on autoreply, refer to the doc [here](https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_autoreply_transport.html).

Next, find `AUTHENTICATION CONFIGURATION` section.

![Authentication Configuration section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/711m0rlg9uo4j1n5731o.png)

Then, add these blocks at the end of the section.
```
dovecot_login:
  driver = dovecot
  public_name = LOGIN
  server_socket = /var/run/dovecot/auth-client
  server_set_id = $auth1

dovecot_plain:
  driver = dovecot
  public_name = PLAIN
  server_socket = /var/run/dovecot/auth-client
  server_set_id = $auth1
```
Save all the changes and then quit.

Start exim service, and automatically start the service on server startup.
```bash
sudo systemctl start exim
sudo systemctl enable exim
sudo systemctl status exim
```

Test the `local_delivery` transport and ensure there is no warning or error message.
```bash
exim -d+all -bP transport local_delivery
```

Next, change the certificate folder's ownership so that Exim will be able to access the certificates later.
```bash
chown -R exim:exim /etc/pki/tls/certs/staging
chown -R exim:exim /etc/pki/tls/certs/live
```


## Dovecot Installation and Configuration
We will use `dovecot`, an open-source IMAP and POP3 server. It takes responsibility for connecting your email client (Thunderbird, etc.) to your mail box.

Run this command to install dovecot.
```bash
sudo dnf install -y dovecot
```

### SSL Configuration
Configure SSL in dovecot.
```bash
vim /etc/dovecot/conf.d/10-ssl.conf
```
Then adjust the existing config to this:
```
ssl = yes
ssl_cert = </etc/pki/tls/certs/live/mail.domain.com_ecc/fullchain.cer
ssl_key = </etc/pki/tls/certs/live/mail.domain.com_ecc/mail.domain.com.key
```
Note that, ensure the file paths are prefixed by `<`. Otherwise, it will trigger an error.

### Plain Authentication
Allow plain authentication in dovecot.
```bash
vim /etc/dovecot/conf.d/10-auth.conf
```
Adjust the existing config.
```
disable_plaintext_auth = no
auth_mechanisms = plain login
```

### Mailbox Location
Configure mailbox location.
```bash
vim /etc/dovecot/conf.d/10-mail.conf
```
Adjust the existing config.
```
mail_location = maildir:~/Maildir
```

### Allow Exim to Authenticate
Set up dovecot to allow Exim to use its authentication system.
```bash
vim /etc/dovecot/conf.d/10-master.conf
```
Find the `unix_listener auth-userdb` block.

![unix_listener auth-userdb block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r06uthr5karn2zqd08pn.png)
Then, fully comment the block, and add this block beneath it.
```
unix_listener auth-client {
    mode = 0660
    user = exim
}
```
The final changes should look like this:

![unix_listener auth-client](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sg18znexjlx96gkv4x2b.png)

Start dovecot service, and automatically start the service on server startup.
```bash
sudo systemctl start dovecot
sudo systemctl enable dovecot
sudo systemctl status dovecot
```

## Create an Email Address
We will create an email address, say email-test@domain.com.
```bash
useradd -m email-test
```
Set its password.
```bash
passwd email-test
```

Create info user (optional).
```bash
useradd -m info
passwd info
```

Note that, when sending an email to info@domain.com, you might encounter a problem, this is because info@domain.com is considered a root user. To fix the issue, remove it from the aliases list.
```bash
vim /etc/aliases
```
Then, comment this line by prefixing the line with `#`.
```
# info:          postmaster
```

Test the email user routing, and ensure there is no error or warning message.
```bash
exim -bt email-test@domain.com
```
The output should look like this:
```bash
email-test@domain.com -> /home/email-test/Maildir
  transport = local_delivery
```
And the other user.
```bash
exim -bt info@domain.com
```
The output should look like this:
```bash
info@domain.com -> /home/info/Maildir
  transport = local_delivery
```

## DNS Record Configuration
Before we can send and receive emails, ensure to have these records on your DNS Control Panel:
A record:
![A Record](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0cilzgkdagwl1zr45gk.png)
Change `1.2.3.4` with the IP Address of your email server.

MX record:
![MX Record](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xi6uvp64o5hbdt252c5u.png)
The MX record indicates it will point to your email server.