Steps were re-written from below links for faster setup.
This guide assumes you are using Ubuntu 18 and Nginx.
Part 1: Build Your Own Email Server on Ubuntu: Basic Postfix Setup - LinuxBabe
Part 2: Install Dovecot IMAP server on Ubuntu and Enable TLS Encryption
Part 3: How to Set up SPF and DKIM with Postfix on Ubuntu Server
Part 4: Creating DMARC Record to Protect Your Domain Name From Email Spoofing
Check hostname:
$ hostname -f
Change hostname:
$ hostnamectl set-hostname example.com
$ nano /etc/hostname # change to example.com
Change hosts:
$ nano /etc/hosts
127.0.1.1 example.com example
127.0.0.1 localhost
127.0.0.1 example.com
Check PTR record:
$ apt-get install dnsutils net-tools -y
$ dig -x example.com #-4 for IPV4 -6 for IPV6
Create user to send mails from:
$ adduser noreply # can be hello, info, sales etc
Open SMTP ports on firewall:
$ ufw allow smtp
Install mailutils, Postfix, Dovecot and Open DKIM:
$ apt-get update
$ apt-get install mailutils postfix postfix-policyd-spf-python dovecot-core dovecot-imapd dovecot-lmtpd opendkim opendkim-tools -y
$ apt autoremove
Edit Postfix config:
$ nano /etc/postfix/main.cf
Add mydomain
and update myhostname
& mydestination
:
...
mydomain = example.com
myhostname = mail.example.com
...
mydestination = $myhostname, $mydomain, localhost.localdomain, , localhost
...
Add the following lines at the end of the file:
mailbox_transport = lmtp:unix:private/dovecot-lmtp
smtputf8_enable = no
policyd-spf_time_limit = 3600
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
check_policy_service unix:private/policyd-spf
# Milter configuration
milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:/opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters
Save and close, then restart Postfix:
$ systemctl restart postfix
Create email aliases:
$ nano /etc/aliases
# See man 5 aliases for format
postmaster: root
root: noreply # <-- your mail username
Rebuild aliases:
$ newaliases
Open IMAP ports on firewall
$ ufw allow Nginx Full
$ ufw allow 587/tcp
$ ufw allow 465/tcp
$ ufw allow 143/tcp
$ ufw allow 993/tcp
Install Let’s Encrypt client (certbot):
$ apt install software-properties-common -y
$ add-apt-repository ppa:certbot/certbot
$ apt update
$ apt install certbot python3-certbot-nginx -y
§ apt upgrade -y
Create virtual host for mail.example.com
:
$ mkdir /var/www/mail
$ touch /etc/nginx/sites-available/mail
$ ln -s /etc/nginx/sites-available/mail /etc/nginx/sites-enabled/mail
$ nano /etc/nginx/sites-enabled/mail
server {
listen 80;
server_name mail.example.com;
root /var/www/mail;
location ~ /.well-known/acme-challenge {
allow all;
location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mail.example.com;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_ecdh_curve X448:secp521r1:secp384r1;
ssl_prefer_server_ciphers on;
keepalive_timeout 70;
sendfile on;
client_max_body_size 80m;
root /var/www/mail;
}
$ mkdir /var/www/mail
$ nano /var/www/mail/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$ chown www-data:www-data /var/www/mail -R
# replace www-data with your nginx user if it is different
Test then restart nginx:
$ nginx -t
# or
$ service nginx configtest
# if successful, restart
$ service nginx restart # or reload
Obtain Let’s Encrypt
SSL/TLS certificate:
$ certbot --nginx --agree-tos --redirect --hsts --staple-ocsp -d mail.example.com #-d more domains/subdomains
Configure Postfix
$ nano /etc/postfix/master.cf
Default code example (commented out):
...
#tlsproxy unix - - y - 0 tlsproxy
#submission inet n - y - - smtpd
# -o syslog_name=postfix/submission
# -o smtpd_tls_security_level=encrypt
...
UNcomment and ADD the following, so that it looks like this:
...
#tlsproxy unix - - y - 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_tls_auth_only=yes
# -o smtpd_reject_unlisted_recipient=no
# -o smtpd_client_restrictions=$mua_client_restrictions
# -o smtpd_helo_restrictions=$mua_helo_restrictions
# -o smtpd_sender_restrictions=$mua_sender_restrictions
-o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING
-o smtpd_tls_wrappermode=no
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
#smtps inet n - y - - smtpd
...
Microsoft outlook only supports submission over port 465. If you are going to use Microsoft outlook mail client, then you also need to enable submission service on port 465 by adding the following lines in the file:
smtps inet n - y - - 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_client_restrictions=$mua_client_restrictions
# -o smtpd_helo_restrictions=$mua_helo_restrictions
# -o smtpd_sender_restrictions=$mua_sender_restrictions
-o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
Add this at the end of the file:
policyd-spf unix - n n - 0 spawn
user=policyd-spf argv=/usr/bin/policyd-spf
Save and close, then edit the main postfix config:
$ nano /etc/postfix/main.cf
Change TLS parameters from:
# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
To the following:
# 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_security_level = encrypt
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1
smtpd_tls_loglevel = 1
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_use_tls=yes
smtp_tls_security_level = may
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1
smtp_tls_loglevel = 1
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
NOTE: Remember to change example.com to your folder name.
To check that folder, use: $ ls -la /etc/letsencrypt/live
Restart Postfix and verify success:
$ service postfix restart
$ netstat -lnpt
You should see these:
tcp 0 0 0.0.0.0:587 0.0.0.0:* LISTEN 23511/master
tcp 0 0 0.0.0.0:465 0.0.0.0:* LISTEN 23511/master
tcp6 0 0 :::465 :::* LISTEN 25191/master
tcp6 0 0 :::25 :::* LISTEN 852/sshd
tcp6 0 0 :::443 :::* LISTEN 16812/nginx: master
tcp6 0 0 :::993 :::* LISTEN 25070/dovecot
tcp6 0 0 :::587 :::* LISTEN 25191/master
tcp6 0 0 :::143 :::* LISTEN 25070/dovecot
tcp6 0 0 :::80 :::* LISTEN 16812/nginx: master
Configure Dovecot:
$ nano /etc/dovecot/dovecot.conf
Add the following line, then save and close:
protocols = imap lmtp
Add dovecot to the mail group:
$ adduser dovecot mail
Edit the auth config file:
$ nano /etc/dovecot/conf.d/10-auth.conf
Uncomment the following line:
disable_plaintext_auth = yes
Change username format to use full email address:
auth_username_format = %Ln
Support older email clients:
auth_mechanisms = plain login
Save and close, then edit the SSL/TLS config:
$ nano /etc/dovecot/conf.d/10-ssl.conf
Change yes
to required
ssl = required
Change SSL paths from:
ssl_cert = </etc/dovecot/private/dovecot.pem
ssl_key = </etc/dovecot/private/dovecot.key
To the following:
ssl_cert = </etc/letsencrypt/live/example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/example.com/privkey.pem
NOTE: Remember to change example.com to your folder name.
To check that folder, use: $ ls -la /etc/letsencrypt/live
Save and close, then edit the master config:
$ nano /etc/dovecot/conf.d/10-master.conf
Change service lmtp
section to the following:
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
Change service auth
section to the following:
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}
Save and close, then edit the mailbox config:
$ nano /etc/dovecot/conf.d/15-mailboxes.conf
To auto-create folders, add the following into every mailbox section except Junk:
auto = create
Example:
mailbox Trash {
auto = create
special_use = \Trash
}
Add this to the Junk folder:
auto = subscribe
Restart Postfix and Dovecot:
$ systemctl restart postfix dovecot
Create a TXT
record on your DNS for @
:
TXT @ 3600 "v=spf1 a mx ip4:publicip4 ip6:publicipv6 ~all"
Test TXT
DNS records:
$ dig example.com txt #-4 for IPV4 -6 for IPV6
Add Postfix user to opendkim group:
$ gpasswd -a postfix opendkim
Edit OpenDKIM config:
$ nano /etc/opendkim.conf
Uncomment the #Commonly-used options;
part and paste in this block below it:
# Commonly-used options; the commented-out versions show the defaults.
Canonicalization simple
Mode sv
SubDomains no
AutoRestart yes
AutoRestartRate 10/1M
Background yes
DNSTimeout 5
SignatureAlgorithm rsa-sha256
Add the following lines at the end of this file:
# Map domains in From addresses to keys used to sign messages
KeyTable refile:/etc/opendkim/key.table
SigningTable refile:/etc/opendkim/signing.table
# Hosts to ignore when verifying signatures
ExternalIgnoreList /etc/opendkim/trusted.hosts
# A set of internal hosts whose mail should be signed
InternalHosts /etc/opendkim/trusted.hosts
Save and close, then create a directory structure for OpenDKIM:
$ mkdir /etc/opendkim
$ mkdir /etc/opendkim/keys
$ chown -R opendkim:opendkim /etc/opendkim
$ chmod go-rw /etc/opendkim/keys
$ nano /etc/opendkim/signing.table
Add this line:
*@example.com default._domainkey.example.com
Save and close, then create the key table:
$ nano /etc/opendkim/key.table
Paste the following:
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
Save and close, then create the trusted hosts file:
$ nano /etc/opendkim/trusted.hosts
Add the following, then save and close:
127.0.0.1
localhost
*.example.com
Create a folder for your domain:
$ mkdir /etc/opendkim/keys/example.com
Generate OpenDKIM keys:
$ sudo opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -v
Make OpenDKIM the owner of the key:
$ chown opendkim:opendkim /etc/opendkim/keys/example.com/default.private
Display public key:
$ cat /etc/opendkim/keys/example.com/default.txt
Note: You have to remove the concatenation and make it a single string, example:
"v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkDG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr1dZwz3zuCy+5Fym2JjX5/dqtAlo6ejnn3bzr25xI2IYfNxBrVCqQ8N6qa0DKjlQ0jyskNQl/luHA97X3Fh6hbQEiIuPqQm1nNNbXIfZVTQDHzW1F1Bu6jMvu4SSS27jgtveSnHG4PK8jpYJePkead8BV9mg8jVNMYVM4kA4sN+Ux5pvFHUb5V+AUqx64bpR86sPOw6sX1ES/vOkCVvgx15qEYaGAPaZBXwkyJ/2qi1fNmsFTUOn+0rmkDeizCqVcDDdttdpPdujNwKpONuB+6x0MIvIcjbWl9vWDZZgp7kvKiBVXuGi/IBOkZtFdK/2HkvUw7IVZ5lqCji9ZIGllwIDAQAB"
Create a new TXT
DNS record with the above string:
TXT default._domainkey 3600 "v=DKIM1; h=sha256; k=rsa; p=<key>"
Test your key:
$ opendkim-testkey -d example.com -s default -vvv
Create a directory to hold the OpenDKIM socket file:
$ mkdir /var/spool/postfix/opendkim
$ chown opendkim:postfix /var/spool/postfix/opendkim
$ nano /etc/opendkim.conf
Replace:
Socket local:/var/run/opendkim/opendkim.sock
With the following:
Socket local:/var/spool/postfix/opendkim/opendkim.sock
Restart services:
$ systemctl restart opendkim postfix dovecot
Send test mail:
$ echo 'Testing mail server' | mail your-email@gmail.com -s 'Test subject' -r noreply
Install SpamAssassin
$ apt-get install spamassassin spamc
$ groupadd spamd
$ useradd -g spamd -s /bin/false -d /var/log/spamassassin spamd
$ mkdir /var/log/spamassassin
$ chown spamd:spamd /var/log/spamassassin
$ nano /etc/default/spamassassin
Edit the File:
# Change to one to enable spamd
ENABLED=1
# Options
# See man spamd for possible options. The -d option is automatically added.
# SpamAssassin uses a preforking model, so be careful! You need to
# make sure --max-children is not set to anything higher than 5,
# unless you know what you're doing.
OPTIONS="--create-prefs --max-children 5 --helper-home-dir --username spamd \
-H /var/log/spamassassin/ -s /var/log/spamassassin/spamd.log"
# Cronjob
# Set to anything but 0 to enable the cron job to automatically update
# spamassassin's rules on a nightly basis
CRON=1
Start, check Status and enable spamassassin
$ systemctl start spamassassin
$ systemctl status spamassassin
$ systemctl enable spamassassin.service
$ netstat -lnpt
You should see these:
tcp 0 0 127.0.0.1:783 0.0.0.0:* LISTEN 6004/perl
tcp6 0 0 ::1:783 :::* LISTEN 6004/perl
$ nano /etc/postfix/master.cf
add behind:
$ smtp inet n - - - - smtpd
this:
-o content_filter=spamassassin
and add at the end of the File:
spamassassin unix - n n - - pipe
user=spamd argv=/usr/bin/spamc -f -e
/usr/sbin/sendmail -oi -f ${sender} ${recipient}
$ systemctl restart postfix
Edit the local.cf
file.
$ nano /etc/spamassassin/local.cf
# Add *****SPAM***** to the Subject header of spam e-mails
#
# rewrite_header Subject *****SPAM*****
rewrite_header Subject [***** SPAM _SCORE_ *****]
# Set the threshold at which a message is considered spam (default: 5.0)
#
required_score 4.0
$ systemctl restart spamassassin
$ apt-get install -y dovecot-sieve dovecot-managesieved
$ nano /etc/dovecot/conf.d/20-lmtp.conf
Change at the end of the file to:
protocol lmtp {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins sieve
}
$ systemctl restart dovecot
$ netstat -lnpt
You should see these:
tcp 0 0 0.0.0.0:4190 0.0.0.0:* LISTEN 6831/dovecot
tcp6 0 0 :::4190 :::* LISTEN 6831/dovecot
$ mkdir /var/lib/dovecot/sieve/ && nano mkdir /var/lib/dovecot/sieve/default.sieve
require ["fileinto", "mailbox"];
if header :contains "X-Spam-Flag" "YES" {
fileinto :create "Spam";
}
$ sievec /var/lib/dovecot/sieve/default.sieve
$ chown -R dovecot:dovecot /var/lib/dovecot/sieve/