Django3.1基于python:3.8-slim-buster生产环境发邮件报SSL EOF错误解法
升级背景
- 将Django API服务从2.2版本升级至3.1版本,参照django-cookiecutter项目的对应配置变更,同步更新了Dockerfile及关联bash脚本文件
- 升级后整体服务运行正常,仅生产环境邮件发送功能异常
排查复现过程
- 编写极简测试管理命令验证邮件配置有效性,代码如下:
from django.conf import settings from django.core.mail import send_mail from django.core.management.base import BaseCommand class Command(BaseCommand): """ Sends an email to the provided addresses. """ help = "Sends an email to the provided addresses." def add_arguments(self, parser): parser.add_argument("emails", nargs="+") def handle(self, *args, **options): self.stdout.write(f"send_email from: {settings.EMAIL_FROM_FIELD}") for email in options["emails"]: try: send_mail( "expert-system test email", "a simple email", settings.EMAIL_FROM_FIELD, [email], ) except BaseException as e: self.stdout.write(f"Problem sending email: {e}")
- 执行测试命令的输出结果:
$ python manage.py send_email harry@test.com send_email from: test@test.com Problem sending email: EOF occurred in violation of protocol (_ssl.c:1125)
- 验证容器环境全局TLS能力,检查结果显示环境本身支持TLS 1.3、全主流加密套件,无全局TLS配置问题,排除系统层面的TLS能力缺陷:
$ python -c "from urllib.request import urlopen ; print(urlopen('https://www.howsmyssl.com/a/check').read())" b'{"given_cipher_suites":["TLS_AES_256_GCM_SHA384","TLS_CHACHA20_POLY1305_SHA256","TLS_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV"],"ephemeral_keys_supported":true,"session_ticket_supported":true,"tls_compression_supported":false,"unknown_cipher_suite_supported":false,"beast_vuln":false,"able_to_detect_n_minus_one_splitting":false,"insecure_cipher_suites":{},"tls_version":"TLS 1.3","rating":"Probably Okay"}'
配置调整方案
- 核对SMTP连接参数匹配性
- 按照SMTP服务商的标准端口规则校验配置:
- 端口为465时:设置
EMAIL_USE_SSL = True,EMAIL_USE_TLS = False,直接建立SSL加密连接 - 端口为587时:设置
EMAIL_USE_TLS = True,EMAIL_USE_SSL = False,明文连接后通过STARTTLS协议升级为加密连接
- 端口为465时:设置
- 不可同时开启
EMAIL_USE_SSL和EMAIL_USE_TLS,否则会导致连接逻辑冲突,直接被服务端断开连接
- 按照SMTP服务商的标准端口规则校验配置:
- 调整SMTP连接的SSL安全级别
- 升级后的基础镜像内置OpenSSL 1.1.1+版本,默认安全级别为2,会拒绝SMTP服务端提供的旧TLS版本、弱加密套件,直接导致握手失败并抛出EOF错误
- 在Django配置文件中添加自定义SSL上下文,降低加密套件检查的安全级别以兼容老版本SMTP服务:
import ssl # 邮件发送专用SSL上下文,兼容旧版SMTP服务 EMAIL_SSL_CONTEXT = ssl.create_default_context() EMAIL_SSL_CONTEXT.set_ciphers("DEFAULT:@SECLEVEL=1")
- 配置调整完成后,重新执行测试管理命令,验证邮件发送功能恢复正常
内容的提问来源于stack exchange,提问作者Harry Moreno
相关产品推荐
相关产品推荐

