Skip to content

Instantly share code, notes, and snippets.

@emotality
Last active November 9, 2022 13:35
Show Gist options
  • Save emotality/eba37a3be6530bdc8f45a792ff0a1275 to your computer and use it in GitHub Desktop.
Save emotality/eba37a3be6530bdc8f45a792ff0a1275 to your computer and use it in GitHub Desktop.
Ubuntu 18.04 Postfix with Dovecot mail server

Ubuntu 18.04 Mail Server

UPDATE: I will not be answering questions as this guide is outdated and just copied and pasted into a single page with improved order (not redoing steps or editing the same file over and over in different steps).

Please check out the original article which is being updated frequently:
LinuxBabe - Build Your Own Email Server on Ubuntu: Basic Postfix Setup

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:

$ dig -x example.com +short

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 -y
$ apt-get install postfix postfix-policyd-spf-python -y
$ apt-get install dovecot-core dovecot-imapd dovecot-lmtpd -y
$ apt-get install 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 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

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;
      }
}
$ mkdir /var/www/mail
$ 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

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        
tcp    0    0 0.0.0.0:25     0.0.0.0:*    LISTEN    23511/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 = %n

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:

auto = create

Example:

mailbox Trash {
    auto = create
    special_use = \Trash
}

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:<ip4_server_ip> ip6:<ip6_server_ip> ~all"

Note: ip6 is optional.

Test TXT DNS records:

$ dig example.com txt

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/your-domain.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
@CoFix
Copy link

CoFix commented Nov 5, 2020

First of all, thank you for such a useful document.
Can you include Roundcube upload in this document?

@emotality
Copy link
Author

First of all, thank you for such a useful document.
Can you include Roundcube upload in this document?

Hi @CoFix
Thanks! 😃 I've never installed Roundcube but I'll see what I can do when I get time one day 😊

@riyadhul
Copy link

hostname : example.com or mail.example.com ??

@LuigiMdg
Copy link

LuigiMdg commented Jan 5, 2022

@riyadhul what is not clear to you about this?
`...

mydomain = example.com

myhostname = mail.example.com

...

mydestination = $myhostname, $mydomain, localhost.localdomain, , localhost

...`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment