Mail and other protocols

SMTP STARTTLS certificate verify failed: fix mail TLS

By · Last verified

Error messagestream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:0A000086:SSL routines::certificate verify failed

This error means the mail client upgraded the SMTP connection to TLS with STARTTLS and then rejected the server's certificate. The checks match a browser's: the configured hostname must appear in the certificate, and the chain must reach a trusted root. Mail servers fail them more often, because default certificates are self-signed and hostnames are often aliases.

Quick fix

The verify code and the certificate names show which fix applies.

Inspect what the server presents for the exact host and port your application uses, with the STARTTLS checker or OpenSSL:

# STARTTLS on 587 (or 25)
openssl s_client -starttls smtp -connect mail.example.com:587 -servername mail.example.com \
  -verify_return_error </dev/null 2>&1 | grep -E "subject=|issuer=|Verify return|verify error"
# implicit TLS on 465
openssl s_client -connect mail.example.com:465 -servername mail.example.com </dev/null
  • Subject or SAN does not contain your hostname: use the hostname the certificate is issued for, or install a certificate for your name.
  • Verify error 18/19 (self-signed): replace the default certificate with a publicly trusted one.
  • Verify error 20/21: install the full chain on the server.

Causes

1. Hostname mismatch

Shared hosting and managed mail often present a certificate for the provider's server name (for example server12.hosting.example), while users configure mail.example.com.

  • Confirm: the certificate's SAN list lacks the hostname in your SMTP settings.
  • Fix: configure the SMTP host exactly as named in the certificate, or have the provider issue a certificate covering your alias.

2. Default self-signed certificate

Fresh Postfix, Exim and Dovecot installations often use a self-signed "snakeoil" certificate.

  • Confirm: subject and issuer are identical.
  • Fix: issue a certificate for the mail hostname and configure it:
# /etc/postfix/main.cf
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file  = /etc/letsencrypt/live/mail.example.com/privkey.pem

Dovecot uses ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem and ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem.

3. Missing intermediate

  • Confirm: s_client shows only one certificate and error 20 or 21.
  • Fix: point the server at fullchain.pem, not cert.pem.

4. Renewed but not reloaded

Postfix and Dovecot read certificates at start-up or reload; a renewed file on disk is not used until then.

  • Confirm: the served certificate's notAfter is older than the file on disk.
  • Fix: add a Certbot deploy hook: --deploy-hook "systemctl reload postfix dovecot".

5. Outdated CA store in the application

Old PHP, Python or Java runtimes may lack current roots. Errors then appear only from that application.

  • Fix: update the runtime and its CA bundle (ca-certificates, openssl.cafile in php.ini, Java cacerts).

Fixes by environment

Every client needs the configured hostname on the certificate and a CA store that reaches its root. Outlook shows the same problem as a certificate name warning.

PHPMailer / Symfony Mailer: fix the server or the configured hostname; set the host to the certificate's name. Both libraries use PHP's OpenSSL settings, so openssl.cafile must point to a valid bundle.

Python smtplib: use smtplib.SMTP(host, 587) then starttls(context=ssl.create_default_context()); the default context verifies hostname and chain.

Java (Jakarta Mail): set mail.smtp.ssl.checkserveridentity=true and import private roots into the truststore instead of trusting all hosts.

Self-hosted mail with DANE or MTA-STS: after replacing a certificate, update TLSA records (the TLSA generator helps) before the old key disappears, or sending servers that enforce DANE will refuse delivery.

Don't do this

  • Disable peer verification (verify_peer => false in PHP stream options, mail.smtp.ssl.trust=* in Java). SMTP credentials and message contents become readable by anyone in the path.
  • Fall back to plain SMTP on port 25 or 587 without TLS. Credentials are then sent in clear text.
  • Leave the default self-signed certificate in place and tell users to accept the warning; they learn to accept warnings for real attacks too.

FAQ

Can I use a Let's Encrypt certificate on a mail server?

Yes. Issue it for the mail hostname (for example mail.example.com) with HTTP-01 or DNS-01, point Postfix and Dovecot at the full chain and key, and add a renewal hook that reloads both services.

Do server-to-server (MX) deliveries fail on a bad certificate?

By default most MTAs use opportunistic TLS on port 25 and still deliver if the certificate cannot be verified. Delivery fails only when the sender enforces verification, for example through MTA-STS or DANE policies, or a local policy for that domain.

Which port should my application use?

For message submission, RFC 8314 recommends implicit TLS on port 465; port 587 with STARTTLS is also widely supported. Port 25 is for server-to-server relay and is often blocked for outbound traffic by cloud providers.

How do I test without sending mail?

Use openssl s_client -starttls smtp -connect mail.example.com:587 -servername mail.example.com -verify_return_error, or the STARTTLS checker, which reports the certificate, chain and hostname match.

Sources

  1. RFC 3207: SMTP Service Extension for Secure SMTP over TLS
  2. RFC 8314: Cleartext Considered Obsolete: Use of TLS for Email Submission and Access
  3. Postfix TLS Support
  4. OpenSSL openssl-s_client (-starttls)