Mail server with Postfix, Dovecot and virtual users on Ubuntu 20.04
Author: Paul Sueno
Created: 9/22/2021
Set up a mail server with virtual users. The backbone will be Postfix. Access to mail by Dovecot. Virtual users through PostgreSQL DB. Updated for Ubuntu 20.04.
A mail server allows you to send and receive emails. It requires an MTA, and we will use Postfix for this. To access the mail easily, an IMAP service will be set up. Dovecot will serve this role. Basic email servers on Linux deliver mail for local users only. But what we want instead is a custom set of email addresses for our registered domain. This is where virtual users comes into play. A PostgreSQL database will be used to set up the database of email addresses (or usernames) and passwords.
This tutorial will show you how to install the Postfix MTA server, Dovecot IMAP server and create PostgreSQL virtual user database on Ubuntu 20.04. We will be organizing/saving our emails on the server by Maildir format. You will need TLS certificates set up and PostgreSQL installed on your server already. If you've followed along with my other tutorials, then proceed.
If you haven't already:
- Install Ubuntu 20.04 Full Disk Encryption (LUKS) on the Cloud
- Set up TLS certificates via LEMP Stack
- Install PostgreSQL, PHP and phpPgAdmin
If you're looking for the older version, go to the Ubuntu 18.04 version of the blog.
Adjust DNS and firewall settings
The world wide web needs to know where to forward emails sent to your domain. You do this by setting your domain registrar mail mx records. Log into your domain registrar and create the following record type
, host
, value
and priority (if needed)
. Be sure to replace domain.com
with your own. For this tutorial, I will not be using ipv6 for my email server. I will still set up the AAAA record for other functionality.
- A Record, mail, [your ipv4]
- AAAA Record, mail, [your ipv6]
- MX Record, @, mail.domain.com, 10
You also need to create a reverse DNS pointer record (PTR). This is assigned through your cloud hosting service. Make sure your ipv4 PTR is set to mail.domain.com
, where you replace domain.com
with your own. I use Linode. Here's their howto for this.
While you go through the rest of this tutorial, the DNS changes should propagate throughout the inter web.
For the firewall, make sure to allow traffic for SMTP (25,465), IMAP (993) and POP3 (995). I opened both unencrypted and encrypted traffic for SMTP (receive/send email); and only encrypted traffic for IMAP/POP3 (read email).
We will be modifying the nftables
firewall via sudo nano /etc/nftables.conf
. Add the following lines to the end of chain services
after the SSH and web entries:
tcp dport {25,465} counter accept comment "smtp, smtps"
tcp dport {993,995} counter accept comment "imaps, pop3s"
Restart the firewall with command sudo nft -f /etc/nftables.conf
.
Create virtual email database
The basic email servers for Linux only recognize local users. Local users are the users that are allowed to log into your Ubuntu server. This is not the type of email server we want. Instead, we want to set up the mail server for a bunch of users (email addresses, e.g., name@domain.com
), who do not have access to our Ubuntu server. This is where virtual comes in. They are email users (virtual) but are not true Ubuntu users on our server. This assumes you have your own domain. Throughout this tutorial, you must replace domain.com
with the one you have registered.
Create the PostgreSQL user and database for the virtual users. The database and username will be the same name. Use your own password. I recommend sticking with letters and numbers. The password will be passed to other services on your server. Oftentimes the scripting language used between the services will require escaping symbols. To make things easier, just don't use symbols. I recommend saving a local file only accesible to you that stores the password, and then just use a long random string. Here's a simple command to generate a random string with 20 characters: cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1
. Be sure to actually type in the single-quote '
around your password in the command below. Please don't use password
as your password.
Go ahead and log into phpPgAdmin as a super user and click on SQL in the upper right corner. Type in the following commands in the pop-up window. Under server, keep it as PostgreSQL. Make sure schema is public
. OK to leave Database as --
. You must Execute
each line separately.
CREATE USER virtualmail WITH PASSWORD 'password';
CREATE DATABASE virtualmail;
GRANT ALL ON DATABASE virtualmail TO virtualmail;
To see if you set it up correctly, go to the main phpPgAdmin page and look for your database on the left panel. Click on it. On the main panel, click on Privileges and see that the user (or Role) you created is granted access to this database.
We want PostgreSQL to be able to create hashed and encrypted entries. If you would rather execute SQL commands by Ubuntu bash command line, then use this format. You can also execute the SQL command inside the quotes using the phpPgAdmin method. From here on out, I'll just put in SQL commands, and I'll let you decide which method to use.
sudo -i -u postgres psql -c "CREATE EXTENSION pgcrypto;"
Create tables. Every SQL database is a collection of tables. Before doing this, let's log out of phpPgAdmin super user; and log in again as virtualmail
user/role instead. If you happen to create the tables with sql_root
user, you will just have to make sure to grant privileges to virtualmail
later on. The first table we'll create is the email domain our server will be used for. We'll insert values into our tables later on. Make sure the phpPgAdmin SQL pop-up window has virtualmail
as the Database.
CREATE TABLE virtual_domains(
id SERIAL PRIMARY KEY,
name varchar(50) NOT NULL
);
Create the virtual users database. This is the main database that stores the email addresses and the hashed passwords. You can set whatever default quota storage you want. Here, I set it as 500M
. This is based on Dovecot's quota documentation, recommending use of b, k, M, G, T or % suffixes.
CREATE TABLE virtual_users(
id SERIAL PRIMARY KEY,
domain_id int NOT NULL,
email varchar(100) NOT NULL UNIQUE,
password varchar(250) NOT NULL,
quota varchar(20) DEFAULT '500M',
created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (domain_id) REFERENCES public.virtual_domains (id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
Let's add a column comment, so you see how this is done in PostgreSQL.
COMMENT ON COLUMN virtual_users.password IS 'This is hashed';
Aliases are important to point specific email addresses to one account. For example, let's assume you create your personal email address as me@domain.com
. You can point the source emails (e.g., postmaster@domain.com, webmaster@domain.com and abuse@domain.com) to the destination me@domain.com
.
CREATE TABLE virtual_aliases(
id SERIAL PRIMARY KEY,
domain_id int NOT NULL,
source varchar(100) NOT NULL,
destination varchar(100) NOT NULL,
FOREIGN KEY (domain_id) REFERENCES public.virtual_domains (id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
Now let's insert some values into our tables. We'll start with the domain.
INSERT INTO public.virtual_domains (name) VALUES ('domain.com');
For the email addresses, we will need to submit a password and then store the one-way hashed password. We will use a PHP script to output the hashed password. For password hashing, we will utilize SHA512 with a 16 character salt. The prefix $6$
signifies SHA512. We'll store the script in your home directory: nano ~/virtualemailpassword.php
. Be sure to use your own password, and make sure it is strong.
<?php
$stdin = fopen('php://stdin', 'r');
echo 'Password: ';
$pass = trim(fgets($stdin));
// Set up the salt
$i = 0;
$salt = '';
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$chars .= strtolower($chars);
$chars .= '0123456789';
$max = strlen($chars);
// Create the salt
while ($i < 16) {
$i = $i + 1;
$salt .= $chars[rand(0,$max - 1)];
}
// Create the one-way encrypted hash
$passHash = crypt($pass, '$6$rounds=5000$'.$salt);
echo $passHash;
echo PHP_EOL;
?>
Now let's get our hashed password. Run php -f ~/virtualemailpassword.php
. Copy the output, and replace [password-hash]
with this value when we insert our first virtual email in the database. Also create another [password-hash]
, which we will use for a generic users@domain.com
account. This account will receive the emails created by Ubuntu for users like www-data
.
INSERT INTO public.virtual_users
(domain_id,email,password)
VALUES
('1','me@domain.com','[password-hash]'),
('1','users@domain.com','[password-hash]');
Insert some aliases into the database. Also add your own [Ubuntu user]
account as an alias. That way you will receive any system alerts that may be sent to your [Ubuntu user]
account to your new email address.
INSERT INTO public.virtual_aliases
(domain_id,source,destination)
VALUES
('1', 'root', 'me@domain.com'),
('1', '[Ubuntu user]', 'me@domain.com'),
('1', 'postmaster@domain.com', 'me@domain.com'),
('1', 'webmaster@domain.com', 'me@domain.com'),
('1', 'abuse@domain.com', 'me@domain.com'),
('1', 'www-data', 'users@domain.com');
Now that the virtual email database is set up, let's install the mail server.
Install Postfix
To send and receive email, we need to install and set up a message transfer agent (or MTA). We will use Postfix.
Update package repositories and install postfix package. We'll include two additional packages. These will allow Postfix to interface with our PostgreSQL database and use Perl compatible regular expressions (or PCRE) to help prevent spam mail.
sudo apt update
sudo apt install postfix postfix-pgsql postfix-pcre mailutils
When the installer configuration pops up, select Internet Site
as our mail configuration. Your mail name should be your domain, as in domain.com
.
Back up Postfix configuration files and create our own configuration files.
sudo cp /etc/postfix/main.cf /etc/postfix/main.cf.bak
sudo cp /etc/postfix/master.cf /etc/postfix/master.cf.bak
sudo touch /etc/postfix/pgsql-virtual-mailbox-domains.cf
sudo touch /etc/postfix/pgsql-virtual-mailbox-maps.cf
sudo touch /etc/postfix/pgsql-virtual-alias-maps.cf
sudo touch /etc/postfix/pgsql-virtual-email2email.cf
Now it's time to customize Postfix.
Postfix - configuration file
The Postfix configuration file main.cf
is where we set all the options about our email server. We'll do it in chunks, so that it'll easier to follow along.
We want to use our Certbot Let's Encrypt TLS certificate to make sure our emails are sent encrypted. Unfortunately, we can only ensure connections to our server are encrypted. We cannot ensure other MTA's would also use TLS encrypted connections. Run sudo nano /etc/postfix/main.cf
. Look for # TLS parameters
and replace the lines in that paragraph with the lines below. Be sure to substitute the names and locations of your own certificates!!
# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/www.domain.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/www.domain.com/privkey.pem
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_tls_auth_only = yes
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtpd_tls_loglevel = 2
smtp_tls_loglevel = 2
smtpd_tls_received_header = yes
smtp_tls_CApath = /etc/ssl/certs
smtpd_tls_CApath = /etc/ssl/certs
Immediately following the above lines, I set which encryption versions and ciphers I allow and disable. I based the list on the blog at kruyt.org. I modified the lines to include TLSv1.3. I thought to disable TLSv1.1, because Chrome has it deprecated. However, it broke my system, and I didn't figure out where the breakdown was. After allowing TLSv1.1, it worked again. You can allow or disallow at your own risk.
# TLS cipher suites
smtpd_tls_protocols = TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3
smtp_tls_protocols = TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3
smtp_tls_ciphers = high
smtpd_tls_ciphers = high
smtpd_tls_mandatory_protocols = TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3
smtp_tls_mandatory_protocols = TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3
smtp_tls_mandatory_ciphers = high
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL
smtpd_tls_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL
smtp_tls_mandatory_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL
smtp_tls_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL
tls_preempt_cipherlist = yes
We will completely replace the last block of lines in main.cf
configuration file, e.g., all the lines after # See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
. Use the Postfix Configuration Parameters site for details on each line. Be sure to replace your own settings in the example below. This is especially the case for host.domain.com
and [my ipv4 address]
(which should be your public ip address in x.x.x.x given to you by your cloud hosting company). I do not use ipv6 for my email server.
# Basic configuration
myhostname = host.domain.com
myorigin = $mydomain
mydestination = localhost
mynetworks = 127.0.0.0/8 [::1]/128
relay_domains =
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = [your ipv4 address], 127.0.0.1
inet_protocols = ipv4
Mail handling settings will tell Postfix which senders to accept/reject (e.g., emails from), which recipients to accept/reject (e.g., emails to) and other accept/reject parameters. Header checks is a file of regular expression lines that I use to reject server name patterns that send me spam. reject_rbl_client
lines tell Postfix to look up the sender in blacklists set up by the servers named. The other settings help prevent sloppy spammers from getting to you (won't prvent more savvy ones); and ensure only people on your network and in your database are allowed to use your MTA.
If you allow everybody access to your MTA, your cloud service provider may kick you off and your mail server IP will likely get blacklisted.
# Mail handling
header_checks = pcre:/etc/postfix/pcre_headers
smtpd_client_restrictions =
permit_dnswl_client list.dnswl.org
check_client_access hash:/etc/postfix/client_access
check_sender_access hash:/etc/postfix/sender_email_access
reject_unknown_reverse_client_hostname
reject_rbl_client zen.spamhaus.org
reject_rbl_client bl.spamcop.net
reject_rbl_client dnsbl.sorbs.net
smtpd_helo_required = yes
smtpd_helo_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_invalid_helo_hostname
reject_non_fqdn_helo_hostname
smtpd_sender_restrictions =
permit_sasl_authenticated
reject_unknown_reverse_client_hostname
reject_unknown_sender_domain
reject_non_fqdn_sender
smtpd_recipient_restrictions =
reject_unknown_recipient_domain
reject_unauth_pipelining
permit_mynetworks
permit_sasl_authenticated
# Once the mail server is sending/receiving OK, can
# COMMENT out defer_unauth_destination and
# UNCOMMENT reject_unauth_destination
defer_unauth_destination
#reject_unauth_destination
smtpd_relay_restrictions =
permit_mynetworks
permit_sasl_authenticated
# Once the mail server is sending/receiving OK, can
# COMMENT out defer_unauth_destination and
# UNCOMMENT reject_unauth_destination
defer_unauth_destination
#reject_unauth_destination
smtpd_data_restrictions =
reject_unauth_pipelining
permit
Create some of the look up files we noted in our configuration file. We will modify these once we start getting spam emails. Hopefully you don't.
sudo touch /etc/postfix/pcre_headers
sudo touch /etc/postfix/client_access
sudo touch /etc/postfix/sender_email_access
sudo postmap /etc/postfix/client_access
sudo postmap /etc/postfix/sender_email_access
The new Postfix settings have to be reloaded.
sudo service postfix reload
Let's test sending an email. You can use the mail
command, but will need to install it first. Run sudo apt install mailutils
. Now let's test it out. Be sure to replace your own email address you currently use.
echo "Test email being sent." | mail -s "Test Email Subject" myemail@address.com
Next step is to tell Postfix to look up email addresses in our PostgreSQL database.
Postfix - virtual users
The following files tell Postfix to lookup specific query results in our PostgreSQL database. Be sure to replace your own PostgreSQL virtualmail user password.
Run sudo nano /etc/postfix/pgsql-virtual-mailbox-domains.cf
.
user = virtualmail
password = [password]
hosts = 127.0.0.1
dbname = virtualmail
query = SELECT 1 FROM virtual_domains WHERE name='%s'
Run sudo nano /etc/postfix/pgsql-virtual-mailbox-maps.cf
.
user = virtualmail
password = [password]
hosts = 127.0.0.1
dbname = virtualmail
query = SELECT 1 FROM virtual_users WHERE email='%s'
Run sudo nano /etc/postfix/pgsql-virtual-alias-maps.cf
.
user = virtualmail
password = [password]
hosts = 127.0.0.1
dbname = virtualmail
query = SELECT destination FROM virtual_aliases WHERE source='%s'
Run sudo nano /etc/postfix/pgsql-virtual-email2email.cf
.
user = virtualmail
password = [password]
hosts = 127.0.0.1
dbname = virtualmail
query = SELECT email FROM virtual_users WHERE email='%s'
Modify Postfix configuration file by running sudo nano /etc/postfix/main.cf
. Add these lines at the bottom of the file.
# Virtual domains, users and aliases
virtual_mailbox_domains = pgsql:/etc/postfix/pgsql-virtual-mailbox-domains.cf
virtual_mailbox_maps = pgsql:/etc/postfix/pgsql-virtual-mailbox-maps.cf
virtual_alias_maps =
pgsql:/etc/postfix/pgsql-virtual-alias-maps.cf
pgsql:/etc/postfix/pgsql-virtual-email2email.cf
Postfix now knows which virtual email users to serve.
Postfix - master process
The Postfix daemon processes control how Postfix is called and when. We'll modify the master process configuration file. Run sudo nano /etc/postfix/master.cf
.
Uncomment the following lines and make sure they read as the following. The lines should be 17-20.
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
Do the same for this line, should be line 23.
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
Do the same for the following lines, should be lines 28-32.
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
Do the same for this line, should be line 34.
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
Do the same for this line, should be line 39.
-o milter_macro_daemon_name=ORIGINATING
Reload the Postfix configuration settings by running sudo service postfix reload
.
Dovecot - IMAP service
IMAP is a way for us to access our emails remotely. It is separate from MTA. We will use Dovecot as our IMAP service.
Install Dovecot package and other related packages.
sudo apt install dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-pgsql
Back up the files we will be editing.
sudo cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.bak
sudo cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.bak
sudo cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.bak
sudo cp /etc/dovecot/conf.d/auth-sql.conf.ext /etc/dovecot/conf.d/auth-sql.conf.ext.bak
sudo cp /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/dovecot-sql.conf.ext.bak
sudo cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.bak
sudo cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.bak
sudo cp /etc/dovecot/conf.d/15-lda.conf /etc/dovecot/conf.d/15-lda.conf.bak
sudo cp /etc/dovecot/conf.d/15-mailboxes.conf /etc/dovecot/conf.d/15-mailboxes.conf.bak
sudo cp /etc/dovecot/conf.d/20-imap.conf /etc/dovecot/conf.d/20-imap.conf.bak
In my prior blog for Ubuntu 18.04, I showed how to encrypt your users' emails. If you are so inclined to do so, please refer to that older blog. You would add more lines starting here.
Modify the Dovecot conifguration file by running sudo nano /etc/dovecot/dovecot.conf
. Add these lines at the bottom. These allow the encyrption and quota modules.
plugin {
quota = maildir:User quota
quota_rule = *:storage=10M
quota_rule2 = Trash:storage=+10%%
quota_warning = storage=95%% quota-warning 95 %u
quota_warning2 = storage=80%% quota-warning 80 %u
quota_warning3 = -storage=100%% quota-warning below %u # user is no longer over quota
}
service quota-warning {
executable = script /usr/local/bin/quota-warning.sh
# use some unprivileged user for executing the quota warnings
user = vmail
unix_listener quota-warning {
}
}
Create the quota warning script by running sudo nano /usr/local/bin/quota-warning.sh
.
#!/bin/sh
PERCENT=$1
USER=$2
cat << EOF | /usr/lib/dovecot/dovecot-lda -d $USER -o "plugin/quota=maildir:User quota:noenforcing"
From: postmaster@domain.com
Subject: quota warning
Your mailbox is now $PERCENT% full.
EOF
Make the file executable by running sudo chmod 775 /usr/local/bin/quota-warning.sh
.
Run sudo nano /etc/dovecot/conf.d/10-mail.conf
.
[Change line, should be 30] mail_location = maildir:/var/mail/vhosts/%d/%n
[Change line, should be 218] mail_plugins = $mail_plugins quota
Let's create the Maildir directories and create the Ubuntu user and group vmail.
sudo mkdir -p /var/mail/vhosts/domain.com
sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 vmail -d /var/mail
sudo chown -R vmail:vmail /var/mail
Run sudo nano /etc/dovecot/conf.d/10-auth.conf
.
[uncomment line, should be 10] disable_plaintext_auth = yes
[change line, should be 100] auth_mechanisms = plain login
[comment out line, should be 122] #!include auth-system.conf.ext
[uncomment line, should be 123] !include auth-sql.conf.ext
Run sudo nano /etc/dovecot/conf.d/auth-sql.conf.ext
. When deleting contents of a file, be sure to read them first. The comments are very helpful for guidance and troubleshooting. You can always read the back ups we created above. Delete all the lines in this file, and replace them with the following.
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
driver = prefetch
}
userdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
Run sudo nano /etc/dovecot/dovecot-sql.conf.ext
. Delete the lines in the file, and replace them with below. Be mindful of your own settings.
driver = pgsql
connect = host=127.0.0.1 dbname=virtualmail user=virtualmail password=[password no quotes or brackets]
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email AS user, password, \
'vmail' AS userdb_uid, 'vmail' AS userdb_gid, \
CONCAT('/var/mail/vhosts/domain.com/',SPLIT_PART(email,'@',1)) AS userdb_home, \
CONCAT('*:storage=', quota) AS userdb_quota_rule \
FROM virtual_users WHERE email='%u';
user_query = \
SELECT CONCAT('/var/mail/vhosts/domain.com/',SPLIT_PART(email,'@',1)) AS home, \
'vmail' AS uid, 'vmail' AS gid, CONCAT('*:storage=', quota) AS quota_rule \
FROM virtual_users WHERE email='%u';
iterate_query = SELECT SPLIT_PART(email,'@',1) AS username, \
SPLIT_PART(email,'@',2) AS domain \
FROM virtual_users;
Run sudo nano /etc/dovecot/conf.d/20-imap.conf
.
[change line, should be 93] mail_plugins = $mail_plugins imap_quota
Run sudo nano /etc/dovecot/conf.d/10-master.conf
. This tells Dovecot how to listen for connections and what to do with these connections. I will only use dovecot over encrypted connections. Read the file and know that the back up exists. Delete all the lines, and replace it with the following.
service imap-login {
inet_listener imap {
port = 0
}
inet_listener imaps {
port = 993
ssl = yes
}
}
service pop3-login {
inet_listener pop3 {
port = 0
}
inet_listener pop3s {
port = 995
ssl = yes
}
}
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
service imap {
}
service pop3 {
}
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
unix_listener auth-userdb {
mode = 0600
user = vmail
}
user = dovecot
}
service auth-worker {
user = vmail
}
service dict {
unix_listener dict {
}
}
Run sudo nano /etc/dovecot/conf.d/10-ssl.conf
. Be sure to replace it with your own settings.
[change line, should be 6] ssl = required
[change line, should be 12] ssl_cert = </etc/letsencrypt/live/www.domain.com/fullchain.pem
[change line, should be 13] ssl_key = </etc/letsencrypt/live/www.domain.com/privkey.pem
[change line, should be 53] ssl_dh = </etc/ssl/private/dhparam.pem
[change line, should be 57] ssl_min_protocol = TLSv1.1
[uncomment line, should be 60] ssl_cipher_list = ALL:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!LOW@STRENGTH
Run sudo nano /etc/dovecot/conf.d/15-lda.conf
. Be sure to replace @domain.com
with your own.
change line 7: postmaster_address = postmaster@domain.com
I want to have default folders populate for all my new email users. Run sudo nano /etc/dovecot/conf.d/15-mailboxes.conf
. Add or modify the following sections in namespace inbox { ... }
:
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
}
mailbox Archive {
auto = subscribe
special_use = \Archive
}
We need to tell Postfix to use Dovecot. Let's go back to the configuration file by running sudo nano /etc/postfix/main.cf
. Add these lines to the end of the file.
# Dovecot stuff
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain =
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
# Handing off local delivery to Dovecot's LMTP, and telling it where to store mail
virtual_transport = lmtp:unix:private/dovecot-lmtp
Change some ownership and permission settings. Then restart Postfix and Dovecot.
sudo chown -R vmail:dovecot /etc/dovecot
sudo chmod 770 /etc/dovecot
sudo service dovecot restart
sudo service postfix restart
That's it!! You now have your own email server. You can use any email client program like Thunderbird.
Fail2ban - harden server
You will be flooded with spam mail and nepharious agents trying to use your server as a relay agent for their badness. Here are some mitigation steps by using Fail2ban. Pay attention to when your Postfix server rejects mail. You can check by typing in cat /var/log/mail.log | grep reject
or gunzip -c /var/log/mail.0.gz | grep reject
. I noticed somebody always trying send spam from=<spameri@tiscali.it>
. I wanted to block this user, and you can block whichever host you want. I also wanted to block hosts trying to send email to my server but not on my domain. Be sure to replace domain\.com
with your own and whichever emailers you want specifically. For these cases, I wanted to ban rather quickly. I used another filter for cases that I was a little more permissive. We'll create the filters for each one.
Create a Fail2ban filter by running sudo nano /etc/fail2ban/filter.d/postfix-ban.conf
.
[Definition]
failregex = ^.*NOQUEUE: reject: RCPT from [\w\.\:\-\_]+\[<HOST>\]: .* from=<spameri@tiscali.it>
^.*from [\w\.\:\-\_]+\[<HOST>\]:.*to=<[\w\.\-\_]+\@((?!domain\.com).)*$
ignoreregex =
For the more permissive filter run sudo nano /etc/fail2ban/filter.d/postfix-permissive.conf
.
[Definition]
failregex = ^.*NOQUEUE: reject: RCPT from [\w\.\:\-\_]+\[<HOST>\]: [\w\.\<\@\> ]+: Relay access denied;
^.*warning: hostname [\w\.\:\-\_]+ does not resolve to address <HOST>: Name or service not known
^.*warning: [\w]*\[<HOST>\]: SASL LOGIN authentication failed:
^.*postfix\/smtpd\[[\d]+\]: connect from unknown\[<HOST>\]
ignoreregex =
Now that we've created the filters, we have to create the jails. For postfix-ban
, these are bad actors, and I don't want them access to any of my network. I'll ban for a week, and I'll allow Fail2ban to find one instance for up to a day at a time. Set this up by running sudo nano /etc/fail2ban/jail.d/postfix-ban.conf
:
[postfix-ban]
enabled = true
logpath = /var/log/mail.log
banaction = nftables-allports
bantime = 518400 ; 6 days
findtime = 86400 ; 1 day
maxretry = 1
protocol = 0-255
For the more permissive ban, I'll have Fail2ban give them up to 3 chances within a day. Afterwards they'll be banned for 2 days. Run sudo nano /etc/fail2ban/jail.d/postfix-permissive.conf
:
[postfix-permissive]
enabled = true
logpath = /var/log/mail.log
banaction = nftables-allports
bantime = 172800 ; 48 hr
findtime = 86400 ; 24 hr
maxretry = 3
protocol = 0-255
Similarly, we do not want people logging into our mail server from the wrong domains. This will be done through Dovecot filter and jail. To set up the filter, run sudo nano /etc/fail2ban/filter.d/dovecot-ban.conf
. Be sure to replace domain\.com
with your own.
[Definition]
failregex = ^.*(?:imap|pop3)-login:.*user=(?:<[\w\.\-\_]+((?!@domain\.com>).)*>|<>),.*rip=<HOST>,.*$
ignoreregex =
For the Dovecot jail, I didn't want to be overly strict. I know I can easily type in the wrong email address when logging in. So we will be much more permissive than for Postfix. Run sudo nano /etc/fail2ban/jail.d/dovecot-ban.conf
:
[dovecot-ban]
enabled = true
logpath = /var/log/mail.log
banaction = nftables-multiport
bantime = 14400 ; 4 hr
findtime = 14400 ; 4 hr
maxretry = 3
port = pop3,pop3s,imap,imaps,submission,465,sieve
Restart Fail2ban by running sudo service fail2ban restart
. You can see if your Fail2ban jails are set up by running cat /var/log/fail2ban
.
Lets Encrypt - renewal hooks
When Let's Encrypt renews our certificate (about every 90 days), we need to make sure all our services are reset. If this isn't done, the web, mail and IMAP servers will fail because they don't know that the TLS certificates have renewed. We will create a bash script and have it run as a renewal hook
.
Let's start by creating the renewal script. Run sudo nano /etc/letsencrypt/renewal-hooks/post/servers-reload.sh
.
#!/bin/sh
service nginx reload
service postfix reload
service dovecot reload
Make this executable by running sudo chmod 774 /etc/letsencrypt/renewal-hooks/post/servers-reload.sh
.
Up next - apps and verification
Now that we have an email server and, more importantly, a method to authenticate users, our server can host web-based software as a service (SaaS) apps. We can create a webmail app and show you how to tell the world your email system is verified by SPF, DKIM and DMARC.
- Install LEMP Stack on Ubuntu 20.04 LTS
- Bind9 caching DNS server on Ubuntu 20.04 LTS or Ubuntu 18.04 LTS
- Install LEMP Stack on Ubuntu 18.04 LTS