Cause #1: You're using http:// instead of https://
This is the most common reason I see this error. Your dev server is listening on HTTPS, but you pointed curl at an HTTP URL. The server sees a plain HTTP request over a TLS socket and responds with a TLS alert — hence the wrong version number.
Check your command. If you wrote curl http://localhost:3000 but the server is HTTPS, swap it to:
curl https://localhost:3000If you're using a self-signed cert, add -k to skip certificate verification:
curl -k https://localhost:3000This tripped me up the first time I ran a Vite dev server with HTTPS enabled. Vite logs the URL as https://localhost:5173 but I kept typing http out of habit.
Cause #2: Wrong port — pointing to an HTTP-only service
Sometimes the server isn't HTTPS at all. You think it is, but it's actually plain HTTP on a different port. For example, your Node.js app might be running on port 8080 as HTTP while you're trying https://localhost:8080. That will trigger the exact same error because the server speaks plain HTTP, not TLS.
Verify what's actually listening on the port. On Linux/macOS, use:
lsof -i :8080On Windows, use:
netstat -ano | findstr :8080If the output shows a plain HTTP server, use http:// instead. Or if you need HTTPS, configure the server to use TLS properly.
Cause #3: Proxy or load balancer terminating TLS but forwarding as HTTP
In a development setup with a reverse proxy (like nginx or a Node.js proxy), the external connection might be HTTPS, but the backend talks plain HTTP. If you're hitting the proxy with https:// and it forwards to an HTTP backend, that's fine. But if you're hitting the backend directly with https://, you'll get the wrong version error.
The fix: make sure you're connecting to the TLS-terminating endpoint, not the raw backend. For example, if your nginx listens on port 443 and proxies to http://127.0.0.1:3000, then use curl https://localhost (or https://localhost:443 if non-standard). Don't try https://localhost:3000 unless that port itself is TLS-enabled.
You can check what protocol a port speaks using OpenSSL:
openssl s_client -connect localhost:3000If it fails with a similar error, the port isn't TLS. If it succeeds and shows certificates, it's HTTPS — good.
Quick check with curl verbose
Run curl -v https://localhost:3000 and look at the output. If you see Connected to localhost and then an SSL error, you're at least on the right port. If you see nothing SSL-related, you're probably hitting a plain HTTP port.
Quick-reference summary
| Scenario | Fix |
|---|---|
| Server is HTTPS but you used http:// | Use https:// in your curl command |
| Port is actually HTTP (not TLS) | Use http:// or enable TLS on that port |
| Proxy or load balancer in front | Hit the TLS endpoint, not the backend |
| Self-signed cert | Add -k to skip verification |
One more thing: if you're using a hostname like localhost and your server only listens on 127.0.0.1, you might be connecting to a different service. Always check the exact bind address of your server.