Configure self-hosted email server with Postfix

15 Apr 2020 | by Derek

In this setup, we setup a mail server using Postfix, Dovecot, and MySQL on Ubuntu.

Step 1: Install Packages

Run the following command to install the packages:

apt-get install postfix postfix-mysql dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql

When prompted:

  • Choose Internet Site
  • Enter in FQDN, eg. example.com

Step 2: Create MySQL database

mysqladmin -p create servermail
mysql> grant select on servermail.* to 'usermail'@'localhost' identified by 'mailpassword';
mysql> flush privileges;
mysql> connect servermail;
mysql> create table virtual_domains(
    id int not null auto_increment,
    name varchar(50) not null,
    primary key (id)
    ) engine=innodb default charset=utf8;
mysql> create table virtual_users(
    id int not null auto_increment,
    domain_id int not null,
    password varchar(106) not null,
    email varchar(120) 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;
mysql> create table virtual_aliases(
    id int not null auto_increment,
    domain_id int 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;

Adding Virtual Domains

mysql> insert into virtual_domains (name)
    values ('example.com');

Adding Virtual Users

mysql> insert into virtual_users (domain_id, password , email)
    values (1, encrypt('firstpassword', concat('$6$', substring(sha(rand()), -16))), 'email1@example.com');

Adding Virtual Aliases

mysql> insert into virtual_aliases (domain_id, source, destination)
    values (1, 'alias@example.com', 'email1@example.com');

Step 3: Configure Postfix

vi /etc/postfix/main.cf 

Ammend certificate details

# 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 
smtpd_tls_cert_file=/etc/ssl/certs/dovecot.pem
smtpd_tls_key_file=/etc/ssl/private/dovecot.pem
smtpd_use_tls=yes
smtpd_tls_auth_only = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination
mydestination = localhost
myhostname = hostname.example.com
virtual_transport = lmtp:unix:private/dovecot-lmtp
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

Create file, mysql-virtual-mailbox-domains.cf

vi /etc/postfix/mysql-virtual-mailbox-domains.cf
    
user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT 1 FROM virtual_domains WHERE name='%s'

Create file, mysql-virtual-mailbox-maps.cf

vi /etc/postfix/mysql-virtual-mailbox-maps.cf 
    
user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT 1 FROM virtual_users WHERE email='%s'

Create file, mysql-virtual-alias-maps.cf

vi /etc/postfix/mysql-virtual-alias-maps.cf

user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT destination FROM virtual_aliases WHERE source='%s'

Edit /etc/postfix/master.cf

Uncomment the following:

submission inet n       -       -       -       -       smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject

Step 4: Configure Dovecot

Edit configuration file /etc/dovecot/dovecot.conf

vi /etc/dovecot/dovecot.conf

Make sure the following is uncommented:

protocols = imap lmtp

Edit mail configuration file /etc/dovecot/conf.d/10-mail.conf

vi /etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:/var/mail/vhosts/%d/%n
mail_privileged_group = mail

Edit the auth file, /etc/dovecot/conf.d/10-auth.conf

disable_plaintext_auth = yes
auth_mechanisms = plain login
#!include auth-system.conf.ext

Create file, /etc/dovecot/conf.d/auth-sql.conf.ext

passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
} 

Edit the file /etc/dovecot/dovecot-sql.conf.ext with custom MySQL details:

vi /etc/dovecot/dovecot-sql.conf.ext
driver = mysql
connect = host=127.0.0.1 dbname=servermail user=usermail password=mailpassword
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

Edit /etc/dovecot/conf.d/10-master.conf

vi /etc/dovecot/conf.d/10-master.conf
    
    service imap-login {
      inet_listener imap {
        port = 0
      }
      inet_listener imaps {
        #port = 993
        #ssl = yes
      }
    
      # Number of connections to handle before starting a new process. Typically
      # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
      # is faster. 
      #service_count = 1
    
      # Number of processes to always keep waiting for more connections.
      #process_min_avail = 0
    
      # If you set service_count=0, you probably need to grow this.
      #vsz_limit = $default_vsz_limit
    }
    
    service pop3-login {
      inet_listener pop3 {
        #port = 110
      }
      inet_listener pop3s {
        #port = 995
        #ssl = yes
      }
    }
    
    service lmtp {
      unix_listener /var/spool/postfix/private/dovecot-lmtp {
            mode = 0600
        user = postfix
        group = postfix
        #mode = 0666
      }
    
      # Create inet listener only if you can't use the above UNIX socket
      #inet_listener lmtp {
        # Avoid making LMTP visible for the entire internet
        #address =
        #port = 
      #}
    }
    
    service imap {
      # Most of the memory goes to mmap()ing files. You may need to increase this
      # limit if you have huge mailboxes.
      #vsz_limit = $default_vsz_limit
    
      # Max. number of IMAP processes (connections)
      #process_limit = 1024
    }
    
    service pop3 {
      # Max. number of POP3 processes (connections)
      #process_limit = 1024
    }
    
    service auth {
      
    unix_listener /var/spool/postfix/private/auth {
      mode = 0666
      user = postfix
      group = postfix
      }
        
    unix_listener auth-userdb {
        mode = 0600
        user = vmail
        #group = 
      }
    
      # Auth process is run as this user.
      user = dovecot
    }
    
    service auth-worker {
      user = vmail
    }
    
    service dict {
      unix_listener dict {
        #mode = 0600
        #user = 
        #group = 
      }
    }

Edit /etc/dovecot/conf.d/10-ssl.conf

Make sure ssl is enabled:

ssl = required

Change path of ssl certificate files

ssl_cert = </etc/ssl/certs/dovecot.pem
ssl_key = </etc/ssl/private/dovecot.pem

File permissions

mkdir -p /var/mail/vhosts/example.com
groupadd -g 5000 vmail 
useradd -g vmail -u 5000 vmail -d /var/mail
chown -R vmail:vmail /var/mail
chown -R vmail:dovecot /etc/dovecot
chmod -R o-rwx /etc/dovecot

Step 5: Congratulations, you're done!

service dovecot restart
service postfix restart