# SMTP STARTTLS certificate verify failed: fix mail TLS

> Mail apps fail with certificate verify failed after STARTTLS when the SMTP server certificate has the wrong name, is self-signed, expired or lacks its chain.

Source: https://ssl.sh/errors/smtp-starttls-certificate-error/
Last updated: 2026-11-10

**This error means the mail client upgraded the SMTP connection to TLS with [STARTTLS](https://ssl.sh/glossary/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](https://ssl.sh/tools/starttls-checker/) or OpenSSL:

```bash
# 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:

```text
# /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](https://ssl.sh/errors/outlook-certificate-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](https://ssl.sh/tools/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.
