Decoding the Error
Few things stall a Python project faster than a cryptic SSL error. You’re likely seeing this message in your terminal right now:
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1129)
It’s a misleading error. While it suggests your version of TLS or OpenSSL is outdated, that is rarely the case. In 99% of scenarios, this is a protocol mismatch. You are trying to speak "encrypted" to a port that only understands "plain text."
The Root Cause: A Language Barrier
This error happens when a client (like a Python script using requests) initiates a secure TLS handshake with a server port configured for standard HTTP.
Think of it as a breakdown in communication:
- The Client: Sends a "Client Hello" packet—a binary string intended to start an encrypted session.
- The Server: It isn't set up for encryption. It sees the binary data, doesn't recognize it, and responds with a plain text string like
HTTP/1.1 400 Bad Request. - The Mismatch: Your client looks at the first few bytes of that response. It expects a TLS version header (like
0x16for a handshake). Instead, it sees the letter "H" (0x48in hex) from "HTTP." OpenSSL tries to parse "H-T-T-P" as a version number, fails, and throws theWRONG_VERSION_NUMBERerror.
How to Fix It
1. Audit Your URL and Port
Start with the most obvious culprit: a typo in your connection string. Local development environments are famous for this. If your Flask or Django app is running on port 8000, it usually doesn't have SSL enabled by default.
- Wrong:
https://127.0.0.1:8000 - Right:
http://127.0.0.1:8000
If you are targeting a production API on a non-standard port (like 8080 or 8443), double-check if that specific port actually supports HTTPS. Just because a site has an SSL certificate doesn't mean every port on that server is encrypted.
2. Check Your Nginx or Apache Config
Nginx users often trip over a missing keyword. If you tell Nginx to listen on port 443 but forget to tell it to use SSL, it will serve plain HTTP on a port everyone expects to be secure.
Your Nginx server block should look like this:
server {
listen 443 ssl; # The 'ssl' keyword is the most important part here
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location / {
proxy_pass http://localhost:3000;
}
}
Without that ssl flag, Nginx ignores your certificates and sends raw text, triggering the error on the client side.
3. Fix Docker Port Mapping
Docker adds another layer where ports can get crossed. A common mistake is mapping a secure external port to an insecure internal container port. For example:
# This will break if you try to visit https://localhost
docker run -p 443:80 my-web-app
In this command, the host listens on 443, but it passes traffic to port 80 inside the container. Since the container is likely serving HTTP on port 80, the handshake fails immediately. Match your protocols: if the host port is 443, ensure the containerized app is actually configured with an SSL certificate.
4. Resolve Proxy and Load Balancer Loops
If you’re using AWS ALB, Cloudflare, or an Nginx reverse proxy, you might be dealing with "SSL Offloading." This is where the proxy handles the HTTPS connection and talks to your backend via HTTP. If your backend code tries to force an HTTPS connection to another internal service that only listens on port 80, you'll hit this error.
Verify the Fix
Don't guess—test the port directly using curl. Use the -v (verbose) flag to see exactly what the server sends back before the error occurs.
curl -vI https://your-api-endpoint.com
If the output includes * error:1408F10B:SSL routines:ssl3_get_record:wrong version number, the server is definitely sending plain text. You can also use OpenSSL to see the raw response:
openssl s_client -connect your-api-endpoint.com:443
If you see a CONNECTED(00000003) followed by a hang or a plain text HTTP error, your server config is the problem, not your Python code.
Prevention Tips
Config errors usually hide in messy YAML or JSON files. I've found that using a validator like this YAML ↔ JSON Converter helps catch structural issues in Docker Compose or Kubernetes manifests before they reach production.
- Environment Variables: Never hardcode
https://. Use variables so you can toggle betweenhttpfor local testing andhttpsfor production. - Be Explicit: When debugging, always include the port in your URL (e.g.,
:443) to ensure you aren't hitting a default you didn't intend to. - Monitor Headers: Use the
X-Forwarded-Protoheader to let your application know if the original request was secure, even if the proxy is talking to it via HTTP.

