How to Resolve Python Error "ssl.SSLError: [SSL: WRONG_VERSION_NUMBER]"
The ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number
is a common Python error encountered when establishing a secure connection (SSL/TLS). It fundamentally indicates a mismatch in the expected communication protocol during the initial handshake phase, most often because the client and server disagree on whether SSL/TLS should be used immediately on the connected port, or if plain communication should start first.
This guide explains the common causes of this error, particularly in contexts like smtplib
(email), requests
/urllib3
(HTTP), and Django email configurations, and provides clear solutions.
Understanding the Error: SSL/TLS Handshake and Protocol Mismatch
Secure connections (like HTTPS or SMTPS) use Transport Layer Security (TLS) or its predecessor, Secure Sockets Layer (SSL), to encrypt data. Establishing this connection involves a "handshake" where the client and server negotiate the protocol version and encryption methods.
The WRONG_VERSION_NUMBER
error typically occurs during this initial handshake. It means one side (e.g., your Python client) started the handshake expecting a certain SSL/TLS protocol negotiation, but the other side (the server) responded with data that doesn't conform to that expected protocol version for that specific port. This usually happens because:
- The client connected to a port expecting immediate SSL/TLS (like port 465 for SMTPS) but tried to speak plain text first.
- The client connected to a port expecting plain text first, followed by an upgrade to TLS (like port 587 for SMTP with STARTTLS), but tried to initiate an SSL handshake immediately.
- Less commonly, actual SSL/TLS version incompatibilities or proxy interference.
Common Cause 1 (smtplib
): Incorrect Port for SSL vs. TLS
This is a frequent cause when sending emails using Python's smtplib
. Email servers use different ports for different connection methods:
- Port 465 (SMTPS): Expects an immediate SSL connection upon connecting. Use with
smtplib.SMTP_SSL
. - Port 587 (SMTP with STARTTLS): Expects an initial plain text connection, which is then upgraded to secure TLS using the
STARTTLS
command. Use withsmtplib.SMTP
followed byserver.starttls()
.
The error occurs if you mismatch the smtplib
class/method and the port number.
Error Scenario (SMTP_SSL
with Non-SSL Port)
Using SMTP_SSL
(which initiates SSL immediately) but connecting to port 587 (which expects plain text first) is a common mistake.
# Error Scenario
import smtplib
port = 587 # Incorrect port for SMTP_SSL
smtp_server = 'smtp.gmail.com'
sender_email = '[email protected]'
password = 'your_password'
receiver_email = "[email protected]"
message = "Subject: Test\n\nThis is a test."
try:
# Using SMTP_SSL which expects immediate SSL on port 465
# But connecting to port 587 which expects plain text first
# ⛔️ ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number
with smtplib.SMTP_SSL(smtp_server, port) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)
except smtplib.SMTPConnectError as e:
print(f"Connect Error: {e}")
except smtplib.SMTPServerDisconnected as e:
print(f"Server Disconnected: {e}")
except smtplib.SMTPAuthenticationError as e:
print(f"Auth Error: {e}")
except ConnectionRefusedError as e:
print(f"Connection Refused: {e}")
except Exception as e: # Catching generic Exception to show the SSL error if others don't match
print(f"Caught Error: {e}") # Will likely print the SSLError here
Solution: Match Port and Class/Method (465 for SMTP_SSL
, 587 for starttls
)
Ensure the port number matches the connection method:
- If using
smtplib.SMTP_SSL
: Use port 465.import smtplib
port = 465 # ✅ Correct port for SMTP_SSL
# ... other variables ...
try:
with smtplib.SMTP_SSL(smtp_server, port) as server:
# ... login and send ...
print("Email sent successfully using SMTP_SSL on port 465.")
except Exception as e: print(f"Error: {e}") - If using
smtplib.SMTP
and upgrading withserver.starttls()
: Use port 587. (See Recommended Approach below).
Recommended Approach: Use TLS on Port 587
Using explicit TLS via the STARTTLS
command on port 587 is generally preferred over implicit SSL on port 465 (which is somewhat deprecated).
# ✅ Recommended: TLS on port 587
import smtplib
import ssl
port = 587 # ✅ Correct port for STARTTLS
smtp_server = 'smtp.gmail.com'
sender_email = '[email protected]'
password = 'your_password'
receiver_email = "[email protected]"
message = "Subject: Test TLS\n\nThis is a test using TLS."
# Create a default SSL context for security
context = ssl.create_default_context()
try:
# Connect using standard SMTP class
with smtplib.SMTP(smtp_server, port) as server:
# Upgrade the connection to secure TLS
server.starttls(context=context) # ✅ Upgrade step
# Login and send over the secure connection
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)
print("Email sent successfully using STARTTLS on port 587.")
except Exception as e:
print(f"Error: {e}")
Common Cause 2 (requests
/urllib3
): Proxy or Library Issues
While less common, you might see this error when making HTTPS requests, potentially due to issues with underlying libraries like urllib3
or proxy configurations.
Solution: Upgrade urllib3
Older versions of urllib3
(a dependency of requests
) sometimes had bugs related to SSL/TLS handling. Upgrading it might resolve the issue.
pip install --upgrade urllib3
# Or use
pip3 / python -m pip
After upgrading, retry your requests
code.
Solution: Check HTTPS Proxy Configuration
If you are using a proxy for HTTPS traffic, there's a known interaction/bug in some older setups where the https
proxy URL needs to be specified using the http://
protocol prefix in the proxies
dictionary for requests
. This tells requests
to connect to the proxy using plain HTTP first, and then the proxy handles the subsequent TLS connection to the destination. This is counter-intuitive.
import requests
https_proxy_url = "http://your-proxy-address:proxy-port" # Note: uses http://
proxies = {
"http": "http://your-proxy-address:proxy-port", # Optional: http proxy
"https": https_proxy_url # ✅ Specify HTTPS proxy using http:// URL
}
target_url = "https://example.com" # The secure site you want to reach
try:
response = requests.get(
target_url,
proxies=proxies,
verify=True # Keep SSL verification if possible
)
response.raise_for_status()
print(f"Successfully connected to {target_url} via proxy.")
except requests.exceptions.RequestException as e:
print(f"Request failed via proxy: {e}") # Error might occur here if proxy setup is wrong
Consult the requests
documentation on proxies for more details. Using an https://
URL for the https
proxy key often leads to the WRONG_VERSION_NUMBER
error because requests
tries to establish TLS with the proxy itself immediately, which the proxy might not expect.
Common Cause 3 (Django): Incorrect Email Settings
In Django projects using SMTP for email, incorrect configuration in settings.py
can cause this error when sending emails.
Solution: Match EMAIL_PORT
to EMAIL_USE_TLS
/EMAIL_USE_SSL
Django needs to know which security protocol and port to use. Ensure your settings are consistent:
- If using TLS (Recommended):
# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.example.com' # Your SMTP host
EMAIL_USE_TLS = True # ✅ Use TLS
EMAIL_USE_SSL = False # ✅ Mutually exclusive with TLS
EMAIL_PORT = 587 # ✅ Port for TLS
EMAIL_HOST_USER = '[email protected]'
EMAIL_HOST_PASSWORD = 'your_password' - If using SSL:
# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.example.com'
EMAIL_USE_SSL = True # ✅ Use SSL
EMAIL_USE_TLS = False # ✅ Mutually exclusive with SSL
EMAIL_PORT = 465 # ✅ Port for SSL
EMAIL_HOST_USER = '[email protected]'
EMAIL_HOST_PASSWORD = 'your_password'
Crucially:
- Set either
EMAIL_USE_TLS
orEMAIL_USE_SSL
toTrue
, not both. - Set
EMAIL_PORT
to587
ifEMAIL_USE_TLS
isTrue
. - Set
EMAIL_PORT
to465
ifEMAIL_USE_SSL
isTrue
.
Mismatching these settings (e.g., EMAIL_USE_SSL = True
with EMAIL_PORT = 587
) will likely cause the WRONG_VERSION_NUMBER
error.
Conclusion
The ssl.SSLError: [SSL: WRONG_VERSION_NUMBER]
typically indicates a protocol mismatch during the SSL/TLS handshake, often related to incorrect port usage.
Key solutions include:
- For
smtplib
: Ensure you use port465
withsmtplib.SMTP_SSL
or (preferably) port587
withsmtplib.SMTP
followed byserver.starttls()
. - For
requests
/urllib3
: Try upgradingurllib3
. If using proxies for HTTPS, ensure the proxy URL specified for thehttps
key in theproxies
dict uses anhttp://
prefix. - For Django: Correctly configure
settings.py
, ensuringEMAIL_PORT
(587 or 465) matches theEMAIL_USE_TLS
(True/False) orEMAIL_USE_SSL
(True/False) setting, with only one of the latter beingTrue
.
By aligning the expected protocol (implicit SSL vs. explicit STARTTLS) with the correct port number and ensuring library compatibility, you can effectively resolve this SSL error.