Back to Guides
TroubleshootingNEW

ERR_SSL_VERSION_OR_CIPHER_MISMATCH: What It Means and How to Fix It

ERR_SSL_VERSION_OR_CIPHER_MISMATCH means the browser and server share no TLS version or cipher suite. Diagnose it in one command, then fix it on nginx, Apache, IIS, F5, or Java.

14 min readAugust 2026Troubleshooting Guide
ERR_SSL_VERSION_OR_CIPHER_MISMATCH browser error - diagnosing TLS version and cipher suite mismatches

Quick answer

ERR_SSL_VERSION_OR_CIPHER_MISMATCH is Chrome telling you that it and the server could not agree on a TLS version or a cipher suite, so the handshake ended before any data moved. It is almost always a server configuration problem, not a browser problem.

Run this first:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>&1 | grep -E "Protocol|Cipher"
  • No protocol or cipher printed: the server offers nothing modern. Go to the fixes below.
  • Protocol: TLSv1 or TLSv1.1: Chrome removed both in Chrome 84 (July 2020). Enable TLS 1.2 and 1.3.
  • Cipher: 0000 or a handshake failure alert: no cipher overlap. Your cipher list is either empty, all-legacy, or mismatched to your certificate's key type.

The other clients say the same thing in different words: Firefox reports SSL_ERROR_NO_CYPHER_OVERLAP, and OpenSSL and curl surface "sslv3 alert handshake failure" (client side) or "no shared cipher" (server side). Same root cause, different string.

What ERR_SSL_VERSION_OR_CIPHER_MISMATCH actually means

A TLS handshake opens with the client sending a ClientHello listing every protocol version and cipher suite it is willing to use. The server picks one from that list. If the intersection of the two lists is empty, there is nothing to pick, and the server sends a fatal handshake_failure alert. Chrome renders that as ERR_SSL_VERSION_OR_CIPHER_MISMATCH.

The word "or" in the error name is doing real work. Chrome cannot tell you which of the two failed, because from its side both look identical. That is why you diagnose with openssl s_client rather than guessing.

There are four causes:

#CauseTell
1Server only speaks TLS 1.0/1.1s_client negotiates TLSv1 or TLSv1.1; works in old curl, fails in every current browser
2No cipher overlaps_client -tls1_2 fails but -tls1_2 -cipher 'ALL' against a legacy suite succeeds
3Cipher list doesn't match the certificate keyECDSA cert with an RSA-only cipher list, or vice versa
4RC4 or 3DES-only serverLong-abandoned appliance or embedded management interface

A note on what is not on this list: a weak or expired certificate does not produce this error. ERR_SSL_VERSION_OR_CIPHER_MISMATCH is a negotiation failure and happens before certificate validation is reached. A SHA-1 chain gives you a certificate interstitial (ERR_CERT_WEAK_SIGNATURE_ALGORITHM, or ERR_CERT_INVALID in current Chromium), not this. If you are seeing a click-through warning page rather than a hard failure, you are debugging the wrong problem.

Cause 3 is the one that burns people. If you deploy an ECDSA certificate but your ssl_ciphers line only contains ECDHE-RSA suites, there is no suite the server can actually use with the key it has. The config looks modern and still fails.

Step 1: See what the server actually offers

Do not trust the config file. Read it off the wire.

Negotiated protocol and cipher:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>&1 \
  | grep -E "Protocol|Cipher|Verify return"

Full enumeration of what the server accepts, and this is the one that finds the answer:

nmap --script ssl-enum-ciphers -p 443 example.com

The ssl-enum-ciphers script walks every protocol version and every suite and prints what the server agreed to, with a letter grade per suite. If the output shows only TLS_RSA_ and TLS_DHE_ entries, you have found your problem.

Test one specific version:

openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null

A clean connection on -tls1_2 and a failure on the default invocation usually means a protocol-floor problem elsewhere in the path, such as a load balancer or WAF in front of the origin.

Map OpenSSL names to IANA names. This trips up almost everyone: OpenSSL calls a suite AES128-GCM-SHA256 where the RFC and the IANA registry call it TLS_RSA_WITH_AES_128_GCM_SHA256. When you are reading a compliance finding written in IANA names and a config file written in OpenSSL names, translate before you edit:

openssl ciphers -V -stdname 'ALL:COMPLEMENTOFALL'

The -stdname flag (OpenSSL 1.1.1 and later) prints both names side by side. Our Cipher Suite Decoder does the same thing interactively if you would rather paste one in.

If you just want a verdict on a live host without installing anything, the SSL/TLS Configuration Checker will report protocol and suite support from the browser.

Step 2: The target configuration

For every platform below, the destination is the same:

  • Protocols: TLS 1.2 and TLS 1.3 only.
  • Key exchange: ECDHE only.
  • Bulk cipher: AES-GCM or ChaCha20-Poly1305. No CBC, no RC4, no 3DES.
  • Removed entirely: anything whose IANA name starts with TLS_RSA_, TLS_DHE_, TLS_DH_, or TLS_PSK_DHE_. Static TLS_ECDH_ suites (note: not TLS_ECDHE_) should go too, because RFC 10015 discourages them.

In OpenSSL syntax that is six suites:

ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305

Keep both the ECDSA and RSA variants even if you only deploy one certificate type today. They cost nothing and they save you from cause 3 the day someone swaps the certificate.

One caveat on generated configs: Mozilla's SSL Configuration Generator is the usual starting point, and it has already caught up: as of guidelines 6.0 the "intermediate" profile contains exactly the six ECDHE suites above and no DHE at all. Older copies did carry DHE as a compatibility hedge, and 5.7's intermediate profile has three of them (DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-CHACHA20-POLY1305). If you are working from a config generated a while ago, or from an internal standard that was copied from one, strip any entry beginning DHE-.

Step 3: Fix it, by platform

nginx

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;

Two things people miss. First, ssl_ciphers governs TLS 1.2 and below only. TLS 1.3 suites are separate, and nginx leaves them at the OpenSSL default unless you set ssl_conf_command Ciphersuites (nginx 1.19.4 and later). The TLS 1.3 defaults are already fine, so leaving them alone is correct. Second, if you had an ssl_dhparam directive, you can delete it once DHE is gone; it has no effect on ECDHE.

nginx -t && systemctl reload nginx

For the full nginx HTTPS setup beyond ciphers and protocols, see nginx SSL Certificate Configuration.

Apache (mod_ssl)

SSLProtocol             -all +TLSv1.2 +TLSv1.3
SSLHonorCipherOrder     off
SSLCipherSuite          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305

The +TLSv1.3 option requires httpd 2.4.36 or later built against OpenSSL 1.1.1. As with nginx, SSLCipherSuite covers TLS 1.2 and below; for TLS 1.3 you must use the protocol-specifier form, SSLCipherSuite TLSv1.3 followed by the suite list, on 2.4.36 and later. Note that form applies at vhost level only.

apachectl configtest && systemctl reload httpd

For the full Apache HTTPS setup, see Apache SSL Certificate Configuration.

IIS / Windows Schannel

Windows does not read a cipher string. Two approaches.

PowerShell (Windows Server 2016 and later), per suite:

Get-TlsCipherSuite | Select-Object -ExpandProperty Name
Disable-TlsCipherSuite -Name 'TLS_RSA_WITH_AES_128_GCM_SHA256'
Disable-TlsCipherSuite -Name 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256'

Enumerate with Get-TlsCipherSuite first, then disable every returned name matching TLS_RSA_, TLS_DHE_, or TLS_DH_. Re-run Get-TlsCipherSuite afterwards to confirm the list actually shrank. Microsoft does not document whether the change survives a reboot, so verify on the box rather than assuming, and bake the commands into your build image if you need them to stick.

Prefer the cmdlets. You will also find the registry route in circulation:

HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms\PKCS
HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms\Diffie-Hellman

PKCS is Microsoft's name for RSA key exchange, and both subkeys are documented. But be careful here: the "Enabled = 0" DWORD that every blog post tells you to add under these keys traces back to KB 245030, which Microsoft has retired. The current TLS registry documentation lists only ClientMinKeyBitLength under KeyExchangeAlgorithms, documents Enabled only under the SCHANNEL Protocols keys, and explicitly warns against values not explicitly detailed in that article. It may well work; it is not supported, and it is not what you want to be defending in a change review. Use Disable-TlsCipherSuite.

Two things are true regardless of which route you take. Schannel changes are machine-wide, so they hit SQL Server, RDP, WinRM, and SMB, not just IIS. Test on one node. And Microsoft notes that Schannel registry settings and per-application SSPI settings can override CNG cryptographic configuration, so verify on the wire rather than trusting the config. If a change appears not to have taken, restart the service and then reboot before concluding it failed.

For certificate installation, SNI bindings, and the rest of the IIS setup, see IIS SSL Certificate Configuration.

F5 BIG-IP

Do not paste an OpenSSL cipher string into a BIG-IP profile. This is the single biggest trap on this platform. F5's own documentation states that TMM ciphers use a slightly different naming scheme from the IANA and OpenSSL cipher naming schemes, and that although the format looks similar, the results are not always the same. The OpenSSL keyword families kRSA, aRSA, kDHE, aNULL, and eNULL do not appear in current F5 documentation at all; they belonged to the COMPAT (OpenSSL) stack, which F5 removed in 14.0.0. BIG-IP rejects unrecognised keywords outright:

01070312:3: Invalid keyword <cipher> in ciphers list for profile <profile>

and the configuration can fail to load. F5 also spells things differently: AES-GCM, not AESGCM; CHACHA20-POLY1305, not CHACHA20.

So build the list from named suites and preview it before you apply it:

tmm --clientciphers 'ECDHE:ECDHE_ECDSA'

The tmm --clientciphers command prints the exact suites a string resolves to on your specific version. Run it, read the output, and confirm no RSA-keyed or DHE-keyed rows remain before you touch a profile. If BIG-IP rejects a keyword, the error above tells you which one; check your version's cipher string reference rather than guessing. Then apply:

tmsh modify ltm profile client-ssl /Common/my_clientssl ciphers 'ECDHE:ECDHE_ECDSA'

From v13.0 onward, cipher rules and cipher groups are the better mechanism. They compose, they are readable, and they survive upgrades more gracefully than a hand-tuned string. F5 documents both as supported and calls the choice optional, so a raw cipher string is not wrong; it is just harder to maintain. If you are on v13 or later and touching this anyway, build the rule.

If you are new to where cipher settings live on BIG-IP, start with F5 SSL Profiles Explained. For a symptom-based runbook covering this and other F5 SSL failures, see F5 SSL Troubleshooting.

HAProxy

All three directives are global section keywords. Pasted without the header, the config will not load:

global
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2

Note the two separate directives: ssl-default-bind-ciphers is TLS 1.2 and below, ssl-default-bind-ciphersuites is TLS 1.3. Setting only the first leaves TLS 1.3 on defaults, which is fine, but setting only the second does nothing for your TLS 1.2 problem.

For the full HAProxy setup including termination and passthrough, see HAProxy SSL Certificate Configuration.

Java / JVM

In the java.security file, at conf/security/java.security under JAVA_HOME on JDK 9 and later, or lib/security/java.security on a JDK 8 install:

jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
    DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
    TLS_RSA_*, TLS_DHE_*, TLS_DH_*

The asterisk is mandatory. The jdk.tls.disabledAlgorithms property matches cipher suites by wildcard, and the JDK only compiles entries that actually contain an asterisk. Write TLS_RSA_ without one and it is silently ignored: no error, no warning, no effect, and a config that looks correct in review. TLS_RSA_ followed by an asterisk matches every suite beginning with that string, which saves enumerating dozens of IANA names by hand.

Worth knowing before you get surprised by it: the JDK already made this change. TLS_RSA suites are disabled by default from JDK 24 onward, and the change was backported to the LTS lines in the January 2026 CPU, specifically 8u481, 11.0.30, 17.0.18, and 21.0.10. Teams have hit connection failures immediately after those patches, with MQTT brokers and older middleware the common casualties, because the client stopped offering RSA key exchange and the server had nothing else to fall back on. If a handshake started failing the day after a JDK update, this is almost certainly why, and the fix belongs on the server, not in re-enabling the suite on the JVM.

For PKIX errors and other JVM handshake failures, see Java SSL Troubleshooting.

Why this error is about to get more common

On 16 July 2026 the IETF published RFC 10015, "Deprecating Obsolete Key Exchange Methods in TLS 1.2 and DTLS 1.2". It is Standards Track, and it updates 17 existing RFCs including BCP 195 / RFC 9325.

What changed:

Key exchangeRFC 9325RFC 10015
Static RSASHOULD NOTMUST NOT
Ephemeral FFDH (DHE)SHOULD NOTMUST NOT
Non-ephemeral FFDHSHOULD NOTMUST NOT
Fixed-DH certificate typesunspecifiedSHOULD NOT
Non-ephemeral ECDHSHOULD NOTno change

IANA has set the "Recommended" column to D, meaning Discouraged per RFC 9847, on 222 cipher suites:

FamilyCountStatus
TLS_RSA_ (static RSA)52MUST NOT
TLS_DHE_ and TLS_PSK_DHE_ (ephemeral FFDH)69MUST NOT
TLS_DH_ (static FFDH, including 22 anon)62MUST NOT
TLS_ECDH_ (static ECDH, including 5 anon)39SHOULD NOT

That is 183 MUST NOT and 39 SHOULD NOT. Four ClientCertificateType identifiers were flagged as well: rsa_fixed_dh, dss_fixed_dh, rsa_fixed_ecdh, and ecdsa_fixed_ecdh.

The entries were not removed from the registry; they were re-marked. The MUST NOT lives in RFC 10015's normative text, not in the registry label. If you are citing this in a finding, cite the RFC.

The reasoning is worth understanding, because it is not the same for both families:

  • RSA key exchange has no forward secrecy by construction. The client encrypts the premaster secret to the server's public key. Anyone holding a recording of the traffic can decrypt all of it retroactively the day that private key leaks. It is also the perennial home of Bleichenbacher-style padding oracles. ROBOT, DROWN, and the cross-protocol attacks all live here, and the countermeasure keeps being implemented wrong.
  • DHE does have forward secrecy. Its problem is structural. In RFC 10015's words, "there is no mechanism for negotiating the group". The server chooses, custom groups are widespread because that was the standard advice after Logjam, and a client cannot practically verify that a server-supplied group is safe or fall back to one it trusts. RFC 7919 did define a negotiation mechanism for TLS 1.2, but adoption never reached the point where a client could rely on it, which is why the RFC treats the gap as unfixable in practice and deprecates the suites instead. Key reuse also exposes DHE to the Raccoon timing attack.

TLS 1.3 is unaffected. It never supported static RSA or non-ephemeral DH, and its FFDHE groups are negotiated properly, so they remain permitted. Nothing to change on a TLS 1.3-only endpoint. See TLS 1.2 vs TLS 1.3 for the full protocol comparison.

The practical consequence: as scanners, hardening baselines, and platform defaults absorb RFC 10015, endpoints that only ever negotiated TLS_RSA_ or TLS_DHE_ suites will start failing against updated clients. That failure looks like ERR_SSL_VERSION_OR_CIPHER_MISMATCH.

What not to do

Do not re-enable weak ciphers to make the error go away. Chrome's cipher-suite-blacklist flag and the various "allow insecure content" policies do nothing but move the vulnerability somewhere you cannot see it. The error is the system working.

Do not add DHE back as a compatibility hedge. This is the reflex when an old client breaks, and it is now a MUST NOT violation you would have to explain in an audit.

Do not fix it only at the edge. If a load balancer terminates TLS and re-encrypts to the origin, both legs need the same treatment. The browser only ever sees the first one, which is exactly why the origin stays broken for years.

Do check the whole estate, not just web servers. Management interfaces on appliances, iDRAC and iLO, older Java middleware, and internal-only services are where legacy suites survive, because nobody points a browser at them until the day they do.

Verify the fix

# Should negotiate TLS 1.3 or 1.2 with an ECDHE suite
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>&1 \
  | grep -E "Protocol|Cipher"

# Should now fail - proof the legacy suites are gone
openssl s_client -connect example.com:443 -servername example.com \
  -tls1_2 -cipher 'AES128-GCM-SHA256' </dev/null

# Full re-enumeration
nmap --script ssl-enum-ciphers -p 443 example.com

That middle command is the one to keep. A failure there is the pass condition. It proves TLS_RSA_WITH_AES_128_GCM_SHA256 is genuinely refused rather than merely deprioritized. Deprioritizing is not removing, and a scanner will still flag it.

For more on reading handshakes off the wire, see OpenSSL s_client.

FAQ

Is ERR_SSL_VERSION_OR_CIPHER_MISMATCH a problem on my computer?

Almost never. Clearing your SSL state or trying another browser will confirm it in thirty seconds, but if the site fails for everyone, it is the server. The one client-side exception is a corporate TLS-inspection proxy with its own stale cipher list.

Why does the site work in Firefox but not Chrome?

The two ship slightly different default cipher lists. A server offering exactly one suite that Firefox still accepts and Chrome has dropped will work in one and fail in the other. Fix the server rather than picking a browser.

Will removing RSA and DHE break old clients?

Yes, deliberately, and you should find out which ones before you push it. Anything without ECDHE support loses the ability to connect: Windows XP-era Schannel, Android 4.3 and below, Java 6, and a long tail of embedded and IoT firmware. Java 7 is not on that list, since it supports ECDHE and TLS 1.2 fine, though it needs TLS 1.2 explicitly enabled on the client side. Run the nmap enumeration against a canary host and check your access logs for ancient user agents first. For most public web properties in 2026 the affected population is effectively zero; for an internal estate with old appliances it may not be.

Does this affect TLS 1.3?

No. TLS 1.3 never offered static RSA or non-ephemeral DH, and RFC 10015 explicitly leaves its FFDHE suites permitted.

What is the difference between DHE and ECDHE?

Both are ephemeral Diffie-Hellman and both provide forward secrecy. DHE uses a finite-field group; ECDHE uses an elliptic curve. The distinction now matters because TLS 1.2 can negotiate the curve for ECDHE but cannot negotiate the group for DHE, which is precisely why one survived RFC 10015 and the other did not.