This OpenSSL error means the first bytes your client received back were not a valid TLS record. Node.js tried to start a TLS handshake, and the thing on the other end answered with something that was not TLS. Despite the wording, it is rarely about a real version mismatch.
Error: write EPROTO 00BC0EA101000000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:355:
errno: -71, code: 'EPROTO', syscall: 'write'
On older Node versions built on OpenSSL 1.1 the code reads error:1408F10B:SSL routines:ssl3_get_record:wrong version number. In browsers, the same underlying cause appears as ERR_SSL_PROTOCOL_ERROR; see ERR_SSL_PROTOCOL_ERROR. Node may also report ERR_SSL_WRONG_VERSION_NUMBER as the code in some paths.
Decision order
- Check the URL scheme and port: are you using
https://against a port that serves plain HTTP? - Check whether a proxy is involved and how it is configured.
- Check that the server really speaks TLS on that port.
- Only then consider genuine protocol-version incompatibility.
Cause 1: HTTPS to a plain HTTP port (most common)
An HTTP server received the TLS ClientHello, did not understand it, and answered with a plain-text 400 Bad Request or similar. Node reads that text as a broken TLS record. Test:
curl -v https://example.com:8080/
curl -v http://example.com:8080/
openssl s_client -connect example.com:8080 -servername example.com </dev/null
If the http:// call works and https:// fails, the port is plain HTTP. Fix by using http://, or by enabling TLS on that listener. In nginx, TLS requires listen 443 ssl; (and a certificate); a bare listen 8080; serves cleartext. In Express or Node itself, https.createServer({ key, cert }, app) is TLS and http.createServer(app) is not. Mixing them up in your own code, such as using the https module to call a local http://localhost:3000 service, is a common cause in development.
// Wrong: https agent talking to an HTTP-only dev server
https.get('http://localhost:3000/'); // throws ERR_INVALID_PROTOCOL
// Wrong: HTTPS URL pointing at a plain HTTP port
https.get('https://localhost:3000/'); // EPROTO wrong version number
Cause 2: HTTPS proxy settings written with the wrong scheme
A frequent version: HTTPS_PROXY=https://proxy.corp:3128 where the proxy actually listens for plain HTTP. Most corporate proxies accept an unencrypted CONNECT on their own port even for HTTPS traffic, so the correct value is http://proxy.corp:3128. The same error shows up in npm install, git, pip and curl through that proxy. Fix the scheme:
npm config set proxy http://proxy.corp:3128
npm config set https-proxy http://proxy.corp:3128
Cause 3: a non-TLS service on the port
You may be talking to a different service than expected: a database, SSH, an admin console, or a container that exited and left another process on the host port. Identify the listener with sudo ss -ltnp 'sport = :8443', and check from outside with the Port Checker. See Node EADDRINUSE for tracing port owners.
Cause 4: TLS terminated somewhere unexpected
A load balancer may accept TLS on 443 but forward plain HTTP to the backend, while your code connects to the backend port with https://. Or the reverse: the service expects TLS (mutual TLS or an internal CA) and you send cleartext. Draw the path: client, CDN, load balancer, proxy, app. Note where TLS starts and ends at each hop.
Cause 5: real version or cipher incompatibility
Rarely, the server only supports old protocols such as TLS 1.0 or 1.1, or the client enforces a higher minimum than the server offers. Check what the server accepts:
openssl s_client -connect example.com:443 -tls1_2 </dev/null
openssl s_client -connect example.com:443 -tls1_3 </dev/null
The TLS Version Checker lists the versions a public server offers, and the SSL Checker verifies the certificate. If versions are the issue, upgrade the server rather than loosening the client. Node's minVersion option for tls exists to enforce a floor; lowering it to accept TLS 1.0 weakens security and should be a last resort for a legacy system you cannot change. The browser-side counterpart is ERR_SSL_VERSION_OR_CIPHER_MISMATCH.
Cause 6: a captive portal or middlebox
On guest Wi-Fi or filtered networks, a device may intercept port 443 and answer with an HTML page. Test from another network.
Verify
curl -sv https://example.com/ -o /dev/null 2>&1 | grep -E "SSL connection|TLS|HTTP/"
You should see a TLS version line and an HTTP status. Then rerun your Node program.
Next steps
- Check protocol support with the TLS Version Checker.
- Verify the certificate with the SSL Checker.
- Read SSL/TLS certificate errors explained.
- Probe the port with the Port Checker.
FAQ
Is my Node.js version too old or too new?
Seldom. The error usually means a non-TLS response, not a version gap. Check the scheme and port first.
Why does the browser show a different message?
Chrome reports the same failure as ERR_SSL_PROTOCOL_ERROR. The wording differs, but the cause is often identical.
Should I set NODE_TLS_REJECT_UNAUTHORIZED=0?
No. That disables certificate verification and does not fix a wrong-version error. It only hides certificate problems.
What does EPROTO mean?
It is the system error for a protocol error. Node uses it when the TLS layer fails during the handshake.
Get notified when this fix updates
Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.
This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.