6.2 KiB

name description version
email-server-setup Self-host email server (Postfix + Dovecot + DKIM) on Ubuntu. 1.0.0

Email Server Setup (Postfix + Dovecot + DKIM)

Self-hosted mail server on Ubuntu with virtual mailboxes, SASL authentication, DKIM signing, and IMAPS/SMTPS.

Architecture

Postfix (SMTP:25/465/587) → Dovecot SASL auth → passwd-file
                          → OpenDKIM milter (8891) → DKIM signing
Dovecot (IMAP:143/993, POP3:110/995) → Maildir /var/mail/vhosts/

Step 1: Install packages

export DEBIAN_FRONTEND=noninteractive
sudo debconf-set-selections <<< "postfix postfix/mailname string $DOMAIN"
sudo debconf-set-selections <<< "postfix postfix/main_mailer_type string 'Internet Site'"
sudo apt-get update -qq
sudo apt-get install -y -qq postfix postfix-pcre dovecot-core \
  dovecot-imapd dovecot-pop3d opendkim opendkim-tools certbot

Step 2: Create vmail user and directories

sudo groupadd -f vmail
sudo useradd -g vmail -d /var/mail -s /usr/sbin/nologin vmail
sudo mkdir -p /var/mail/vhosts/$DOMAIN
sudo chown -R vmail:vmail /var/mail
sudo chmod -R 700 /var/mail

Step 3: Configure Postfix

Key settings in /etc/postfix/main.cf (see references/postfix-main.cf for the full working config):

  • virtual_mailbox_domains — the domain(s) to serve
  • virtual_mailbox_base = /var/mail/vhosts
  • virtual_mailbox_maps = hash:/etc/postfix/vmailbox
  • SASL via Dovecot: smtpd_sasl_type = dovecot, socket at /var/spool/postfix/private/auth
  • TLS certs point to Let's Encrypt path (use self-signed as fallback)
  • DKIM milter: smtpd_milters = inet:localhost:8891

Create /etc/postfix/vmailbox:

user@domain.com    domain.com/user/

Hash it: sudo postmap /etc/postfix/vmailbox

Enable submission (587) and SMTPS (465) in master.cf

Uncomment the submission and smtps service blocks. Replace the $mua_client_restrictions, $mua_helo_restrictions, $mua_sender_restrictions variables with concrete values — Postfix does not define them by default and postfix check will warn endlessly.

Step 4: Configure Dovecot

Mail location (/etc/dovecot/conf.d/10-mail.conf)

mail_location = maildir:/var/mail/vhosts/%d/%n

Authentication (/etc/dovecot/conf.d/10-auth.conf)

Disable system auth, enable passwd-file:

#!include auth-system.conf.ext
!include auth-passwdfile.conf.ext

Update /etc/dovecot/conf.d/auth-passwdfile.conf.ext:

passdb {
  driver = passwd-file
  args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/users
}
userdb {
  driver = passwd-file
  args = username_format=%u /etc/dovecot/users
}

Postfix SASL socket (/etc/dovecot/conf.d/10-master.conf)

Uncomment inside service auth { }:

unix_listener /var/spool/postfix/private/auth {
  mode = 0666
}

Create users

HASH=$(doveadm pw -s SHA512-CRYPT -p 'password')
echo "user@domain.com:$HASH::$(id -u vmail):$(id -g vmail)::/var/mail/vhosts/domain.com/user/::" \
  | sudo tee -a /etc/dovecot/users
sudo chmod 640 /etc/dovecot/users
sudo chown root:dovecot /etc/dovecot/users

Step 5: Configure DKIM

sudo mkdir -p /etc/opendkim/keys/$DOMAIN
sudo opendkim-genkey -D /etc/opendkim/keys/$DOMAIN -d $DOMAIN -s mail
sudo chown -R opendkim:opendkim /etc/opendkim/keys

/etc/opendkim.conf (see references/opendkim.conf):

Socket    inet:8891@localhost
KeyTable  file:/etc/opendkim/KeyTable
SigningTable file:/etc/opendkim/SigningTable

/etc/opendkim/KeyTable:

mail._domainkey.domain.com domain.com:mail:/etc/opendkim/keys/domain.com/mail.private

/etc/opendkim/SigningTable:

*@domain.com mail._domainkey.domain.com

Step 6: SSL certificates

Temporary: Self-signed

sudo mkdir -p /etc/letsencrypt/live/mail.$DOMAIN
sudo openssl req -new -x509 -days 365 -nodes \
  -subj "/CN=mail.$DOMAIN" \
  -out /etc/dovecot/private/dovecot.pem \
  -keyout /etc/dovecot/private/dovecot.key
sudo cp /etc/dovecot/private/dovecot.pem /etc/letsencrypt/live/mail.$DOMAIN/fullchain.pem
sudo cp /etc/dovecot/private/dovecot.key /etc/letsencrypt/live/mail.$DOMAIN/privkey.pem

Real: Let's Encrypt (after DNS resolves)

sudo certbot certonly --standalone -d mail.$DOMAIN --agree-tos --email admin@$DOMAIN

Step 7: DNS records

Required records (add at your DNS provider):

Type Host Value Priority
MX @ mail.domain.com 10
A mail server-ip
TXT @ v=spf1 mx ~all
TXT mail._domainkey (from /etc/opendkim/keys/domain.com/mail.txt)
TXT _dmarc v=DMARC1; p=none; rua=mailto:admin@domain.com

Also request PTR/reverse DNS from your hosting provider pointing your IP to mail.domain.com — critical for deliverability.

Step 8: Start and verify

sudo systemctl restart dovecot postfix opendkim
sudo doveadm auth test user@domain.com 'password'   # Must say "succeeded"
echo "test" | sudo /usr/sbin/sendmail user@domain.com  # Local delivery
sudo ss -tlnp | grep -E ':(25|110|143|465|587|993|995)'

Pitfalls

  1. Postfix mua_* warnings — the submission/smtps blocks in master.cf reference $mua_client_restrictions etc. which are not defined by default. Replace them with concrete values or define them in main.cf. Otherwise postfix check produces hundreds of warnings.

  2. OpenDKIM restart loop — OpenDKIM may fail to write its PID file to /run/opendkim/ due to permissions. This causes systemd to restart it repeatedly. The process still starts and binds port 8891; check with ss -tlnp | grep 8891 rather than relying on systemd status alone.

  3. Dovecot ssl = yes line — on Ubuntu 22.04, the default 10-ssl.conf has ssl = yes already uncommented. Verify before editing.

  4. Port 25 outbound — many cloud providers block outbound port 25 by default. Request unblocking from your provider for external delivery.

  5. /etc/dovecot/users permissions — must be 640 and owned by root:dovecot. Plain chmod 600 will cause auth failures.

  6. Maildir vs mbox — the default Dovecot config on Ubuntu uses mbox format (mbox:~/mail:INBOX=/var/mail/%u). You MUST change it to Maildir for virtual mailbox compatibility.