6.6 KiB
| name | description | version | author | license | metadata | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| mail-server-setup | Set up Postfix+Dovecot+DKIM mail server on Ubuntu. | 1.0.0 | Hermes Agent | MIT |
|
Mail Server Setup
Complete mail server on Ubuntu: Postfix (SMTP/SMTPS/Submission), Dovecot (IMAPS), OpenDKIM signing, and Let's Encrypt TLS certificate.
Prerequisites
- Ubuntu 22.04+ with root/sudo access
- DNS managed externally (Aliyun DNS, Cloudflare, etc.)
- Port 25 may need manual unblock from cloud provider (Aliyun requires ticket)
- A domain with MX record pointing to the server
DNS Records Required
Before starting, ensure these DNS records exist:
| Type | Host | Value | Priority |
|---|---|---|---|
| A | mail.domain.com | <server_ip> | — |
| MX | @ | mail.domain.com | 1 |
| TXT | @ | v=spf1 mx ~all | — |
| TXT | _dmarc | v=DMARC1; p=none; rua=mailto:admin@domain.com | — |
| TXT | default._domainkey | (generated below) | — |
Cloud Firewall Ports
Open these TCP ports in the cloud security group:
| Port | Purpose |
|---|---|
| 25 | SMTP (requires manual unblock ticket on Aliyun) |
| 465 | SMTPS (SSL-encrypted submission) |
| 587 | Submission (STARTTLS) |
| 993 | IMAPS (SSL-encrypted retrieval) |
| 80 | HTTP (certbot renewal) |
No local firewall needed if UFW is inactive (default on many Ubuntu images).
Install Packages
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
postfix dovecot-core dovecot-imapd opendkim opendkim-tools
Debconf preseed avoids interactive postfix configuration prompts.
Configure Postfix
postconf -e myhostname=mail.domain.com
postconf -e mydomain=domain.com
postconf -e myorigin=/etc/mailname
postconf -e mydestination=localhost
postconf -e mynetworks=127.0.0.0/8 [::1]/128
postconf -e home_mailbox=Maildir/
postconf -e virtual_alias_maps=hash:/etc/postfix/virtual
postconf -e smtpd_tls_security_level=may
postconf -e smtp_tls_security_level=may
postconf -e smtpd_sasl_type=dovecot
postconf -e smtpd_sasl_path=private/auth
postconf -e smtpd_sasl_auth_enable=yes
postconf -e smtpd_tls_auth_only=yes
postconf -e smtpd_recipient_restrictions=permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
CRITICAL: use key=value syntax (no spaces around =) to avoid shell quoting issues.
Enable submission (587) and smtps (465) in master.cf:
sed -i 's/^#submission/submission/' /etc/postfix/master.cf
sed -i 's/^#smtps/smtps/' /etc/postfix/master.cf
# Also uncomment the -o lines under each
Set up virtual alias and Maildir:
echo "domain.com" > /etc/mailname
echo "admin@domain.com username" > /etc/postfix/virtual
postmap /etc/postfix/virtual
mkdir -p ~/Maildir/{cur,new,tmp} && chmod -R 700 ~/Maildir
Configure Dovecot
Mail location → Maildir:
sed -i 's|^mail_location = .*|mail_location = maildir:~/Maildir|' /etc/dovecot/conf.d/10-mail.conf
Auth config (SASL for Postfix):
cat > /etc/dovecot/conf.d/10-auth.conf << 'EOF'
disable_plaintext_auth = yes
auth_mechanisms = plain login
!include auth-system.conf.ext
EOF
Master config (IMAPS only, SASL socket):
cat > /etc/dovecot/conf.d/10-master.conf << 'EOF'
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
unix_listener auth-userdb {
mode = 0600
user = USERNAME
}
}
service imap-login {
inet_listener imap { port = 0 }
inet_listener imaps { port = 993; ssl = yes }
}
service pop3-login {
inet_listener pop3 { port = 0 }
inet_listener pop3s { port = 0 }
}
EOF
SSL (initially snakeoil):
cat > /etc/dovecot/conf.d/10-ssl.conf << 'EOF'
ssl = yes
ssl_cert = </etc/dovecot/private/dovecot.pem
ssl_key = </etc/dovecot/private/dovecot.key
ssl_min_protocol = TLSv1.2
EOF
Let's Encrypt Certificate
sudo certbot certonly --webroot -w /var/www/html \
-d mail.domain.com --non-interactive --agree-tos \
--email admin@domain.com
Apply to Postfix:
CERT=/etc/letsencrypt/live/mail.domain.com/fullchain.pem
KEY=/etc/letsencrypt/live/mail.domain.com/privkey.pem
postconf -e smtpd_tls_cert_file=$CERT
postconf -e smtpd_tls_key_file=$KEY
postconf -e smtp_tls_cert_file=$CERT
postconf -e smtp_tls_key_file=$KEY
postconf -e smtpd_tls_protocols='!SSLv2, !SSLv3, !TLSv1, !TLSv1.1'
Apply to Dovecot:
cat > /etc/dovecot/conf.d/10-ssl.conf << EOF
ssl = yes
ssl_cert = <$CERT
ssl_key = <$KEY
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes
EOF
Configure DKIM (OpenDKIM)
Generate keys:
mkdir -p /etc/opendkim/keys/domain.com && cd /etc/opendkim/keys/domain.com
opendkim-genkey -s default -d domain.com
chown root:root default.private && chmod 600 default.private
OpenDKIM config:
cat > /etc/opendkim.conf << 'EOF'
Syslog yes
UMask 002
Domain domain.com
KeyFile /etc/opendkim/keys/domain.com/default.private
Selector default
Mode sv
Canonicalization relaxed/simple
Socket inet:8891@localhost
EOF
Add milter to Postfix and restart:
postconf -e milter_default_action=accept
postconf -e milter_protocol=6
postconf -e smtpd_milters=inet:localhost:8891
postconf -e non_smtpd_milters=inet:localhost:8891
systemctl restart opendkim postfix dovecot
Get the DKIM DNS record:
cat /etc/opendkim/keys/domain.com/default.txt
Verification
# Check ports
sudo ss -tlnp | grep -E '25|465|587|993'
# Test TLS (should show "Verify return code: 0 (ok)")
echo "QUIT" | openssl s_client -starttls smtp -connect localhost:25 2>&1 | grep "Verify return code"
echo "Q" | openssl s_client -connect localhost:993 2>&1 | grep "Verify return code"
# Test local delivery
echo "TEST" | mail -s "Test" username@localhost
ls ~/Maildir/new/
Pitfalls
- SSH quoting breaks config commands: When running through SSH, use base64 encoding
to transmit complex scripts. See
socks-proxy-downloadskill → shell-quoting-workaround.md. - postconf whitespace:
postconf -e "key = value"fails (shell consumes=). Always usepostconf -e key=value(no spaces). - opendkim key ownership: Must match the running uid. If no
UserIDin config, opendkim runs as root → key must be owned by root. Error: "not owned by executing uid". - Aliyun blocks port 25: Requires manual ticket to unblock.
- DNS propagation delay: Let's Encrypt validation may fail for newly added DNS records.
Use
--dry-runfirst. Retry after propagation. - Dovecot SASL socket: Must be at
/var/spool/postfix/private/authwith mode 0660, group postfix — otherwise postfix can't authenticate users.