邮件和其他协议
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 检测工具,它会显示证书、证书链和主机名是否匹配。