An expired TLS certificate on a Postfix server breaks mail delivery in two distinct ways, and teams frequently detect only one. The inbound symptom is loud: clients that require STARTTLS fail handshakes, connections drop, and complaints arrive quickly. The outbound symptom is quiet: mail to destinations enforcing mutual TLS or DANE silently defers into the deferred queue, where it sits under Postfix’s increasing backoff schedule until someone notices the queue growing.
Postfix has no built-in certificate expiry monitoring. There is no metric, no warning at startup, and no periodic check. The daemon loads certificate files into memory at process spawn time and serves them until reloaded. If certbot renews a certificate but the deploy hook is missing or broken, Postfix continues serving the old cert from memory. The file on disk is valid. The cert in flight is not.
The diagnostic discipline must cover both sides: the smtpd certificate presented to inbound clients, and the smtp client certificate used for outbound connections to destinations that require mutual TLS or DANE. Certificate expiry is not a Postfix metric. It requires an external openssl x509 check against the actual cert files, and the check should also verify what Postfix serves over the wire, not just what is on disk.
What this means
flowchart TD
A["TLS failures or silent deferrals"] --> B{Inbound or outbound?}
B -->|Inbound smtpd| C["Check smtpd cert expiry"]
B -->|Outbound smtp| D["Check smtp client cert expiry"]
C --> E{Expired?}
E -->|Yes| F["Renew and reload Postfix"]
E -->|No| G["Check cipher or protocol mismatch"]
D --> H{DANE enabled?}
H -->|Yes| I["TLSA hash matches new key?}
H -->|No| E
I -->|No| J["Update TLSA or reuse key"]
I -->|Yes| EAn expired smtpd certificate breaks inbound TLS for every client that requires it. Clients with mandatory-TLS policies (smtp_tls_security_level = encrypt) refuse to deliver. Clients with opportunistic TLS (may) silently fall back to plaintext, which may violate security policy without triggering any alert.
An expired smtp client certificate breaks outbound mutual TLS. Destinations that require authenticated TLS reject the connection, typically with a 4xx deferral. Postfix retries on its backoff schedule, so the deferred queue grows steadily. Because opportunistic outbound TLS falls back silently, the failure may only become visible when a destination enforces it.
DANE-EE (TLSA usage 3) does not check certificate expiration. An expired cert can still pass DANE-EE validation if the TLSA hash matches. But the same expired cert will fail standard PKIX validation at non-DANE destinations. This asymmetry means an expired cert can cause selective failures: DANE-validating destinations continue working while PKI-validating destinations reject.
Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Inbound smtpd cert expired | Clients fail STARTTLS or fall back to plaintext | openssl x509 -enddate on the smtpd cert file |
| Outbound client cert expired | Mail silently defers to mutual-TLS destinations | openssl x509 -enddate on the smtp client cert file |
| Postfix not reloaded after renewal | Cert on disk is valid but Postfix serves old cert from memory | Compare served cert via openssl s_client vs file on disk |
| DANE TLSA mismatch after renewal | Outbound DANE mail defers after certbot run | Compare TLSA record hash with renewed cert public key |
| Expired intermediate CA in chain | Remote servers reject cert with “certificate expired” despite valid leaf | Check chain for expired intermediates, verify --preferred-chain |
Quick checks
All commands below are safe and read-only.
# Show configured cert paths (inbound and outbound)
postconf -h smtpd_tls_cert_file smtpd_tls_key_file
postconf -h smtp_tls_cert_file smtp_tls_key_file
# Postfix >= 3.4: chain files combine key and cert in one file
postconf -h smtpd_tls_chain_files smtp_tls_chain_files
# Check expiry of configured inbound smtpd cert
openssl x509 -in "$(postconf -h smtpd_tls_cert_file)" -noout -enddate -subject -issuer 2>&1
# Check expiry of configured outbound smtp client cert
openssl x509 -in "$(postconf -h smtp_tls_cert_file)" -noout -enddate -subject -issuer 2>&1
# Check if cert expires within 30 days (exit 0 = still valid, exit 1 = expires soon)
openssl x509 -in "$(postconf -h smtpd_tls_cert_file)" -checkend 2592000 -noout 2>&1; echo "exit: $?"
# Verify what Postfix is actually serving on port 25 (inbound cert in memory)
echo QUIT | openssl s_client -connect localhost:25 -starttls smtp 2>/dev/null | openssl x509 -noout -enddate
# Scan for TLS-related failures from the previous hour (GNU date)
grep "$(date -d '1 hour ago' '+%b %e %H')" /var/log/mail.log | grep -iE 'TLS.*fail|SSL.*error|certificate' | tail -20
# Check deferred queue for TLS-related deferral reasons
grep 'status=deferred' /var/log/mail.log | grep -iE 'TLS|certificate|handshake|SSL' | tail -20
# Verify certbot deploy hook exists for Postfix
ls -la /etc/letsencrypt/renewal-hooks/deploy/ 2>/dev/null
How to diagnose it
Identify which cert paths are configured. Run
postconf -h smtpd_tls_cert_filefor inbound andpostconf -h smtp_tls_cert_filefor outbound. If you are running Postfix >= 3.4, also checksmtpd_tls_chain_filesandsmtp_tls_chain_files, which combine key and chain in a single file and are the preferred interface.Check expiry of each cert file. Run
openssl x509 -in <path> -noout -enddateagainst each configured cert. Pay attention to both the end-entity cert and any intermediate CA certs in the chain. An expired intermediate causes the same “certificate expired” rejection from remote validators even when the leaf is valid.Compare what Postfix serves versus what is on disk. After any renewal, there is a window where the file has been updated but Postfix has not been reloaded. Connect with
openssl s_client -connect localhost:25 -starttls smtpand pipe the output throughopenssl x509 -noout -enddate. If the served end date differs from the file, Postfix is holding a stale cert in memory.Scan logs for TLS failure patterns. Default Postfix logging (level 0) hides failed opportunistic TLS negotiations. Temporarily elevate with
postconf -e 'smtp_tls_loglevel = 1'andpostconf -e 'smtpd_tls_loglevel = 1'followed bypostfix reloadto capture negotiation details. This modifiesmain.cfand triggers a reload; revert to level 0 after debugging. Look forSSL_accept error,SSL_connect error,certificate expired, andTLS handshake failed.Check DANE/TLSA state if outbound mail to DANE-adopting destinations is deferring. After certbot renewal, the new certificate typically has a new public key unless
--reuse-keywas specified. The published TLSA record, which hashes the public key, no longer matches. Query the TLSA record and compare against the new cert.Verify the reload mechanism. Check
/etc/letsencrypt/renewal-hooks/deploy/for a script that runspostfix reloadorsystemctl reload postfix. If the hook is missing, certbot renews the cert on disk but Postfix never picks it up.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| Certificate expiry date (external check) | Not a Postfix metric; must be checked externally with openssl | Less than 30 days: alert. Less than 7 days: page. |
| TLS negotiation failure rate | Indicates cert problems, cipher mismatch, or interception | Mandatory TLS: any failure. Opportunistic: more than 5% failure rate. |
| Deferred queue growth rate | Outbound TLS failures cause silent 4xx deferrals | Sustained growth correlated with TLS errors in logs |
| Delivery rate vs injection rate | TLS deferrals reduce delivery while injection continues | Delivery rate diverging downward from injection rate |
| DANE/TLSA validation failures | TLSA record mismatch after renewal breaks outbound DANE mail | Mail to DANE destinations deferring with TLS errors |
| Opportunistic TLS fallback rate | Silent downgrade to plaintext is a security concern | Increase in plaintext connections where TLS was previously negotiated |
Fixes
Expired inbound smtpd certificate
Renew the certificate through your standard process (certbot or equivalent). Then reload Postfix so it reads the new cert into memory:
# Reload Postfix to pick up the renewed cert
postfix reload
Verify the served cert matches the renewed file using openssl s_client as described in the diagnostic steps. If Postfix does not pick up the new cert after reload, a full restart may be needed.
Postfix serving stale cert after renewal
The cert file on disk is valid but Postfix is serving the old one from memory. This means the deploy hook is missing or failed. Create a deploy hook so Postfix reloads automatically after every renewal:
# Create the deploy hook
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-postfix.sh << 'EOF'
#!/bin/sh
/usr/sbin/postfix reload
EOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-postfix.sh
DANE TLSA mismatch after renewal
When certbot generates a new key pair on renewal, the published TLSA record no longer matches. Three options, in order of operational simplicity:
Use
--reuse-keyin certbot. The public key stays the same across renewals, so the TLSA hash remains valid. Add--reuse-keyto your certbot renewal configuration. This is the simplest fix but means the same key persists across renewal cycles.Switch to DANE-TA (TLSA usage 2) with the CA certificate. Instead of hashing the end-entity cert, hash the issuing CA. The TLSA record survives leaf cert rotation as long as the CA remains the same. This requires republishing the TLSA record once.
Update the TLSA record on every renewal. Add a deploy hook that recomputes the TLSA hash from the new cert and updates DNS via your provider API. Publish the new TLSA record before the cert rotation and account for DNS TTL propagation to avoid a validation gap.
Expired intermediate CA in chain
If remote servers reject your cert with “certificate expired” but your leaf cert is valid, the intermediate CA in your chain may be expired. This occurred with the Let’s Encrypt DST Root CA X3 cross-signature expiration in September 2021: remote servers running outdated OpenSSL rejected certs chained to the expired cross-sign, even though the leaf was valid. Request certs with the current preferred chain:
# Request with ISRG Root X1 chain
certbot certonly --preferred-chain "ISRG Root X1" -d mail.example.com
Verify the chain served by Postfix includes only valid intermediates by inspecting the full chain with openssl s_client -showcerts.
Prevention
- Alert on certificate expiry externally. Postfix provides no internal metric. Run
openssl x509 -checkend 2592000(30 days) in a cron job or monitoring check against both smtpd and smtp cert files. Alert at 30 days out, page inside 7 days. - Monitor both sides. Most teams watch only the smtpd (inbound) certificate. The smtp (outbound) client certificate is equally critical for mutual-TLS and DANE destinations. Outbound failures are silent deferrals, not obvious errors.
- Verify deploy hooks after any certbot configuration change. A certbot reconfigure or version update can silently drop or break the deploy hook. Run
certbot renew --dry-runperiodically and confirm Postfix reloads. - Elevate TLS log levels. Default Postfix logging (level 0) hides failed opportunistic TLS negotiations, creating blind spots for downgrade detection and interoperability issues. Set
smtp_tls_loglevel = 1andsmtpd_tls_loglevel = 1to capture negotiation details. Use level 2 only for active debugging, as it adds significant verbosity. - Use
smtpd_tls_chain_filesandsmtp_tls_chain_files(Postfix >= 3.4). These combine key and chain in a single file, avoiding the race condition present when key and cert are specified in separate files during rollover. - Prefer
--reuse-keywith certbot for DANE deployments. This keeps the TLSA record valid across renewals without requiring DNS updates on every cycle. - If you use deprecated TLS parameters, migrate. Postfix 3.9 marked parameters like
smtpd_use_tls,smtpd_enforce_tls, andsmtp_tls_per_siteas obsolete. Replace them withsmtpd_tls_security_level(mayorencrypt) orsmtp_tls_security_level(may,encrypt, ordane) to avoid postconf warnings and future removal.
How Netdata helps
Netdata does not replace the external openssl x509 expiry check, because Postfix does not expose certificate expiry as a metric. What Netdata provides is early detection of the downstream effects of an expired cert:
- Deferred queue growth rate. An expired outbound cert causes silent deferrals to TLS-enforcing destinations. Per-second queue metrics catch the growth before the queue reaches crisis depth.
- Mail flow velocity (injected vs delivered). When delivery rate drops while injection continues, the divergence appears within minutes. Correlating this with TLS error patterns in logs pinpoints the cause.
- TLS negotiation failure rate. Elevated TLS failures in mail logs surface as a rate anomaly. Mandatory TLS failures should be zero; any nonzero rate warrants investigation.
- Bounce rate monitoring. Some destinations reject expired certs with a 5xx permanent failure rather than a 4xx deferral. A bounce rate spike to specific destinations can indicate cert rejection.
- Process count anomalies. If smtpd processes are consumed by clients retrying failed TLS handshakes, the process count anomaly appears alongside the TLS errors.
Correlating deferred queue growth with TLS failure patterns and delivery rate drops shortens the diagnosis from “mail is slow” to “outbound TLS is broken to specific destinations, check cert expiry.”
Related guides
- Postfix active queue saturation: hitting qmgr_message_active_limit
- Postfix backscatter storm: bounces to forged senders and blocklisting
- Postfix IP blocklisted: deliverability collapse and sender reputation
- Postfix bounce rate spike: 5xx failures, bad address lists, and reputation risk
- postfix check warnings: configuration drift and permission problems
- Postfix Connection refused: blocked port 25 and rejected outbound delivery
- Postfix Connection timed out: delivery deferrals to unreachable destinations
- Postfix content_filter backpressure: incoming queue growth when Amavis or Rspamd slows
- Postfix deferred queue growing: why mail piles up and how to drain it
- Postfix destination concurrency limit: tuning per-destination delivery
- Postfix queue partition disk full: /var/spool/postfix out of space
- Postfix DNS resolver failure: when a broken resolver defers mail to everyone






