Fixing the SSL: WRONG_VERSION_NUMBER Error Once and For All

beginner🔒 SSL/TLS2026-04-25| Python (Requests, Urllib, Flask, Django), Nginx, Docker, Node.js, OpenSSL

Error Message

ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1129)
#ssl#python#debugging#nginx#docker#devops

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 0x16 for a handshake). Instead, it sees the letter "H" (0x48 in hex) from "HTTP." OpenSSL tries to parse "H-T-T-P" as a version number, fails, and throws the WRONG_VERSION_NUMBER error.

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 between http for local testing and https for 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-Proto header to let your application know if the original request was secure, even if the proxy is talking to it via HTTP.

Related Error Notes