7.3 KiB
| name | description | tags | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| mail-server-deployment | Deploy Postfix+Dovecot+DKIM mail server with Let's Encrypt. |
|
Mail Server Deployment
Overview
Deploy a complete mail server stack on Ubuntu 22.04:
- Postfix (SMTP/SMTPS/Submission)
- Dovecot (IMAPS)
- OpenDKIM (email signing)
- Let's Encrypt (SSL certificates)
- SASL authentication (system users)
Prerequisites
- Ubuntu 22.04 server with root/sudo access
- Domain with DNS managed externally (e.g., Aliyun DNS)
- Cloud firewall (security group) capable of opening ports
- Port 25 typically blocked by cloud providers — requires support ticket to unblock
Step 1: DNS Records
Create these five DNS records before starting:
| Type | Host | Value |
|---|---|---|
| MX | @ | 1 mail.<domain> |
| A | <server IP> |
|
| TXT | @ | v=spf1 mx ~all |
| TXT | _dmarc | v=DMARC1; p=none; rua=mailto:admin@<domain> |
DKIM record is generated later (Step 5).
Step 2: Install Packages
sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
postfix dovecot-core dovecot-imapd dovecot-pop3d \
opendkim opendkim-tools
Step 3: Postfix Configuration
See references/postfix-config.md for the complete main.cf and master.cf setup.
Key settings:
postconf -e "myhostname = mail.<domain>"
postconf -e "mydomain = <domain>"
postconf -e "mydestination = localhost, mail.<domain>, <domain>"
postconf -e "home_mailbox = Maildir/"
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 "transport_maps = hash:/etc/postfix/transport"
Enable SMTPS (465) and Submission (587) by uncommenting the submission and smtps sections in /etc/postfix/master.cf.
Step 4: Dovecot Configuration
See references/dovecot-config.md for the complete configuration.
Critical settings:
mail_location = maildir:~/Maildir- SASL auth socket at
/var/spool/postfix/private/auth(mode 0660, user postfix) - IMAP on port 0 (disabled), IMAPS on port 993
- POP3/POP3S disabled entirely
Step 5: Let's Encrypt SSL
sudo certbot certonly --webroot -w /var/www/html \
-d mail.<domain> --non-interactive --agree-tos \
--email admin@<domain>
Apply cert to postfix:
postconf -e "smtpd_tls_cert_file = /etc/letsencrypt/live/mail.<domain>/fullchain.pem"
postconf -e "smtpd_tls_key_file = /etc/letsencrypt/live/mail.<domain>/privkey.pem"
Apply cert to dovecot — see references/dovecot-config.md.
Verify TLS on all ports:
echo Q | openssl s_client -starttls smtp -connect localhost:25 | grep "Verify return code"
echo Q | openssl s_client -connect localhost:465 | grep "Verify return code"
echo Q | openssl s_client -connect localhost:993 | grep "Verify return code"
All should show Verify return code: 0 (ok).
Step 6: DKIM (OpenDKIM)
Generate key:
mkdir -p /etc/opendkim/keys/<domain>
opendkim-genkey -s default -d <domain> -D /etc/opendkim/keys/<domain>/
chown root:root /etc/opendkim/keys/<domain>/default.private
chmod 600 /etc/opendkim/keys/<domain>/default.private
Configure /etc/opendkim.conf:
Domain <domain>
Selector default
KeyFile /etc/opendkim/keys/<domain>/default.private
Socket inet:8891@localhost
Mode sv
Canonicalization relaxed/simple
Create /etc/opendkim/TrustedHosts:
127.0.0.1
::1
localhost
<hostname>
mail.<domain>
<domain>
Fix PID directory:
mkdir -p /var/run/opendkim
chown opendkim:opendkim /var/run/opendkim
Connect to postfix:
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"
DKIM DNS Record
Extract the public key from /etc/opendkim/keys/<domain>/default.txt and add as a TXT record:
| Type | Host | Value |
|---|---|---|
| TXT | default._domainkey |
v=DKIM1; h=sha256; k=rsa; p=<public_key> |
Verify: dig +short TXT default._domainkey.<domain>
Step 7: Firewall (Cloud Security Group)
Open these TCP ports in the cloud security group (e.g., Aliyun):
| Port | Protocol | Purpose |
|---|---|---|
| 25 | TCP | SMTP (external receive) |
| 465 | TCP | SMTPS (SSL send) |
| 587 | TCP | Submission (TLS submit) |
| 993 | TCP | IMAPS (SSL receive) |
| 80 | TCP | Certbot renewal |
Port 25 is usually blocked by cloud providers — submit a support ticket to unblock.
Step 8: Test
Local delivery test
echo "Test" | mail -s "Test subject" <user>@localhost
ls ~/Maildir/new/
DKIM signing test (via SMTP — this is the path that triggers DKIM)
echo -e "EHLO mail.<domain>\nMAIL FROM:<<user>@<domain>>\nRCPT TO:<<user>@localhost>\nDATA\nSubject: DKIM test\nFrom: <user>@<domain>\n\nTest body\n.\nQUIT" \
| timeout 10 openssl s_client -connect localhost:465 -quiet 2>/dev/null
Check the delivered email for DKIM-Signature: header.
Important: DKIM signing only works through the SMTP path (ports 25/465/587). The mail command and sendmail use the non_smtpd_milters path which may not trigger signing for all configurations. Always test DKIM via SMTP.
Queue check
mailq # Should be empty
Pitfalls
-
Port 25 blocked by cloud provider: Mail to
user@domainwill queue and fail with "Connection timed out" because postfix tries to deliver via public IP on port 25. Fix: addtransport_mapsentrydomain local:to force local delivery, AND open port 25 on the security group. -
DKIM signing only via SMTP: The
mailcommand andsendmailgo throughnon_smtpd_milterswhich may or may not trigger DKIM signing depending on configuration. Always test DKIM via SMTP submission (port 465 or 587). -
OpenDKIM PID file: On Ubuntu 22.04, opendkim may fail with "Can't open PID file /run/opendkim/opendkim.pid". Fix:
mkdir -p /var/run/opendkim && chown opendkim:opendkim /var/run/opendkimand addPidFile /var/run/opendkim/opendkim.pidto opendkim.conf. -
OpenDKIM key ownership: opendkim runs as root (uid 0) on Ubuntu 22.04 by default. If the key file is owned by
opendkimuser, opendkim will reject it. Set key ownership toroot:root. -
Shell quoting breaks postconf:
postconf -e "key = value"fails through SSH because the=gets consumed. Usepostconf -e key=value(no spaces around=) or the base64-encoding pattern (seereferences/shell-quoting-workaround.mdunder socks-proxy-download skill). -
Let's Encrypt DNS requirement: Certbot webroot validation requires the domain to resolve publicly. If DNS is misconfigured, certbot will fail with "SERVFAIL". Run
--dry-runfirst to verify. -
mydestination must include the domain: Without
<domain>inmydestination, postfix will try to deliveruser@domainexternally via SMTP (which fails if port 25 is blocked). Add atransport_mapsentry as backup:echo "<domain> local:" > /etc/postfix/transport && postmap /etc/postfix/transport.
References
| File | Content |
|---|---|
references/postfix-config.md |
Complete postfix main.cf and master.cf |
references/dovecot-config.md |
Complete dovecot configuration files |
references/dns-records.md |
DNS record reference with examples |