Skip to content

Instantly share code, notes, and snippets.

@steffenweb
Forked from emotality/ubuntu_18_mail_server.md
Last active June 9, 2021 18:53
Show Gist options
  • Save steffenweb/8070b5ddac7691b128ca09db5637f54c to your computer and use it in GitHub Desktop.
Save steffenweb/8070b5ddac7691b128ca09db5637f54c to your computer and use it in GitHub Desktop.
Ubuntu 18.04 Postfix with Dovecot mail server

Ubuntu 18.04 Mail Server

Overview

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

Prerequisites

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

Postfix

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

IMAP server

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 

Dovecot

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

SPF and DKIM Records

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/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment