This guide builds a complete, production-ready self-hosted mail server from scratch. When you’re done, you’ll have:
- Postfix for SMTP (sending and receiving)
- Dovecot for IMAP/POP3 (mail retrieval)
- TLS encryption everywhere (Let’s Encrypt)
- SPF, DKIM, and DMARC configured
- Virtual mailboxes supporting multiple domains and users
Architecture Overview
Internet
│
▼
Postfix (port 25 — inbound SMTP)
Postfix (port 587 — submission, authenticated)
│
▼
Dovecot (delivers to Maildir)
│
▼
Mail clients (IMAP port 993 / POP3 port 995)
Prerequisites
- Fresh Ubuntu 22.04 VPS with at least 1 GB RAM
- A domain name:
example.com - Mail subdomain:
mail.example.comwith A record pointing to your server IP - Ports 25, 587, 993, 995 open in your firewall
Phase 1: System Preparation
sudo apt update && sudo apt upgrade -y
sudo hostnamectl set-hostname mail.example.com
Edit /etc/hosts:
YOUR_IP mail.example.com mail
Install prerequisites:
sudo apt install -y postfix postfix-mysql dovecot-core dovecot-imapd \
dovecot-pop3d dovecot-lmtpd dovecot-mysql opendkim opendkim-tools \
certbot mailutils
Select No Configuration when the Postfix dialog appears — we’ll configure manually.
Phase 2: TLS Certificate
sudo certbot certonly --standalone -d mail.example.com
Certificates are at /etc/letsencrypt/live/mail.example.com/.
Set up auto-renewal hook to reload services after cert renewal:
cat > /etc/letsencrypt/renewal-hooks/post/mail.sh << 'EOF'
#!/bin/bash
systemctl reload postfix dovecot
EOF
chmod +x /etc/letsencrypt/renewal-hooks/post/mail.sh
Phase 3: Postfix Configuration
main.cf
sudo nano /etc/postfix/main.cf
# Basic settings
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = all
# TLS inbound (smtpd)
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
# TLS outbound (smtp)
smtp_tls_security_level = may
smtp_tls_loglevel = 1
# SASL authentication via Dovecot
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
# Virtual mailboxes
virtual_mailbox_domains = example.com
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# Relay restrictions
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
# DKIM milter
milter_default_action = accept
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891
Virtual Mailbox Map
Create /etc/postfix/vmailbox:
user@example.com example.com/user/
admin@example.com example.com/admin/
Index it:
sudo postmap /etc/postfix/vmailbox
master.cf — Enable Submission (Port 587)
Edit /etc/postfix/master.cf and uncomment/add the submission service:
submission inet n - y - - 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
Create Virtual Mail Directory
sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 vmail -d /var/mail/vhosts
sudo mkdir -p /var/mail/vhosts/example.com
sudo chown -R vmail:vmail /var/mail/vhosts
Phase 4: Dovecot Configuration
10-mail.conf
mail_location = maildir:/var/mail/vhosts/%d/%n
mail_uid = vmail
mail_gid = vmail
10-auth.conf
disable_plaintext_auth = yes
auth_mechanisms = plain login
!include auth-static.conf.ext
Create /etc/dovecot/conf.d/auth-static.conf.ext:
passdb {
driver = static
args = password=changeme
}
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}
Note: For production, use a database-backed auth. This static config is for initial testing.
10-ssl.conf
ssl = required
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem
ssl_min_protocol = TLSv1.2
10-master.conf — LMTP and Auth Socket
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}
Phase 5: DKIM Setup
sudo mkdir -p /etc/opendkim/keys/example.com
sudo opendkim-genkey -b 2048 -d example.com -s mail \
-D /etc/opendkim/keys/example.com/
sudo chown -R opendkim:opendkim /etc/opendkim/keys/
# Get the DNS record to publish
cat /etc/opendkim/keys/example.com/mail.txt
Edit /etc/opendkim.conf:
Mode sv
Domain example.com
Selector mail
KeyFile /etc/opendkim/keys/example.com/mail.private
Socket inet:8891@localhost
TrustAnchorFile /usr/share/dns/root.key
Phase 6: DNS Records
Add these records to your domain’s DNS:
| Type | Name | Value |
|---|---|---|
| MX | example.com | mail.example.com (priority 10) |
| A | mail.example.com | YOUR_SERVER_IP |
| TXT | example.com | v=spf1 ip4:YOUR_SERVER_IP ~all |
| TXT | mail._domainkey.example.com | (from mail.txt above) |
| TXT | _dmarc.example.com | v=DMARC1; p=none; rua=mailto:admin@example.com |
Also configure reverse DNS (PTR record) through your VPS provider panel.
Phase 7: Start Services and Test
sudo systemctl restart opendkim postfix dovecot
sudo systemctl enable opendkim postfix dovecot
Send a test email:
echo "Test email body" | mail -s "Test" someone@gmail.com
Check logs:
sudo tail -f /var/log/mail.log
Test authentication by connecting with a mail client using:
- IMAP:
mail.example.com:993(SSL/TLS), useruser@example.com - SMTP:
mail.example.com:587(STARTTLS), same credentials
Maintenance Checklist
- TLS cert renewal:
sudo certbot renew --dry-run - Log rotation:
/var/log/mail.logrotated automatically - IP blacklist checks: check weekly with MXToolbox
- Dovecot password changes: update
auth-static.conf.extand reload - Software updates:
sudo apt upgrademonthly
What’s Next
- Set DMARC policy from
p=nonetop=quarantineafter reviewing reports - Add SpamAssassin for inbound spam filtering
- Consider Rspamd as a more modern alternative
- Set up Roundcube for webmail access