Andrew Mercer
on this page

TLS provides encryption for a mail session — but only if both your configuration and the peer server's configuration cooperate, since encryption is negotiated per-hop, per-message. Postfix's own reference doc on this (TLS_README) is genuinely worth reading in full; this is a distillation focused on what actually needs deciding.

The two independent directions

Postfix has entirely separate settings for TLS as a client (sending mail out, smtp_*) versus TLS as a server (receiving mail in, smtpd_*). It's easy to configure one and forget the other:

# /etc/postfix/main.cf — TLS config
smtp_use_tls = yes
smtpd_use_tls = yes

smtp_tls_security_level = may
smtpd_tls_security_level = may

smtpd_tls_key_file = /path/to/mx/certs/smtpd.pem
smtpd_tls_cert_file = /path/to/mx/certs/smtpd.pem
smtpd_tls_CAfile = /path/to/mx/certs/smtpd.pem

smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom

# Force encryption for specific sites only
smtp_tls_policy_maps = hash:/etc/postfix/tls_policy

smtp_use_tls/smtpd_use_tls are the legacy on/off switches from before *_security_level existed; on any reasonably current Postfix, *_tls_security_level supersedes them, but it's harmless (and common in the wild, as in these notes) to see both set.

Security levels: may vs. encrypt

  • may — opportunistic. Advertise/attempt STARTTLS, use it if the peer supports it, but fall back to plaintext if not. This is the sane default for general internet mail, since you can't control whether every random peer server supports TLS.
  • encrypt — mandatory. Refuse to send/accept mail at all without a successful TLS negotiation. Be ready for Must issue a STARTTLS command first errors on the receiving side if a peer that doesn't expect mandatory TLS connects — this setting is a deliberate trade of compatibility for strict encryption, appropriate for a specific trusted pair of servers, not general-purpose.

Forcing encryption to/from specific domains only

A middle ground: opportunistic by default, but mandatory for a specific set of partner domains, via a policy map:

smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
# /etc/postfix/tls_policy
domain.tld     encrypt
.domain.tld    encrypt
postmap /etc/postfix/tls_policy

The leading . on the second line means "and all subdomains of domain.tld too." If this map file doesn't exist yet but is referenced, you'll get:

fatal: open database /etc/postfix/tls_policy.db: No such file or directory

— create the file (even empty, if you're not ready to add entries yet) and postmap it.

Generating and installing a certificate

The notes this is distilled from used a self-signed CA for internal/personal mail servers rather than a public CA — reasonable for a small closed set of hosts that all trust the same private CA, but for anything receiving mail from the general internet, a certificate from a publicly trusted CA (including free automated ones) is what avoids TLS warnings/failures from strangers' mail servers. Either way, the Postfix-side setup is the same three settings:

smtpd_tls_key_file  = /path/to/certs/smtpd.pem
smtpd_tls_cert_file = /path/to/certs/smtpd.pem
smtpd_tls_CAfile    = /path/to/certs/smtpd.pem

If distributing a private CA's cert/key pair to multiple mail hosts that need to trust each other, a plain scp of the cert material is sufficient:

scp certs*.pem user@otherhost:/path/to/certs/

A mistake worth flagging explicitly (since it came up directly in the original notes): if you're forwarding mail from host A to host B and set up a private CA, the CA (and the certificate it issues) needs to live on whichever host is presenting the certificate during the TLS handshake — i.e. the receiving side in that leg (or both, if TLS is mutual/enforced both directions). Getting the CA "backwards" — generating it on the wrong host relative to which side needs to present a cert — is a classic self-inflicted TLS failure that produces confusing, generic-looking errors rather than a clear "wrong host" message.

Verifying encryption actually happened

Don't just trust the config — check a delivered message's headers for the TLS details Postfix records:

Received: from mail.example.org (mail.example.org [192.0.2.40])
    (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))
    (No client certificate requested)
    by mx.example.com (Postfix) with ESMTPS id D9B5220469
    for <you@example.com>; Tue, 19 Mar 2024 20:37:19 -0400 (EDT)

ESMTPS (vs. plain ESMTP) in the by ... with clause, plus the explicit cipher line, confirm the hop was encrypted. For strongest results, prefer modern TLS versions and AES-256 ciphers over legacy options — a cipher like ADH-AES256-SHA (anonymous Diffie-Hellman) provides encryption but no peer authentication, which is fine for confidentiality against passive eavesdropping but doesn't verify you're actually talking to who you think you are; for anything security-sensitive, prefer certificate-authenticated cipher suites.

Troubleshooting TLS

SELinux is preventing ... smtpd from read access on the file ...pem — SELinux is blocking Postfix from reading a cert/key file outside its expected context (commonly because the file lives under a user's home directory rather than a proper system location). Either: - move the cert material to a location with an appropriate SELinux file context, or - generate and apply a local policy module for the specific access:

grep smtpd /var/log/audit/audit.log | audit2allow -M mypol
semodule -i mypol.pp

Moving the files to a conventional location (e.g. under /etc/pki/tls/ or wherever your distribution expects TLS material) is the more maintainable long-term fix versus generating one-off policy exceptions.

STARTTLS "just doesn't work" with no clear error — before assuming a config problem, rule out environmental interference. Things that have been known to cause exactly this symptom: - SELinux enforcing mode blocking file access silently in ways that don't always surface a clean error (see above; a full temporary disable via /etc/sysconfig/selinux + reboot is a blunt but effective diagnostic step to isolate whether SELinux is the culprit at all). - A second MTA still listening/interfering — e.g. sendmail not fully disabled (chkconfig sendmail off), or a previously-attempted mail server install (the notes specifically mention a leftover Zimbra service) still enabled and competing for the port:

systemctl list-units
systemctl status zimbra.service
systemctl stop zimbra.service
chkconfig zimbra off

warning: TLS library problem ... SSL_CTX_use_PrivateKey_file ... — almost always a stale or mismatched path in smtp_tls_key_file/smtpd_tls_key_file (e.g. pointing at a filename left over from before a certificate was renamed/regenerated). Double-check the exact path referenced in main.cf actually matches the current certificate/key files on disk.

warning: TLS library problem ... enabling PIX workarounds — not actually your problem: it means the peer mail gateway is a Cisco PIX firewall running its (historically buggy) SMTP "fixup" feature, and Postfix is logging that it detected this and activated a compatibility workaround. Informational, not an error on your end.

warning: request to update table btree:/var/run/smtpd_tls_session_cache in non-postfix directory /var/run — as of Postfix 2.5+, the TLS session cache is required to live under Postfix's own data directory; Postfix automatically redirects the request and logs a warning rather than failing. Update the config to point at the correct location directly and the warning goes away:

smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache