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.
sudo dnf install -y epel-release
sudo dnf install -y screen
Now, start a screen session.
screen -U -S email-server-build
Later, when you get disconnected from the ssh session, run this command to resume the session:
screen -R
To list active screen sessions, run this command:
screen -ls
To terminate current screen session, run:
exit
Setup Hostname
We will set up a hostname to identify the server uniquely.
hostnamectl set-hostname <server_name>
For example:
hostnamectl set-hostname mail.domain.com
Then, verify the change by running this command:
hostname
The output should look like this:
mail.domain.com
Next, update the hosts file:
vim /etc/hosts
Change an entry like this:
x.x.x.x vmi123456.server.net vmi123456
Note that, x.x.x.x
is the server's IP address.
To this:
x.x.x.x vmi123456.server.net vmi123456 mail.domain.com
Reboot the server to apply the changes.
shutdown -r now
System Update and Dependencies Installation
We need to update the existing packages.
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
.
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.
Next, install acme.sh
.
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.
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. 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.
mkdir -p /etc/pki/tls/certs/staging
mkdir -p /etc/pki/tls/certs/live
To issue a staging SSL certificate, run this command:
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:
[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:
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:
[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:
EDITOR=vim crontab -e
Replace the following cron:
11 16 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
With:
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.
sudo dnf install -y firewalld
Then, enable and start firewalld on server startup.
sudo systemctl enable firewalld
sudo systemctl start firewalld
Now, configure firewall rules.
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.
sudo firewall-cmd --zone=public --list-all
The output should look like this:
Exim Installation and Configuration
We will install Exim, a mail transfer agent used to deliver and receive emails.
sudo dnf install -y exim
Before configuring exim, we need to back up the existing config file.
cp -p /etc/exim/exim.conf{,.orig}
This will copy exim.conf
to exim.conf.orig
.
After that, we can safely configure exim.
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.
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.
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.
Next, find AUTHENTICATION CONFIGURATION
section.
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.
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.
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.
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.
sudo dnf install -y dovecot
SSL Configuration
Configure SSL in dovecot.
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.
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.
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.
vim /etc/dovecot/conf.d/10-master.conf
Find the unix_listener auth-userdb
block.
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:
Start dovecot service, and automatically start the service on server startup.
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.
useradd -m email-test
Set its password.
passwd email-test
Create info user (optional).
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.
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.
exim -bt email-test@domain.com
The output should look like this:
email-test@domain.com -> /home/email-test/Maildir
transport = local_delivery
And the other user.
exim -bt info@domain.com
The output should look like this:
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:
Change 1.2.3.4
with the IP Address of your email server.
MX record:
The MX record indicates it will point to your email server.
Top comments (4)
Great job! What about setting it up for multiple domains on the same server?
My apologies for the late reply. I've been busy with work lately. I haven't tried it yet. But, you can try to change the domainlist and transport configurations. I'll try to write an article about this someday.
can you send me email or share your mail so i can share more details khnaz35 (@) gmail.com
Sure, will wait to see that day :)