Fix Django Email "[SSL: WRONG_VERSION_NUMBER]" and SMTP TLS Errors
Fix Django SMTP errors such as SSL WRONG_VERSION_NUMBER, STARTTLS extension not supported, and connection unexpectedly closed: match EMAIL_PORT with EMAIL_USE_TLS or EMAIL_USE_SSL, and test the provider’s port from the server.
Problem statement
Sending a password reset or a contact form email fails with one of these:
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1006)
smtplib.SMTPNotSupportedError: STARTTLS extension not supported by server.
smtplib.SMTPServerDisconnected: Connection unexpectedly closed
All three usually mean the same thing: Django's TLS mode does not match the port. SMTP has two ways to encrypt. On port 587, the connection starts as plain text and upgrades with STARTTLS (EMAIL_USE_TLS = True). On port 465, TLS starts immediately (EMAIL_USE_SSL = True). Mixing them up makes one side speak TLS while the other expects plain text.
- Port 587:
EMAIL_USE_TLS = True,EMAIL_USE_SSL = False. - Port 465:
EMAIL_USE_SSL = True,EMAIL_USE_TLS = False. - Never set both to
True; Django raisesValueError. - Check environment variables are parsed as booleans:
"False"as a string is truthy. - Test the port from the server:
openssl s_client -connect smtp.example.com:465and-starttls smtp -connect ...:587.
Step-by-step solution
1. Match the port and mode
# STARTTLS on 587 (most providers)
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.postmarkapp.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_USE_SSL = False
EMAIL_TIMEOUT = 10
# implicit TLS on 465
EMAIL_PORT = 465
EMAIL_USE_SSL = True
EMAIL_USE_TLS = False
WRONG_VERSION_NUMBER appears with EMAIL_USE_SSL = True on port 587: Python starts a TLS handshake, and the server answers with a plain-text SMTP greeting, which looks like a garbage TLS version. STARTTLS extension not supported appears when the server does not offer STARTTLS on that port, for example port 25 on some hosts or a local relay. Connection unexpectedly closed often appears with EMAIL_USE_TLS = True on port 465, because the server waits for a TLS handshake and closes the plain connection.
2. Parse booleans correctly from the environment
EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "False") # wrong: "False" is truthy
EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "false").lower() in ("1", "true", "yes")
With django-environ, use env.bool("EMAIL_USE_TLS", default=False). A string "False" turns on TLS mode, and if EMAIL_USE_SSL is also truthy, Django refuses to send at all.
3. Test the port from the server
Network and provider problems look like TLS problems too, so test outside Django:
# implicit TLS
openssl s_client -connect smtp.postmarkapp.com:465 -quiet </dev/null | head -2
# STARTTLS
openssl s_client -starttls smtp -connect smtp.postmarkapp.com:587 -quiet </dev/null | head -2
Each should print a 220 greeting after the handshake. A timeout means the port is blocked by the host's firewall or the cloud provider; many block outbound 25 and some block 465 and 587 until you request access.
4. Send a test from Django
python manage.py sendtestemail you@example.com
This uses your settings and prints the full exception if it fails.
5. Consider an HTTP API backend
Providers such as Postmark, SendGrid, Mailgun, and Amazon SES offer HTTP APIs. With django-anymail, sending uses HTTPS on port 443, which is never blocked and has no SMTP TLS modes to get wrong. See Configure Django Anymail.
Explanation
EMAIL_USE_TLS makes Django's SMTP backend connect in plain text, read the server's capabilities, and send STARTTLS to upgrade the connection. EMAIL_USE_SSL wraps the socket in TLS before any SMTP traffic. The two are incompatible with each other's ports, and the resulting errors come from Python's ssl and smtplib modules, which describe the symptom at the protocol level, not the configuration mistake behind it.
Edge cases / notes
- Certificates. Connecting by IP or an alias that is not on the server's certificate fails verification; use the provider's documented host name.
- Local relays. A local Postfix on
localhost:25may not offer STARTTLS; set both flags toFalsefor loopback relays only. - Office 365 and Gmail. Both use STARTTLS on 587 and need app-specific authentication methods, which are separate from TLS settings.
Next steps
- For other sending failures, see Fix Django Emails Not Sending in Production.
- Review SMTP settings with Configure Django Email SMTP in Production.
- Improve inbox placement with the Django Email Deliverability Checklist.
FAQ
Should I use port 587 or 465?
Either works when matched with the right flag. Many providers recommend 587 with STARTTLS.
Can I set both EMAIL_USE_TLS and EMAIL_USE_SSL?
No. Django raises a ValueError because they are mutually exclusive.
Why does it work locally but not on the server?
Your server's host may block the port, or the environment variables there parse to different values.
Is STARTTLS less secure than SSL on 465?
Both encrypt the session. With STARTTLS, Django requires the upgrade to succeed, so it does not fall back to plain text.