There are many reasons why would somebody want to host their own email server. There are many advantages for doing this but there are also many drawbacks.
Setting up your own mail server can certainly be a great learning experience filled with discoveries about different set of technologies, protocols and how stuff works in general.
Hosting your own email server can also be a huge pain in the ass, especially if you start to have problems with spamming but I won’t go too deep into pros or the cons since, if you’re already here, you probably read a thing or two and decided for yourself if you want to host your own email server or not. Since you’re here I suppose you have decided to do it. ;-)
Article index:
- [Prerequisites][1]
- [LetsEncrypt][2]
- [Install software][3]
- [Postfix][4]
- [Dovecot][5]
- [Amavisd][6]
- [Dovecot Pigeonhole][7]
[Prerequisites][8]
Some prerequisites for using this guide are:
- You have domain name registered and DNS servers which you can use to point some records to the domain name.
- You have server with root access and correct PTR (reverse DNS record) set up.
- You have pointed domain name or mail.domain name to this server.
- You have set up MX records correctly.
Example of good setup would be:
- example.com has MX pointed to mail.example.com
- mail.example.com points to IP 1.2.3.4
- IP address 1.2.3.4 has PTR record set up to point to mail.example.com
Also consider setting up SPF record for your domain name. It should look something like:
“v=spf1 a mx -all”
In this guide I will be using CentOS 7 system which uses SystemD and this guide assumes you are using the same, though with little bit of thinking you may adapt this guide for any Linux distribution.
This guide will primarily use following technologies:
- Postfix – MTA (mail transfer agent)
- Dovecot – POP and IMAP server
Besides those we will also set up SpamAssassin for spam filtering and issue SSL via LetsEncrypt to secure your connection to the server.
First we will start with enabling EPEL repository since we need some pieces of software that are not available in standard CentOS repositories.
To enable EPEL repository on your CentOS server use:
yum install -y epel-release.noarch
[LetsEncrypt SSL][8]
Next, we’ll want to prepare our SSL certificate so we don’t have to do it later. To do that we will use certbot utility. To install certbot use:
yum install -y certbot.noarch
Using it is quite simple and more options can be found on official documentation but we will use following command to issue SSL:
certbot certonly --standalone -d mail.example.com
You should be presented with following screen:
Type in your working email account address which can later be used to revoke certificate if needed. After that you will be presented with a screen to accept Terms of Service so hit Agree so we can carry on.
Hopefully everything went fine without any issues and you now have your SSL certificate. You should be greeted with message that goes like this:
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/mail.example.com/fullchain.pem. Your cert will
expire on 2016-12-25. To obtain a new or tweaked version of this
certificate in the future, simply run certbot again. To
non-interactively renew *all* of your certificates, run "certbot
renew"
- If you lose your account credentials, you can recover through
e-mails sent to youremail@example.com.
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
It is also wise to set up automatic certificate renewal. It is as easy as placing following line in your crontab:
0 12 * * * /bin/certbot renew --quiet
[Install software][8]
Now that we are done with that we can carry on with setting up our mail server. As I mentioned in the beginning of this post we will be using few components to achieve our goal so go ahead and install them all.
yum install -y postfix.x86_64 dovecot.x86_64 dovecot-pigeonhole.x86_64 spamassassin.x86_64 amavisd-new.noarch
Afet all of the software pieces are installed we can proceed with configuring them.
[Postfix][8]
First, it is turn on Postfix. I strongly recommend that we first backup all of the files we plan to change so we can restore them if needed later, so that will be our first step:
cd /etc/postfix
cp main.cf main.cf.backup
cp master.cf master.cf.backup
Next we will create user and group which will be used for storing email account directories:
useradd vpostfix -s /usr/sbin/nologin -d /var/empty/postfix
To find out UID (User ID) and GID (Group ID) of newly created user and group you can use:
grep vpostfix /etc/passwd
which should print something like (your UID and GID may vary):
vpostfix:x:1000:1000::/var/empty/postfix:/usr/sbin/nologin
Next step is to edit /etc/postfix/main.cf
file in your favorite text editor and ensure following values are in there:
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
home_mailbox = Maildir/
You will also need to add following lines at the end of your main.cf file:
# Virtual domain config
virtual_mailbox_domains = /etc/postfix/virtual_domains
virtual_mailbox_base = /var/mail/vhosts
# Replace UID and GID numbers with real ones
virtual_minimum_uid = 1000
virtual_uid_maps = static:1000
virtual_gid_maps = static:1000
virtual_alias_maps = hash:/etc/postfix/virtual
# TLS
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom
# Harden TLS (disables sslv3 and lower, uses strong ciphers)
smtpd_tls_ciphers = high
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_protocols = TLSv1.2,TLSv1.1,TLSv1,!SSLv3,!SSLv2
smtpd_tls_protocols = TLSv1.2,TLSv1.1,TLSv1,!SSLv3,!SSLv2
lmtp_tls_mandatory_protocols = TLSv1.2,TLSv1.1,TLSv1,!SSLv3,!SSLv2
lmtp_tls_protocols = TLSv1.2,TLSv1.1,TLSv1,!SSLv3,!SSLv2
smtp_tls_mandatory_protocols = TLSv1.2,TLSv1.1,TLSv1,!SSLv3,!SSLv2
smtp_tls_protocols = TLSv1.2,TLSv1.1,TLSv1,!SSLv3,!SSLv2
smtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA
# Needs to be generated with:
# openssl dhparam -out /etc/postfix/dhparams.pem 2048
# Can be more than config directive suggests (1024, 2048, 4096..)
smtpd_tls_dh1024_param_file = /etc/postfix/dhparams.pem
# Encrypt transport between servers
smtp_tls_security_level = may
smtp_tls_note_starttls_offer = yes
smtp_tls_loglevel = 1
# SASL
smtpd_sasl_type = dovecot
broken_sasl_auth_clients = yes
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
smtpd_relay_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
# This one is optional and it is used to change max message size limit (10MB by default)
message_size_limit = 51200000
Next, edit your /etc/postfix/master.cf
and make sure following lines are not commented out:
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
If you want to enable legacy smtps on port 465 for whatever reason you can also enable following lines:
smtps inet n - n - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
In next step we will list our domain names so Postfix knows for which domains will it accept email:
touch /etc/postfix/virtual_domains
List your domain names in there like:
# Each domain goes to a new line
example.com
example2.com
example3.com
Of course we need to save all of our messages somewhere so we will create email directory and ensure proper permissions are set on them:
mkdir -p /var/mail/vhosts
chgrp vpostfix /var/mail
chgrp -R vpostfix /var/mail
Next create folders for each of the domains you plan to use:
cd /var/mail/vhosts
mkdir example.com
mkdir example2.com
mkdir example3.com
cd ../
chown -R vpostfix:vpostfix vhosts/
Postfix will later create its file and directory structure automatically.
Every time you make changes to /etc/postfix/virtual
which contains virtual aliases you need to issue:
postmap /etc/postfix/virtual
There is also another file which is used for setting up local system aliases and every time you make changes to your local aliases file you need to issue:
postalias /etc/aliases
So if you haven’t already do that now.
[Dovecot][8]
Now it is turn on Dovecot. First we will backup dovecot configuration file as we did with Postfix configuration files:
cd /etc/dovecot/
cp dovecot.conf dovecot.conf.backup
Next edit dovecot.conf
file and ensure that following is enabled:
protocols = imap pop3 lmtp
Next switch to dovecot conf.d
directory and backup files which will be edited next:
cd /etc/dovecot/conf.d/
cp 10-auth.conf 10-auth.conf.backup
cp 10-logging.conf 10-logging.conf.backup
cp 10-mail.conf 10-mail.conf.backup
cp 10-master.conf 10-master.conf.backup
cp 10-ssl.conf 10-ssl.conf.backup
cp 15-lda.conf 15-lda.conf.backup
cp 90-sieve.conf 90-sieve.conf.backup
After you have backed up those files edit them and ensure they have following contents:
10-auth.conf
disable_plaintext_auth = yes
#!include auth-system.conf.ext
!include auth-passwdfile.conf.ext
10-logging.conf
log_path = /var/log/dovecot.log
auth_debug = no
verbose_ssl = no
10-mail.conf
mail_home = /var/mail/vhosts/%d/%n
mail_location = maildir:~
mail_uid = 1000 # These are the GID and UID numbers for vpostfix
mail_gid = 1000 # Don't just put these numbers here
mail_privileged_group = vpostfix
10-master.conf
unix_listener auth-userdb {
mode = 0600
user = postfix
group = postfix
}
# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
10-ssl.conf
ssl = yes
ssl_cert =
Remember that we changed something in 10-auth.conf
? Basically we commented the line #!include auth-system.conf.ext
and uncommented the !include auth-passwdfile.conf.ext
. Take a look at this file (/etc/dovecot/conf.d/auth-passwdfile.conf.ext
) and you will see:
passdb {
driver = passwd-file
args = scheme=CRYPT username_format=%u /etc/dovecot/users
}
userdb {
driver = passwd-file
args = username_format=%u /etc/dovecot/users
}
That means that our user name/password database will be in the file /etc/dovecot/users
. To generate a password with SHA512-CRYPT password scheme you can use
doveadm pw -s SHA512-CRYPT
You’ll be prompted to enter a password twice and the output will be similar to this.
doveadm pw -s SHA512-CRYPT
Enter new password:
Retype new password:
{SHA512-CRYPT}$6$DPxyK4EDwjw0OdY7$xcSqD4N4oE.2sOrYnDwVubVhh4ySHyQs6eZNLqrGUmrd65d6m/dZZCa8y4STv1MPJWsu3zbQ4iKdjUulB75.s1
If you want to use a different password scheme, take a look at official documentation.
So to define users and user password we need to generate hash of the password using above mentioned command and edit /etc/dovecot/users
to insert the password after the user name.
To create user@example.com
your entry needs to look something like:
user@example.com:$6$DPxyK4EDwjw0OdY7$xcSqD4N4oE.2sOrYnDwVubVhh4ySHyQs6eZNLqrGUmrd65d6m/dZZCa8y4STv1MPJWsu3zbQ4iKdjUulB75.s1::::
Don’t forget to add 4 colons after the password “::::
”.
With all set let’s test if email authentification works:
doveadm auth test -a /var/spool/postfix/private/auth user@example.com EmailTest
Hopefully it works, in that case you should get result similar to this:
passdb: user@example.com auth succeeded
extra fields:
user=user@example.com
You can also test IMAP or POP3 login via telnet. To install telnet you can use
yum install telnet.x86_64
Note that when logged in postfix will automatically create directory structure for the user so I definitely recommend this :-)
Usage is like:
telnet localhost 110
user user@example.com
pass EmailTest
stat
list
quit
telnet localhost 143
login user@example.com EmailTest
list “” “*”
logout
With that we should have finished setting up dovecot.
[Amavisd][8]
Next, it is turn on Amavisd which will be used as bridge between Postfix, and SpamAssassin.
Edit /etc/amavisd/amavisd.conf
and ensure it contains:
@bypass_virus_checks_maps = (1); # controls running of anti-virus code
# @bypass_spam_checks_maps = (1); # controls running of anti-spam code
# $bypass_decode_parts = 1; # controls running of decoders&dearchivers
$daemon_user = 'amavis'; # (no default; customary: vscan or amavis), -u
$daemon_group = 'amavis'; # (no default; customary: vscan or amavis), -g
$mydomain = 'example.com'; # a convenient default for other settings (change it)
$MYHOME = '/var/spool/amavis'; # a convenient default for other settings, -H
@local_domains_maps = ( [".$mydomain","example2.com"] ); # list of all domains
$myhostname = 'mail.example.com'; # must be a FQDN
I have commented out option “bypass_virus_checks_maps
" because we’re not setting up ClamAV for virus scanning since it requires more RAM. It is recommended to use server with at least 2GB of RAM when running ClamAV so I skipped this step entirely but if you want to know how to enable it please state so and I’ll make sure to update this article or to write a follow up.
Now, let’s tie everything together with Postfix. Edit /etc/postfix/master.cf
and add these lines at the end.
# Amavisd
amavisfeed unix - - n - 2 lmtp
-o lmtp_data_done_timeout=1200
-o lmtp_send_xforward_command=yes
127.0.0.1:10025 inet n - n - - smtpd
-o content_filter=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o smtpd_restriction_classes=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters,no_address_mappings
-o local_header_rewrite_clients=
-o smtpd_milters=
-o local_recipient_maps=
-o relay_recipient_maps=
Edit /etc/postfix/main.cf
and add this to the end:
# Amavisd
content_filter = amavisfeed:[127.0.0.1]:10024
SpamAssassin
Perform initial update of SpamAssassin:
sa-update
To update SpamAssassin definitions you can set up Cron job to do it
crontab -e
and place following entry in it:
1 0 * * * /bin/sa-update && /bin/systemctl restart spamassassin.service
This will update SpamAssassin first minute after midnight (every day) and restart SpamAssassin service after that.
Now, let’s restart all of the services:
systemctl restart postfix.service dovecot.service amavisd.service spamassassin.service
We should also enable them at boot:
systemctl enable postfix.service dovecot.service amavisd.service spamassassin.service
[Dovecot Pigeonhole][8]
OK, we have set up majority of things, next, we’ll need to set up email filtering. To do that we will use pigeonhole
You should have following files in your /etc/dovecot/conf.d/
folder already:
20-managesieve.conf
90-sieve-extprograms.conf
90-sieve.conf
Add this to the end of your /etc/postfix/main.cf
file:
# LMTP
virtual_transport = lmtp:unix:private/dovecot-lmtp
Then edit the following files located in /etc/dovecot/conf.d/
and make sure that it contains options as noted below (though some of them should already since we did that in postfix configuration section):
10-mail.conf
mail_home = /var/mail/vhosts/%d/%n
mail_location = maildir:~
mail_privileged_group = vpostfix
10-master.conf
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
15-lda.conf
recipient_delimiter = +
mail_plugins = $mail_plugins sieve
20-lmtp.conf
lmtp_save_to_detail_mailbox = yes
protocol lmtp {
# Space separated list of plugins to load (default is global mail_plugins).
postmaster_address = joe@whatsup.com
mail_plugins = $mail_plugins sieve
}
90-sieve.com
recipient_delimiter = +
If user directory still isn’t created, log in to that account first (with telnet via POP3 as explained in Dovecot section) so Postfix can set up directory structure for that account.
Now, go to the mail directory of one of the virtual users.
cd /var/mail/vhosts/example.com/user
and create file named .dovecot.sieve
(hidden file) with the following Sieve commands inside.
require "fileinto";
if header :comparator "i;ascii-casemap" :contains "Subject" "***Spam***" {
fileinto "Junk";
stop;
}
That basically means that if an e-mail arrives flagged with “*\*Spam**” in the subject (recognized as SPAM by SpamAssassin) it will move it to the “Junk” folder.
For this to work make sure you have Junk folder created. Folder needs to be named .Junk
in user email directory. For example like this:
/var/mail/vhosts/example.com/user/.Junk/
ensure following permissions are set:
ls -ld /var/mail/vhosts/example.com/user/.Junk/
drwx------ 5 vpostfix vpostfix 4096 Sep 26 18:34 /var/mail/vhosts/example.com/user/.Junk/
Now onto the real tests. To test if messages are correctly filtered as Junk we will be sending test SPAM email message while monitoring the logs.
In one terminal window start following command:
tail -f /var/log/maillog
and then send plain-text email with following contents:
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
You should see something like this in your email logs:
Sep 26 20:53:38 digitalocean amavis[3626]: (03626-02) Blocked SPAM {DiscardedInbound,Quarantined}, [2a00:1450:400c:c09::22e]:36535 [2a00:1450:400c:c09::22e] <something@gmail.com> -> <user@example>, Queue-ID: 1D744418B2, Message-ID: <CAGnadjHX9wfu-icP5mNAH1ME6RRZuYYc16FU-W+D5N2W3ODJTg@mail.gmail.com>, mail_id: xC78jb4x_e9j, Hits: 999.901, size: 2362, dkim_sd=20120113:gmail.com, 583 ms
Which means message was detected as spam and hopefully it should be sorted in Junk folder ;-)
I suggest rebooting your server and seeing if all services are up and running after that and if they’re working correctly.
Thank you!
At last, I should mention that this article is an evolved version of following article: #63 FreeBSD 10: postfix, dovecot, Roundcube, amavisd-new, spamassassin, clamav, pigeonhole. Big thanks to author of original article for breaking down things in a simple way.
This article will hopefully evolve over time and things like different webmail clients, OpenDKIM setup and similar stuff may be added to it or I may post a follow up article.