InboxGrade

TLS certificate expired on sending server

The X.509 certificate presented by your SMTP server during `STARTTLS` is past its `notAfter` date, so receiving MTAs reject the handshake or fall back to cleartext.

Impact — High impact

Receivers that enforce DANE or MTA-STS (policy: enforce) will refuse delivery outright, bouncing mail with 454 4.7.0 TLS not available due to local problem or 421 4.7.0 TLS handshake failed. Peers without a strict policy downgrade to plaintext, which breaks TLS-RPT reporting, trips opportunistic-TLS alerts at Google and Microsoft, and exposes message contents in transit. Expect a sharp drop in inbox placement and a flood of tls=TLS_error entries in receiver logs within hours of expiry.

What causes it

  1. ACME renewal cron (certbot renew, acme.sh --cron) failed silently — usually a port 80 HTTP-01 challenge blocked by a firewall rule or a reverse proxy that stopped forwarding /.well-known/acme-challenge/.
  2. Certificate was issued manually by an ops engineer who left, with no renewal automation and no monitoring on notAfter.
  3. Postfix or Exim is pointing at a stale smtpd_tls_cert_file path after a renewal wrote the new cert elsewhere, and the daemon was never reloaded.
  4. DNS CAA record was tightened to a different CA, so the ACME client now fails the caa check during renewal but keeps serving the old cert until expiry.
  5. Wildcard cert renewed on the web host but never copied to the mail server — common when mail.example.com and www.example.com share a cert distributed by rsync or Ansible that silently broke.

How to fix it

Confirm the expiry and which cert is actually being served

Hit port 25 with openssl s_client and read the notAfter field. Do this against the public hostname your MX points to, not localhost — a split DNS or load balancer may be presenting a different cert than the one on disk.

echo | openssl s_client -starttls smtp -connect mail.example.com:25 -servername mail.example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates

Force an immediate Let's Encrypt renewal

Run certbot with --force-renewal to bypass the 30-day threshold, then verify the new cert chain. If HTTP-01 is blocked, switch to DNS-01 — most providers (Cloudflare, Route 53, deSEC) have a certbot plugin. For mail servers behind NAT, DNS-01 is the only reliable long-term option.

sudo certbot renew --force-renewal --cert-name mail.example.com
sudo certbot certificates   # confirm new notAfter

Reload the MTA so it picks up the new cert

Certbot's --deploy-hook is the right place for this, but if you renewed manually, reload Postfix, Exim, or Haraka explicitly. A restart is not required and will drop active connections — reload re-reads the cert files in place.

sudo systemctl reload postfix
# or for exim: sudo systemctl reload exim4
# verify:
sudo postconf -n | grep smtpd_tls_cert_file

Add expiry monitoring so this never bounces mail again

Set a check that alerts at 14 days before notAfter. A Prometheus blackbox_exporter with the tls_connect module, an Uptime Kuma TLS monitor, or a weekly cron that pipes openssl s_client into date -d all work. Pair it with a TLS-RPT record (_smtp._tls.example.com TXT "v=TLSRPTv1; rua=mailto:tls-reports@example.com") so receivers tell you when handshakes fail.

_smtp._tls.example.com. 3600 IN TXT "v=TLSRPTv1; rua=mailto:tls-reports@example.com"

Fixed it? Run a fresh test to confirm.

Test my inbox →

Related errors