I based this write-up off of this Linode guide.
Make sure the server's hosts file contains the server's public IP and is associated with it's FQDN and local hostname
127.0.0.1 localhost
192.252.0.6 mail.mydomain.com mail
Setup corresponding A and MX records
Install Certbot to use LetsEncrypt
apt install postfix postfix-mysql dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql mariadb-server
Postfix will prompt with the following:
Select ‘Internet Site’
Enter your domain
Run the secure installation
mysql_secure_installation
Login to MariaDB as the root user
mysql
Create a new DB
CREATE DATABASE mail;
Create a new user for the mail server to use
CREATE USER 'mailuser'@'127.0.0.1' IDENTIFIED BY '**********';
Grant select privileges on the new DB for the new user
GRANT SELECT ON mail.* TO 'mailuser'@'127.0.0.1';
Flush privileges
FLUSH PRIVILEGES;
Switch to the new mail DB
USE mail;
Create a table that will contain the domains that will receive mail
CREATE TABLE `virtual_domains` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Create a table that will contain all email address and their respective passwords
CREATE TABLE `virtual_users` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`password` varchar(106) NOT NULL,
`email` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Create a table for email aliases
CREATE TABLE `virtual_aliases` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
While still logged in to the MariaDB and using the DB ‘mail’. Repeat this command for each domain you would like to add.
INSERT INTO mail.virtual_domains (name) VALUES ('mydomain.com');
Verify the domain(s) was added
SELECT * FROM mail.virtual_domains;
From the Linux shell, hash the password for your first email address
doveadm pw -s SHA512-CRYPT -p "*********" -r 5000
Return to MariaDB, still using the DB ‘mail’. Repeat this command for each email address you would like to add.
INSERT INTO mail.virtual_users (domain_id, password , email) VALUES ('1', '$6$***********', 'user@mydomain.com');
Verify the email address(es) was added
SELECT * FROM mailserver.virtual_users;
While still logged in to the MariaDB and using the DB ‘mail’. Repeat this command for each alias you would like to add.
INSERT INTO mail.virtual_aliases (domain_id, source, destination) VALUES ('1', 'alias@mydomain.com', 'user@mydomain.com');
Verify the alias(es) was added
SELECT * FROM mail.virtual_aliases;
Make a copy of the default Postfix config, just to be safe
cp /etc/postfix/main.cf /etc/postfix/main.cf.orig
Open main.cf
and change every occurrence example.com to your domain and update the path to your SSL cert and key.
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
# Debian specific: Specifying a file name will cause the first
# line of that file to be used as the name. The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2
# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/example.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/example.com/privkey.pem
smtpd_use_tls=yes
smtpd_tls_auth_only = yes
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
# Authentication
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
# Restrictions
smtpd_helo_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_invalid_helo_hostname,
reject_non_fqdn_helo_hostname
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain,
reject_unlisted_recipient,
reject_unauth_destination
smtpd_sender_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_sender,
reject_unknown_sender_domain
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
defer_unauth_destination
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
myhostname = example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydomain = example.com
myorigin = $mydomain
mydestination = localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
# Handing off local delivery to Dovecot's LMTP, and telling it where to store mail
virtual_transport = lmtp:unix:private/dovecot-lmtp
# Virtual domains, users, and aliases
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf,
mysql:/etc/postfix/mysql-virtual-email2email.cf
# Even more Restrictions and MTA params
disable_vrfy_command = yes
strict_rfc821_envelopes = yes
#smtpd_etrn_restrictions = reject
#smtpd_reject_unlisted_sender = yes
#smtpd_reject_unlisted_recipient = yes
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtp_always_send_ehlo = yes
#smtpd_hard_error_limit = 1
smtpd_timeout = 30s
smtp_helo_timeout = 15s
smtp_rcpt_timeout = 15s
smtpd_recipient_limit = 40
minimal_backoff_time = 180s
maximal_backoff_time = 3h
# Reply Rejection Codes
invalid_hostname_reject_code = 550
non_fqdn_reject_code = 550
unknown_address_reject_code = 550
unknown_client_reject_code = 550
unknown_hostname_reject_code = 550
unverified_recipient_reject_code = 550
unverified_sender_reject_code = 550
main.cf defines the location of the virtual_mailbox_domains
, virtual_mailbox_maps
, and virtual_alias_maps
files. These files contain credentials and a query for mailbox domains, users, and aliases.
Create each file:
/etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_domains WHERE name='%s'
/etc/postfix/mysql-virtual-mailbox-maps.cf
user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_users WHERE email='%s'
/etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM virtual_aliases WHERE source='%s'
/etc/postfix/mysql-virtual-email2email.cf
user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT email FROM virtual_users WHERE email='%s'
Restart Postfix
systemctl restart postfix
Each one of these tests should return 1
Test virtual_domains
table
postmap -q mydomain.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
Test virtual_users
table
postmap -q user@mydomain.com mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
Test virtual_aliases
table
postmap -q user-alias@mydomain.com mysql:/etc/postfix/mysql-virtual-alias-maps.cf
Create a backup of the config
cp /etc/postfix/master.cf /etc/postfix/master.cf.orig
Edit your config to match the following, everything else can be left alone
#
# Postfix master process configuration file. For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n - n - - smtpd
#smtp inet n - - - 1 postscreen
#smtpd pass - - - - - smtpd
#dnsblog unix - - - - 0 dnsblog
#tlsproxy unix - - - - 0 tlsproxy
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - - - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
...
Tighten down permissions for the Postfix config dir
chmod -R o-rwx /etc/postfix
Restart Postfix
systemctl restart postfix
Edit /etc/dovecot/dovecot.conf
. Add the following config
!include_try /usr/share/dovecot/protocols.d/*.protocol
protocols = imap pop3 lmtp
postmaster_address = postmaster at example.com
Edit /etc/dovecot/conf.d/10-mail.conf
. Modify/add the following
...
mail_location = maildir:/var/mail/vhosts/%d/%n/
...
mail_privileged_group = mail
...
Create a vhosts
directory with a sub-directory for your domain(s)
mkdir -p /var/mail/vhosts/mydomain.com
Create the vmail
group with ID 5000
. Add a new user vmail
to the vmail
group. This system user will read mail from the server.
groupadd -g 5000 vmail
useradd -g vmail -u 5000 vmail -d /var/mail
Change ownership of of /var/mail
chown -R vmail:vmail /var/mail
Modify these parameters in /etc/dovecot/conf.d/10-auth.conf
...
disable_plaintext_auth = yes
...
auth_mechanisms = plain login
...
!include auth-system.conf.ext
...
!include auth-sql.conf.ext
...
Ensure /etc/dovecot/conf.d/auth-sql.conf.ext
contains the following lines
...
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
...
#userdb {
# driver = sql
# args = /etc/dovecot/dovecot-sql.conf.ext
#}
...
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}
...
Update the /etc/dovecot/dovecot-sql.conf.ext
file with your MySQL connection information. Make sure to uncomment the following parameters.
...
driver = mysql
...
connect = host=127.0.0.1 dbname=mailserver user=mailuser password=mailuserpass
...
default_pass_scheme = SHA512-CRYPT
...
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
...
Change the owner and group of the /etc/dovecot/
directory to vmail
and dovecot
chown -R vmail:dovecot /etc/dovecot
Change the permissions on the /etc/dovecot/
directory
chmod -R o-rwx /etc/dovecot
Edit the service settings file /etc/dovecot/conf.d/10-master.conf
Disable the unencrypted services by telling them to listen on port 0.
...
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
}
}
...
Find the service lmtp
section of the file and use the configuration shown below:
...
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
#mode = 0666i
mode = 0600
user = postfix
group = postfix
}
...
}
Locate service auth
and configure it as shown below:
...
service auth {
...
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
unix_listener auth-userdb {
mode = 0600
user = vmail
}
...
user = dovecot
}
...
In the service auth-worker
section, uncomment the user
line and set it to vmail
:
...
service auth-worker {
...
user = vmail
}
Edit /etc/dovecot/conf.d/10-ssl.conf
file to require SSL and to add the location of your domain’s SSL certificate and key. Replace example.com
with your domain:
...
# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt>
ssl = required
...
ssl_cert = </etc/letsencrypt/live/example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/example.com/privkey.pem
Restart Dovecot to enable the new config
systemctl restart dovecot
Install mailutils
apt install mailutils
Send a test email to an email address outside of your mail server, like a Gmail account. Replace email1@example.com
with an email address from your mail server:
echo "Email body text" | sudo mail -s "Email subject line" recipient@gmail.com -aFrom:email1@example.com
Verify that the gmail address received the test email.