Troubleshooting
Email delivery

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.

Why the handshake failsDjango settingsTLS or SSL modeConnects to port587 or 465Modes disagreeplain vs TLSSSL errorwrong versionPort 587 pairs with EMAIL_USE_TLS and port 465 with EMAIL_USE_SSL, never the other way.
  1. Port 587: EMAIL_USE_TLS = True, EMAIL_USE_SSL = False.
  2. Port 465: EMAIL_USE_SSL = True, EMAIL_USE_TLS = False.
  3. Never set both to True; Django raises ValueError.
  4. Check environment variables are parsed as booleans: "False" as a string is truthy.
  5. Test the port from the server: openssl s_client -connect smtp.example.com:465 and -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.

which flag goes with which portEncryptionDjango settingPort 587STARTTLS upgradeEMAIL_USE_TLSPort 465TLS from the startEMAIL_USE_SSLPort 25!often blockedavoid on cloudPort 2525STARTTLS (some)EMAIL_USE_TLSOne flag per port, never bothdjango-deployment.com
Check your provider’s documentation; most recommend 587 with STARTTLS.

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.

error and mismatchErrorMismatch and fixWRONG_VERSION_NUMBERSSL mode on a STARTTLSport; use TLS on 587STARTTLS not supportedport offers no upgrade; use 587 or SSLconnection unexpectedly closedTLS mode on port 465;use EMAIL_USE_SSLtimed outport blocked by host;test with opensslThree of the four are a port and flag mismatchdjango-deployment.com
Fix the flag first; check the firewall only if the error is a timeout.

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:25 may not offer STARTTLS; set both flags to False for 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

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.

Keep going