Fix curl (35) ssl3_get_record:wrong version number on Nginx
curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number
You run curl https://example.com against your Nginx server and get the error above instead of a page. If you searched for "nginx error https . curl: (35) error:1408f10b:ssl routines:ssl3_get_record:wrong version number", the cause is almost always one of these three:
- Nginx listens on 443 without
ssl. The port is open, but it serves plain HTTP. - You're calling
https://on a plain HTTP port. Examples are:80,:8080orphp artisan serveon:8000. - A proxy setting is wrong.
https_proxypoints athttps://...while the proxy itself only speaks plain HTTP.
Short answer: curl sent a TLS handshake, and the server answered with plain HTTP. OpenSSL read the first bytes of that reply (HTTP/1.1 ...) as a TLS record header, found a "version" that makes no sense, and stopped. To fix it, make the port speak TLS (listen 443 ssl; with a certificate), or use http:// for that port.
Quick fix: add ssl to the 443 listen directive
In my experience this solves most cases. Someone writes listen 443;, adds the certificate lines, and expects Nginx to switch on TLS by itself. It doesn't. The ssl parameter on the listen line is what enables TLS on that socket.
Find your listen directives:
sudo nginx -T 2>/dev/null | grep -n "listen"
If you see listen 443; or listen [::]:443; without ssl, fix the server block:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
Test the config, then reload:
sudo nginx -t && sudo systemctl reload nginx
curl -I https://example.com
If your config uses the old standalone ssl on; directive, replace it with the listen ... ssl form. That directive was deprecated a long time ago, and recent Nginx releases reject it.
What causes the nginx error https . curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number
Go through each cause in turn. Each one has a quick check, so you aren't guessing.
Cause 1: Port 443 serves plain HTTP
How to confirm: request port 443 with plain HTTP.
curl -v http://example.com:443/
If this returns your normal page or a normal Nginx response, port 443 is not doing TLS. The access log also shows it clearly. Nginx logs the raw TLS handshake bytes as a broken request line:
sudo tail -n 20 /var/log/nginx/access.log
203.0.113.5 - - [...] "\x16\x03\x01\x02\x00\x01\x00\x01\xFC\x03\x03..." 400 157 "-" "-"
\x16\x03\x01 is the start of a TLS ClientHello. If you see it in the access log, your 443 listener is plain HTTP.
Fix: add ssl as shown in the quick fix. Check every server block that listens on 443, not just the one you edited, and pay special attention to any default_server block. Also check that the site is actually enabled:
ls -l /etc/nginx/sites-enabled/
If your HTTPS site exists in sites-available but has no symlink in sites-enabled, a different block is answering on 443. Create the link:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Cause 2: https:// used on an HTTP-only port
This catches a lot of Laravel developers. php artisan serve and most dev servers only speak plain HTTP, so curl https://127.0.0.1:8000 gives exactly this error. Use http://127.0.0.1:8000 instead. If the dev server won't start in the first place, see php artisan serve: Failed to Listen on 127.0.0.1:8000.
The same applies to https://example.com:80 or to any app port behind Nginx, such as Node or Gunicorn on :3000 or :8080.
How to confirm: remove the s.
curl -I http://example.com:8080/
If that works, the port is plain HTTP.
Fix: use the correct scheme for that port. To expose the app over HTTPS, let Nginx handle TLS on 443 and forward to the app over plain HTTP:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
The proxy_pass target must be http:// unless the backend really serves TLS. Pointing proxy_pass https://... at a plain backend produces the same "wrong version number" error, and it shows up in Nginx's error log.
Cause 3: Docker or another service owns port 443
A Docker mapping like -p 443:80 forwards host port 443 to the container's plain HTTP port. In that case no TLS ever happens, even though the port number looks right.
How to confirm:
sudo ss -tlnp | grep ':443'
docker ps --format '{{.Names}}\t{{.Ports}}'
Fix: map 443 to a container port that actually terminates TLS. Alternatively, keep the container on an internal HTTP port and let host Nginx handle 443 with ssl.
Cause 4: Wrong https_proxy value
Modern curl can talk TLS to the proxy itself. If your environment has https_proxy=https://proxy.local:3128 and the proxy only speaks HTTP, the handshake fails before your server is even contacted.
How to confirm:
env | grep -i proxy
curl -v --noproxy '*' https://example.com
If the request works with --noproxy, the proxy setting is the problem.
Fix: point the variable at the proxy using http://. It still tunnels HTTPS traffic with CONNECT:
export https_proxy=http://proxy.local:3128
Variations of the "wrong version number" error
The wording depends on your OpenSSL version. On OpenSSL 3 you will usually see:
curl: (35) OpenSSL/3.0.13: error:0A00010B:SSL routines::wrong version number
It has the same meaning and the same fixes. PHP's curl_exec(), Guzzle and Laravel's Http:: client report the same underlying error, often as cURL error 35.
If nothing worked: gather the evidence
Collect these before you change anything else:
# Full handshake view from the client side
curl -v https://example.com 2>&1 | head -n 30
# What the server actually answers on 443
openssl s_client -connect example.com:443 -servername example.com </dev/null
# Effective config, exactly as Nginx loaded it
sudo nginx -T 2>/dev/null | grep -nE "server_name|listen|ssl_certificate"
# Recent errors
sudo tail -n 50 /var/log/nginx/error.log
If openssl s_client prints wrong version number and no certificate, the listener isn't doing TLS at all. Go back to Cause 1 or Cause 3. If it shows a certificate chain, the server side is fine, and the problem is on the client side: a proxy, a wrong port in the URL, or a stale DNS record pointing to a different machine. Running dig +short example.com quickly rules out that last one.
FAQ
What does "wrong version number" mean in curl?
curl expected a TLS record but got something else, usually a plain HTTP response. OpenSSL reads those bytes as a protocol version and rejects them. Despite the name, it is almost never a real TLS version mismatch.
Does curl -k fix this error?
No. -k only skips certificate verification, and this error happens before any certificate is exchanged. The handshake itself fails, so you have to fix the port or scheme.
Why does the site open in my browser but fail with curl?
The browser and curl are probably not hitting the same thing. The browser may follow a redirect from port 80, use different proxy settings, or show a cached page. Run curl -v with the exact URL from the address bar to compare.
Is error 0A00010B the same as 1408F10B?
Yes. 0A00010B is the OpenSSL 3 code for the same "wrong version number" condition that OpenSSL 1.1 reports as 1408F10B. The causes and fixes are identical.
To sum up: the nginx error https . curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number means TLS went in and plain HTTP came back. Find which listener is answering on that port, give it listen 443 ssl with a certificate, or use http://, and the error goes away.


