Setting Up Your Own Mail Server on Ubuntu

on Postfix, Ubuntu

It is far more professional to have an email address with your own domain name, e.g. firstname@surname.tld, rather than one offered by a free email provider. Sure, you could buy a custom email address from one of the several hosting companies out there, but how about setting up your own email server?

Whether you feel you're spending too much for your custom email address, or you're worried about your privacy, or just for the sake of it, it is worth considering having your own email server. You just need to either have an unused computer at home/office with a static IP address, or buy a cheap VPS from your favorite hosting provider.

In this tutorial, you will learn how to set up an email server on Ubuntu. The email server will be based on the following applications:

  • Postfix: A popular MTA (Mail Transfer Agent) used for sending and receiving emails.
  • Dovecot: An IMAP and POP3 server used by email clients, e.g. Mozilla Thunderbird, for transferring emails.
  • SQLite: A database system used for storing user mailbox accounts and other settings.

If you are planning to set up your mail server on a new VPS, I recommend that you read my post, Your First Steps with Your Brand New VPS Server, before following this tutorial. Also, you will need a SSL certificate for your mail server. If you don't have one yet, check my post on how to obtain a free Let's Encrypt certificate. You could also create your own custom self-signed certificate, which I don't recommend if you're new at this.

In the following configuration, it is assumed that your email server hostname is MAIL.DOMAIN.TLD and your email address will be USER@DOMAIN.TLD, so replace as appropriate.

The steps below have been successfully tested on Ubuntu 18.04.

Postfix

Install

$ apt install postfix postfix-sqlite

Before proceeding to configuring Postfix, create a new Unix user account for Postfix to use when writing to virtual mailbox files.

$ groupadd --system vmail -g 5000
$ useradd --system vmail -u 5000 -g 5000
$ mkdir /var/spool/mail/virtual
$ chown -R vmail:vmail /var/spool/mail/virtual && chmod 770 /var/spool/mail/virtual

And insert your mail server hostname in /etc/hosts and /etc/mailname.

$ nano /etc/hosts
YOUR_EXTERNAL_IP  MAIL.DOMAIN.TLD

$ nano /etc/mailname
MAIL.DOMAIN.TLD

Edit main.cf

$ nano /etc/postfix/main.cf
# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# General
myhostname = MAIL.DOMAIN.TLD
myorigin = /etc/mailname
mydestination =
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
mynetworks_style = host
append_dot_mydomain = no
local_recipient_maps =
readme_directory = no
compatibility_level = 2

# SMTPD parameters
smtpd_banner = $myhostname ESMTP $mail_name
biff = no
delay_warning_time = 0h
maximal_queue_lifetime = 1d
bounce_queue_lifetime = 1d
minimal_backoff_time = 1000s
maximal_backoff_time = 8000s
smtp_helo_timeout = 60s
smtpd_recipient_limit = 20
smtpd_soft_error_limit = 3
smtpd_hard_error_limit = 12

smtpd_helo_required = yes
smtpd_delay_reject = yes
disable_vrfy_command = yes
strict_rfc821_envelopes = yes

smtpd_client_restrictions =
 permit_mynetworks
 permit_sasl_authenticated
 reject_unknown_client_hostname
 reject_rbl_client bl.spamcop.net
 reject_rbl_client zen.spamhaus.org
 reject_rbl_client blackholes.easynet.nl
smtpd_helo_restrictions =
 permit_mynetworks
 permit_sasl_authenticated
 reject_invalid_helo_hostname
 reject_non_fqdn_helo_hostname
smtpd_sender_restrictions =
 permit_mynetworks
 permit_sasl_authenticated
 reject_non_fqdn_sender
 reject_unknown_sender_domain
 reject_unauth_pipelining
smtpd_recipient_restrictions =
 reject_non_fqdn_recipient
 reject_unknown_recipient_domain
 reject_unauth_pipelining
 permit_mynetworks
 permit_sasl_authenticated
 reject_unauth_destination
smtpd_data_restrictions =
 reject_unauth_pipelining

# Virtual domains setup
alias_maps = hash:/etc/postfix/aliases
alias_database = hash:/etc/postfix/aliases
virtual_mailbox_base = /var/spool/mail/virtual
virtual_mailbox_maps = sqlite:/etc/postfix/sqlite_mailbox.cf
virtual_alias_maps = sqlite:/etc/postfix/sqlite_alias.cf
virtual_mailbox_domains = sqlite:/etc/postfix/sqlite_domains.cf
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

# SASL parameters
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = no
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain =
smtpd_sasl_authenticated_header = no

# TLS parameters
smtpd_tls_cert_file = /PATH/TO/SSL_CERTIFICATE
smtpd_tls_key_file = /PATH/TO/SSL_CERTIFICATE_KEY
smtpd_tls_protocols = TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3
smtp_tls_protocols = TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3
smtp_tls_ciphers = high
smtpd_tls_ciphers = high
smtpd_tls_mandatory_protocols = TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3
smtp_tls_mandatory_protocols = TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3
smtp_tls_mandatory_ciphers = high
smtpd_tls_mandatory_ciphers = high
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

tls_random_source = dev:/dev/urandom
smtp_tls_note_starttls_offer = yes
smtpd_tls_auth_only = yes
smtpd_tls_loglevel = 0
smtpd_tls_received_header = yes

# Dovecot
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1

Replace /PATH/TO/SSL_CERTIFICATE and /PATH/TO/SSL_CERTIFICATE_KEY with your certificate paths.

Edit Postfix aliases

$ cp /etc/aliases /etc/postfix/aliases
$ nano /etc/postfix/aliases
root: USER@DOMAIN.TLD
postmaster: USER@DOMAIN.TLD
$ postalias /etc/postfix/aliases

Edit SQLite lookup files

$ nano /etc/postfix/sqlite_mailbox.cf
dbpath = /etc/postfix/vmail.sqlite
query = SELECT email FROM users WHERE email = '%s'
result_format = %d/%u/

$ nano /etc/postfix/sqlite_domains.cf
dbpath = /etc/postfix/vmail.sqlite
query = SELECT domain FROM domains WHERE domain = '%s'

$ nano /etc/postfix/sqlite_alias.cf
dbpath = /etc/postfix/vmail.sqlite
query = SELECT alias FROM aliases WHERE email = '%s'

Edit master.cf

$ nano /etc/postfix/master.cf
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_client_restrictions=permit_sasl_authenticated,reject
 -o smtpd_sasl_security_options=noanonymous,noplaintext
 -o smtpd_sasl_tls_security_options=noanonymous

dovecot unix - n n - - pipe
 flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -d $(recipient)

Dovecot

Install

$ apt install dovecot-imapd dovecot-sqlite

Edit Dovecot configuration

$ nano /etc/dovecot/conf.d/auth-sql.conf.ext
passdb {
 driver = sql
 args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
 driver = sql
 args = /etc/dovecot/dovecot-sql.conf.ext
}

$ nano /etc/dovecot/dovecot-sql.conf.ext
driver = sqlite
connect = /etc/postfix/vmail.sqlite
default_pass_scheme = SSHA512
password_query = SELECT password FROM users WHERE email = '%u'
user_query = SELECT '/var/spool/mail/virtual/%d/%n' AS home, \
5000 AS uid, 5000 AS gid, '*:storage=' || quota AS quota_rule \
FROM users WHERE email = '%u'

$ nano /etc/dovecot/conf.d/10-auth.conf
# Comment (#) all lines above starting with !include
disable_plaintext_auth = yes
auth_mechanisms = plain login
!include auth-sql.conf.ext

$ nano /etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:/var/spool/mail/virtual/%d/%n/Maildir
mail_uid = vmail
mail_gid = vmail
first_valid_uid = 5000
last_valid_uid = 5000

$ nano /etc/dovecot/conf.d/10-ssl.conf
ssl = required
ssl_cert = </PATH/TO/SSL_CERTIFICATE
ssl_key = </PATH/TO/SSL_CERTIFICATE_KEY

$ nano /etc/dovecot/conf.d/10-master.conf
service auth {
 unix_listener auth-userdb {
  mode = 0600
  user = vmail
  group = vmail
 }
 unix_listener /var/spool/postfix/private/auth {
  mode = 0660
  user = postfix
  group = postfix
 }
}

$ nano /etc/dovecot/conf.d/20-imap.conf
protocol imap {
 mail_plugins = quota imap_quota
}

$ nano /etc/dovecot/conf.d/90-quota.conf
plugin {
 quota = maildir:User quota
}

$ nano /etc/dovecot/conf.d/15-lda.conf
postmaster_address = postmaster@DOMAIN.TLD

$ chown -R vmail:dovecot /etc/dovecot && chmod -R o-rwx /etc/dovecot

Replace /PATH/TO/SSL_CERTIFICATE and /PATH/TO/SSL_CERTIFICATE_KEY with your certificate paths.

SQLite

Install

$ apt install sqlite3

Open a new SQLite database

$ sqlite3 /etc/postfix/vmail.sqlite

The sqlite3 command allows you to manually execute SQL statements against the specified SQLite database. If the database does not exist, it will be created.

  1. First, create tables for the users that will have a mailbox account, the domains that the mail server will handle, and the email aliases. Copy-paste the following commands in the SQLite shell, and press Enter:
    CREATE TABLE users (email TEXT PRIMARY KEY, password TEXT, quota INTEGER DEFAULT 0);
    CREATE TABLE domains (id INTEGER PRIMARY KEY, domain TEXT UNIQUE);
    CREATE TABLE aliases (id INTEGER PRIMARY KEY, email TEXT UNIQUE, alias TEXT);
    
  2. Assuming that your registered domain is DOMAIN.TLD, insert a new row in the domains table by copy-pasting the command below, and press Enter:
    INSERT INTO domains (domain) VALUES ('DOMAIN.TLD');
    
  3. Each user must have a password. It is always a good practice to hash passwords stored in a database, in case the database is compromised. If a user's password is MY_PASSWORD, you can hash it using Dovecot's password hash generator:
    $ doveadm pw -s SSHA512
    Enter new password:MY_PASSWORD
    Retype new password:MY_PASSWORD
    {SSHA512}HASHED_PASSWORD
    
  4. Assuming that you want to create a new mailbox account with email address USER@DOMAIN.TLD and a 2000000 KB quota, you should insert a new row in the users table as shown below. The user's password must be hashed, so make sure to replace HASHED_PASSWORD with the exact output of the command in Step 3:
    INSERT INTO users (email,password,quota) VALUES ('USER@DOMAIN.TLD','HASHED_PASSWORD', 2000000);
    
  5. You can also create aliases to forward messages from an email address to a mailbox account. The postmaster@DOMAIN.TLD alias is required for all email servers and it's also good to have an abuse@DOMAIN.TLD alias:
    INSERT INTO aliases (email,alias) VALUES ('postmaster@DOMAIN.TLD','USER@DOMAIN.TLD');
    INSERT INTO aliases (email,alias) VALUES ('abuse@DOMAIN.TLD','USER@DOMAIN.TLD');
    

Press Ctrl+D to exit the SQLite shell, and then change your database permissions so that it is only accessible by root:

$ chown root:root /etc/postfix/vmail.sqlite && chmod 600 /etc/postfix/vmail.sqlite

UFW (optional)

If you have followed my Your First Steps with Your Brand New VPS Server guide, you will probably need to open your SMTP and IMAP ports before continuing.

$ ufw allow 25
$ ufw allow 993
$ ufw allow 587

Restart Daemons

You should restart Postfix and Dovecot for changes to take effect.

$ service postfix restart
$ service dovecot restart

Mozilla Thunderbird

You should now be able to read and send emails from your server using an email client, such as Thunderbird. Create a new mail account using the settings below:

Incoming server

Server type: IMAP
Server name: MAIL.DOMAIN.TLD
Port: 993
User name: USER@DOMAIN.TLD
Connection security: SSL/TLS
Authentication method: Normal password

Outgoing server

Server name: MAIL.DOMAIN.TLD
Port: 587
User name: USER@DOMAIN.TLD
Connection security: STARTTLS
Authentication method: Normal password

In case of troubleshooting, you should check the log file at /var/log/mail.log to see if any errors have been reported by Postfix/Dovecot.

SPF (optional)

SPF (Sender Policy Framework) allows the recipients of your email to verify that your email server is authorized to send emails for your domain.

Enabling SPF for a domain is very easy, just attach a DNS TXT record on your entire domain (DOMAIN.TLD) with the value below. You should leave the TXT name field empty.

v=spf1 mx -all

The above line authorizes all servers mentioned in your DNS MX record to send emails for that domain.

DKIM (optional)

DKIM (DomainKeys Identified Mail) is a method for signing emails with a digital signature, which guarantees to the recipients of your email that the email was indeed sent by you and the contents have not been modified by a third party.

$ apt install opendkim opendkim-tools

Edit OpenDKIM configuration:

$ nano /etc/opendkim.conf
KeyTable           /etc/opendkim/key_table
SigningTable       /etc/opendkim/signing_table
ExternalIgnoreList /etc/opendkim/trusted_hosts
InternalHosts      /etc/opendkim/trusted_hosts
AutoRestart             Yes
AutoRestartRate         10/1h
Mode                    sv
SignatureAlgorithm      rsa-sha256
Canonicalization        relaxed/simple
UserID                  opendkim:opendkim
Socket                  inet:8891@localhost

$ nano /etc/default/opendkim
SOCKET=inet:8891@localhost

$ mkdir /etc/opendkim
$ nano /etc/opendkim/trusted_hosts
127.0.0.1

Generate a key pair for DOMAIN.TLD:

$ mkdir /etc/opendkim/DOMAIN.TLD
$ cd /etc/opendkim/DOMAIN.TLD
$ opendkim-genkey -s mail -d DOMAIN.TLD
$ chown opendkim:opendkim mail.private

$ nano /etc/opendkim/key_table
mail._domainkey.DOMAIN.TLD DOMAIN.TLD:mail:/etc/opendkim/DOMAIN.TLD/mail.private

$ nano /etc/opendkim/signing_table
DOMAIN.TLD mail._domainkey.DOMAIN.TLD

The public key for DOMAIN.TLD will be in /etc/opendkim/DOMAIN.TLD/mail.txt. You'll have to create a new DNS TXT record named mail._domainkey and copy-paste its contents (the part inside the parenthesis, including the quotes) in that record's value.

Edit main.cf:

$ nano /etc/postfix/main.cf
# DKIM
milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

Make sure to replace DOMAIN.TLD with the actual domain from which your emails are originating.

$ service opendkim restart
$ service postfix restart

That's it! You are now a postmaster! If you feel adventurous and you want to further customize your mail server, you can have a look at the Postfix and Dovecot documentations.

(Original publish date: September 10, 2015)