邮件和其他协议

certificate verify failed:SMTP STARTTLS 发信报错怎么修

作者: · 最后核实

报错信息stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:0A000086:SSL routines::certificate verify failed

这个报错的意思是:邮件客户端用 STARTTLS 把 SMTP 连接升级为 TLS 之后,拒绝了服务器的证书。 邮件客户端的检查和浏览器一样:配置的主机名必须出现在证书里,证书链必须到达受信任的根证书。邮件服务器上这些检查更容易失败,因为默认证书往往是自签名的,主机名也经常是别名。

快速修复

多数情况是主机名不在证书里,改用证书上的主机名即可。先针对程序实际使用的主机名和端口,查看服务器出示的证书(也可以用 STARTTLS 检测工具):

# 587(或 25)端口 STARTTLS
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"
# 465 端口直接 TLS
openssl s_client -connect mail.example.com:465 -servername mail.example.com </dev/null
  • subject 或 SAN 里没有你的主机名: 改用证书上写的主机名,或者为你的主机名安装证书。
  • 验证错误 18/19(自签名): 换成公网受信任的证书。
  • 验证错误 20/21: 服务器装上完整证书链。

原因

连接用的主机名不在证书里最常见,其次是邮件服务器默认的自签名证书。

1. 主机名不匹配

虚拟主机和托管邮箱常常出示服务商服务器名的证书(比如 server12.hosting.example),而用户配置的是 mail.example.com。

  • 确认: 证书的 SAN 列表里没有 SMTP 设置里的主机名。
  • 修复: SMTP 主机名按证书上的名字填写,或者请服务商签发覆盖你别名的证书。用 QQ 企业邮、阿里企业邮箱、网易企业邮箱等服务时,按服务商文档填写它们官方的 SMTP 服务器地址,不要填自己域名的 CNAME 别名。

2. 默认的自签名证书

新装的 Postfix、Exim、Dovecot 通常带一张自签名的 “snakeoil” 证书。

  • 确认: subject 和 issuer 完全相同。
  • 修复: 为邮件主机名签发证书并配置:
# /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 写 ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem 和 ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem。

3. 缺中间证书

  • 确认: s_client 只显示一张证书,报错误 20 或 21。
  • 修复: 让服务器使用 fullchain.pem,而不是 cert.pem,见缺少中间证书。

4. 续期了但没重载

Postfix 和 Dovecot 只在启动或重载时读取证书,磁盘上的新文件在此之前不会生效。

  • 确认: 对外出示的证书 notAfter 比磁盘上文件的旧。
  • 修复: 给 Certbot 加部署钩子:--deploy-hook "systemctl reload postfix dovecot"。

5. 应用自己的 CA 库过旧

老版本 PHP、Python、Java 可能缺少较新的根证书,此时只有这个应用报错。

  • 修复: 升级运行环境及其 CA 证书包(系统的 ca-certificates、php.ini 里的 openssl.cafile、Java 的 cacerts)。

各环境修法

所有客户端的修法相同:Host 填证书上的主机名,客户端保持校验开启,并使用有效的 CA 证书包。

PHPMailer / Symfony Mailer(含宝塔上的 PHP 网站): 修服务器或改正主机名,Host 填证书上的名字。两者都用 PHP 的 OpenSSL 设置,openssl.cafile 必须指向有效的证书包。

Python smtplib: 用 smtplib.SMTP(host, 587),再 starttls(context=ssl.create_default_context()),默认上下文会校验主机名和证书链。

Java(Jakarta Mail): 设置 mail.smtp.ssl.checkserveridentity=true,私有根证书导入 truststore,而不是信任所有主机。

自建邮件服务器并启用了 DANE 或 MTA-STS: 更换证书后,在旧密钥下线前先更新 TLSA 记录(可用 TLSA 生成工具),否则强制 DANE 的发送方会拒绝投递。

不要这样做

关闭验证或退回明文 SMTP,都会让账号密码暴露给网络路径上的任何人。

  • 关闭对端验证(PHP 流选项里 verify_peer => false,Java 里 mail.smtp.ssl.trust=*)。SMTP 账号密码和邮件内容会被路径上的任何人看到。
  • 退回不加密的 SMTP。 账号密码会以明文传输。
  • 保留默认自签名证书,让用户自己点“接受”。 用户会养成习惯,遇到攻击时也照样点接受。

常见问题

邮件服务器能用 Let's Encrypt 证书吗?

能。为邮件主机名(如 mail.example.com)用 HTTP-01 或 DNS-01 签发证书,Postfix 和 Dovecot 指向完整证书链和私钥,再加一个续期后重载两个服务的钩子。

服务器之间投递(MX)会因为证书问题失败吗?

通常不会。多数邮件服务器在 25 端口上默认使用“机会性 TLS”,证书验证不过也照样投递。只有发送方强制验证时才会失败,比如对方域名启用了 MTA-STS 或 DANE,或者发送方对该域名有本地策略。

云服务器上程序发信应该用哪个端口?

提交邮件推荐 465 端口直接 TLS(RFC 8314),587 加 STARTTLS 也普遍支持。阿里云、腾讯云等国内云服务器默认封禁出方向 25 端口,程序发信请用 465 或 587。QQ 邮箱、163 邮箱、企业邮箱的 SMTP 都支持 465。

不发邮件怎么测试?

用 openssl s_client -starttls smtp -connect mail.example.com:587 -servername mail.example.com -verify_return_error,或者 STARTTLS 检测工具,它会显示证书、证书链和主机名是否匹配。

资料来源

  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)