Mail servers

How to set up TLS for Postfix and Dovecot with Let's Encrypt

By · Last verified

Postfix and Dovecot can share one Let's Encrypt certificate for mail.example.com. Postfix offers STARTTLS on port 25 and TLS on submission ports 587 and 465, Dovecot serves IMAP over TLS on port 993, and a Certbot deploy hook reloads both after renewal.

The steps apply to Postfix 3.4 or later and Dovecot 2.3 or 2.4 on Debian, Ubuntu or RHEL-compatible systems with Certbot 4.1 or later.

Prerequisites

The mail host needs a certificate for the name clients and other servers connect to.

  • Postfix and Dovecot installed and working without TLS, with SASL authentication via Dovecot for submission.
  • DNS: an A/AAAA record for mail.example.com (for example 203.0.113.10), the domain's MX pointing to it, and a matching reverse DNS (PTR) record.
  • Inbound TCP 25, 465, 587 and 993 open; TCP 80 open for the HTTP-01 challenge (or use DNS-01).
  • Certbot from snap: sudo snap install --classic certbot && sudo ln -s /snap/bin/certbot /usr/local/bin/certbot.

Step 1: Obtain a certificate for the mail host name

Certbot's standalone mode obtains the certificate when nothing else listens on port 80:

sudo certbot certonly --standalone -d mail.example.com

If a web server on the same host already serves port 80, use --webroot -w /var/www/html or --nginx instead. The files are in /etc/letsencrypt/live/mail.example.com/.

Step 2: Configure Postfix

Postfix needs three settings: the certificate chain, opportunistic TLS on port 25, and TLS logging.

sudo postconf -e 'smtpd_tls_chain_files = /etc/letsencrypt/live/mail.example.com/privkey.pem, /etc/letsencrypt/live/mail.example.com/fullchain.pem'
sudo postconf -e 'smtpd_tls_security_level = may'
sudo postconf -e 'smtpd_tls_mandatory_protocols = >=TLSv1.2'
sudo postconf -e 'smtp_tls_security_level = may'
sudo postconf -e 'smtpd_tls_loglevel = 1'
sudo postconf -e 'smtp_tls_loglevel = 1'

Enable submission (587, STARTTLS) and submissions (465, implicit TLS) in /etc/postfix/master.cf:

submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
submissions inet n      -       y       -       -       smtpd
  -o syslog_name=postfix/submissions
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
sudo postfix check && sudo systemctl reload postfix

Step 3: Configure Dovecot

Dovecot 2.3 and 2.4 use different setting names. On Dovecot 2.3 (Ubuntu 24.04, Debian 12), edit /etc/dovecot/conf.d/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

On Dovecot 2.4 (for example Debian 13), the settings were renamed and take plain paths:

ssl = required
ssl_server_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem

Check your version with dovecot --version, then validate and reload:

sudo doveconf -n | grep -E '^ssl'
sudo systemctl reload dovecot

Step 4: Reload both services on renewal

A Certbot deploy hook reloads Postfix and Dovecot after each renewal of the mail certificate:

sudo tee /etc/letsencrypt/renewal-hooks/deploy/mail.sh > /dev/null <<'EOF'
#!/bin/sh
case "$RENEWED_DOMAINS" in
  *mail.example.com*) systemctl reload postfix dovecot ;;
esac
EOF
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/mail.sh

If you publish DANE TLSA records, reissue once with --reuse-key so future renewals keep the same key: sudo certbot certonly --standalone -d mail.example.com --reuse-key --force-renewal.

Verify

Every port should present the mail.example.com certificate, and OpenSSL should end with Verify return code: 0 (ok). Test each port from another machine. The STARTTLS checker covers SMTP; with OpenSSL:

openssl s_client -connect mail.example.com:25 -starttls smtp -servername mail.example.com </dev/null | head -n 20
openssl s_client -connect mail.example.com:587 -starttls smtp -servername mail.example.com </dev/null | grep -E 'subject|issuer|Verify'
openssl s_client -connect mail.example.com:465 -servername mail.example.com </dev/null | grep -E 'subject|issuer|Verify'
openssl s_client -connect mail.example.com:993 -servername mail.example.com </dev/null | grep -E 'subject|issuer|Verify'

If the host also serves HTTPS, the SSL checker or curl ssl.sh/mail.example.com shows the same certificate's chain and expiry.

Automatic renewal

The Certbot snap timer runs certbot renew twice a day and follows Let's Encrypt's ARI window on Certbot 4.1 and later; the deploy hook then reloads Postfix and Dovecot. Standalone renewals need port 80 free at that moment, so if you later add a web server, switch the certificate to webroot with sudo certbot reconfigure --cert-name mail.example.com --webroot -w /var/www/html.

sudo certbot renew --dry-run --run-deploy-hooks

Mail clients cache nothing about your certificate, so shorter Let's Encrypt lifetimes (64 days from February 10, 2027) need no change. Add mail.example.com to ssl.sh monitoring (in development).

Troubleshooting

  • warning: cannot get RSA private key from file: the order in smtpd_tls_chain_files is wrong; list the key first, then the chain.
  • Dovecot: ssl_cert: Can't open file: on 2.3 the < prefix is missing; on 2.4 you used the old setting names. Run doveconf -n to see which settings are active.
  • Clients report an untrusted or incomplete chain: you pointed at cert.pem; use fullchain.pem, see missing intermediate certificate.
  • Certificate name mismatch in mail clients: clients connect to a name not in the certificate (for example the bare domain); see NET::ERR_CERT_COMMON_NAME_INVALID.
  • A partner's server rejects your MTA's client certificate: Let's Encrypt certificates no longer carry the client authentication EKU; see client auth EKU removed.

FAQ

Should port 25 require TLS?

No. Public MX servers must accept mail from senders without TLS, so use smtpd_tls_security_level = may on port 25 and enforce TLS only on submission ports (587, 465), where your own users authenticate. Use MTA-STS or DANE to let other servers require TLS when delivering to you.

Does renewing the certificate break DANE?

It can. A TLSA record of type 3 1 1 pins the public key, and Certbot generates a new key on each renewal by default. Use --reuse-key for the mail certificate, or publish the next key's TLSA record before rolling over. The TLSA generator builds the record.

Can I use a wildcard certificate for mail?

Yes, as long as the names your MX record and mail clients use are covered. Most setups use a dedicated certificate for mail.example.com so it can be renewed and reloaded independently of the website.

Sources

  1. Postfix TLS Support (TLS_README)
  2. Postfix configuration parameters (postconf.5)
  3. Dovecot: SSL/TLS configuration
  4. Certbot User Guide
  5. Let's Encrypt: Ending TLS Client Authentication Certificate Support