MySQL ERROR 2026 (HY000) is a generic SSL/TLS handshake failure that happens before authentication. In modern setups the most common cause is a TLS version mismatch (old clients negotiating TLSv1.0/1.1, which MySQL removed in 8.0.28), followed by untrusted or self-signed CA certificates. Diagnose it with a single openssl s_client probe, then either trust the CA, skip certificate verification while keeping encryption, or upgrade the client's TLS support.
ERROR 2026 (HY000) is a generic SSL/TLS connection error raised when the MySQL client cannot complete a secure handshake with the server. It occurs during the TLS handshake phase, before authentication is attempted, so it almost always points at the transport layer rather than credentials. Because it is a catch-all, the message alone rarely identifies the root cause: it can mean the negotiated TLS protocol versions do not overlap, the server certificate is self-signed or signed by an untrusted CA, a certificate is expired or has a hostname mismatch, a certificate file is missing or unreadable, or the client is speaking plaintext to a TLS endpoint (or vice versa). The single most useful next step is to reproduce the handshake outside MySQL with openssl s_client, which surfaces the exact OpenSSL error behind the generic 2026.
Before changing any config, reproduce the failure outside MySQL. This is the single best diagnostic because it turns the generic ERROR 2026 into the exact OpenSSL error.
openssl s_client -connect hostname:3306 -starttls mysql -CAfile ca.pemInterpret the output:
- Verify return code: 0 (ok) - certificate chain is valid; the problem is elsewhere (client TLS version or client config).
- Verify return code: 20 (unable to get local issuer certificate) - the client does not have the server's CA; supply the right CA bundle (see the RDS/Azure step).
- Verify return code: 21 (unable to verify the first certificate) / self-signed - untrusted or self-signed CA.
- error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol - TLS version mismatch (see the next step).
- SSL_ERROR_RX_RECORD_TOO_LONG - you are speaking TLS to a plaintext endpoint (or hitting a non-MySQL service); confirm the host/port and that the server actually has TLS enabled.
Pin a version to confirm a mismatch:
openssl s_client -connect hostname:3306 -starttls mysql -tls1_2 -CAfile ca.pemIf -tls1_2 succeeds but the default fails, your client was trying an unsupported (old) protocol.
TLSv1.0 and TLSv1.1 were deprecated in MySQL 8.0.26 and removed in 8.0.28. A server on 8.0.28+ only accepts TLSv1.2/1.3, so any client that can only offer 1.0/1.1 fails with ERROR 2026 and the unsupported protocol signature.
Check what the server allows:
SHOW VARIABLES LIKE 'tls_version';The correct fix is almost always to give the client modern TLS support rather than weakening the server:
- mysql CLI: update the MySQL client package to a current 8.0+ build
- Python: upgrade mysql-connector-python or PyMySQL (and ensure a recent OpenSSL)
- Node.js: npm install mysql2@latest
- PHP: PHP 7.4+ linked against OpenSSL 1.1.1+
Do NOT downgrade or stay on MySQL 8.0.27 or earlier just to keep TLSv1.0/1.1 alive. Those protocols are obsolete and insecure (POODLE, BEAST, weak ciphers); re-enabling them re-exposes you to known attacks. If you genuinely cannot upgrade a legacy client in time, treat it as a temporary, network-isolated exception and plan the upgrade — do not make it the permanent posture.
If openssl reported an untrusted issuer (return code 20/21), the encryption is fine — the client just needs the right CA bundle. This is the normal situation for managed databases.
AWS RDS — download the global bundle and point --ssl-ca at it:
wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
mysql -h your-instance.region.rds.amazonaws.com -u admin -p \
--ssl-ca=global-bundle.pem --ssl-mode=VERIFY_CAAzure MySQL — use Microsoft's root certificate:
mysql -h your-instance.mysql.database.azure.com -u username -p \
--ssl-ca=DigiCertGlobalRootCA.crt --ssl-mode=REQUIREDFor a private/internal CA, hand the client the CA file the server's cert was signed by:
mysql -h hostname -u user -p --ssl-ca=/path/to/internal-ca.pem --ssl-mode=VERIFY_CAProviding the CA keeps both encryption AND verification on, which is the goal.
If openssl could not establish TLS at all, confirm the server supports and is configured for it. Connect locally on the server (or with a working client):
SHOW VARIABLES LIKE 'have_%ssl%';
SHOW VARIABLES LIKE 'ssl_%';Expected:
have_openssl: YES
have_ssl: YES
ssl_ca: /path/to/ca.pem
ssl_cert: /path/to/server-cert.pem
ssl_key: /path/to/server-key.pemIf have_ssl/have_openssl is NO, the server binary lacks TLS support and must be reinstalled with OpenSSL. If the variables are empty, set them in the [mysqld] section of /etc/my.cnf (or /etc/mysql/my.cnf) and restart. Also confirm the certificate files exist and are readable by the mysql process / your client user:
ls -la /path/to/ca.pem /path/to/server-cert.pem /path/to/server-key.pemA valid CA but a hostname or validity-window mismatch still fails verification. Check the cert's CN/SAN:
openssl x509 -in server-cert.pem -text -noout | grep -A1 "Subject Alternative Name"
openssl x509 -in server-cert.pem -noout -dates- Connect using the exact name in the CN/SAN — if the cert says mysql.example.com, connecting to 127.0.0.1 or a raw IP will fail unless that IP is in the SAN.
- Confirm notBefore/notAfter cover the current time.
Large clock drift breaks the validity-window check, so keep both hosts on NTP:
date # compare client and server
timedatectl # confirm NTP sync is activeThese are NOT the same thing, and the difference matters for ERROR 2026. The typical 2026 case (self-signed or internal CA on a server you control) should be solved by keeping encryption and skipping only certificate verification, not by turning TLS off.
Encrypt, but skip CA verification (good for self-signed/internal CA when you can't add the CA yet):
# mysql CLI: encrypted, no CA/identity check
mysql --ssl-mode=REQUIRED -h hostname -u user -p# Python mysql.connector: encrypted, skip cert verification
import mysql.connector
conn = mysql.connector.connect(
host='hostname', user='user', password='password',
ssl_disabled=False, # keep TLS ON
ssl_verify_cert=False, # do not verify the server cert
ssl_verify_identity=False,
)// Node.js mysql2: encrypted, skip cert verification.
// NOTE: ssl: 'skip' is NOT a real mysql2 option and does nothing.
const connection = mysql.createConnection({
host: 'hostname', user: 'user', password: 'password',
ssl: { rejectUnauthorized: false }, // TLS stays ON, cert just isn't verified
});Disable TLS entirely (no encryption at all — only on a trusted/loopback network, never the default):
mysql --ssl-mode=DISABLED -h hostname -u user -pconn = mysql.connector.connect(host='hostname', user='user', password='password', ssl_disabled=True)// Omit the ssl key entirely to not negotiate TLS
const connection = mysql.createConnection({ host: 'hostname', user: 'user', password: 'password' });Key distinction: --ssl-mode=REQUIRED / rejectUnauthorized:false / ssl_verify_cert:False keep traffic encrypted but skip identity checks (vulnerable to MITM but not plaintext). --ssl-mode=DISABLED / ssl_disabled=True / omitting ssl send credentials and data unencrypted. Default to encryption; reach for DISABLED only as a deliberate, isolated last resort.
Version history matters when diagnosing ERROR 2026: TLSv1.0 and TLSv1.1 were deprecated in MySQL 8.0.26 and removed in 8.0.28, so a server upgrade across that boundary is a frequent trigger for previously-working legacy clients. The right response is to upgrade the client's TLS stack, not to pin MySQL to 8.0.27 or re-enable old protocols — TLS 1.0/1.1 carry known vulnerabilities and should only ever be tolerated as a short-lived, network-isolated exception with an explicit migration plan.
Note that --ssl-mode=REQUIRED enforces encryption but does NOT verify the CA or server identity; use VERIFY_CA to validate the chain and VERIFY_IDENTITY to also enforce hostname matching. For managed databases always use the provider's current CA bundle (AWS RDS global-bundle.pem, Azure DigiCert root, Google Cloud SQL server-ca.pem) since these rotate periodically.
Some client libraries ship their own TLS/CA store separate from the system OpenSSL — PHP's mysqlnd, for instance, may bundle its own certificates, so its effective OpenSSL behavior can differ from the system. In Docker, make sure the image includes OpenSSL and that mounted certificate files are readable by the in-container user.
Finally, ERROR 2026 accompanied by SSL_ERROR_RX_RECORD_TOO_LONG almost always means plaintext is being sent to a TLS endpoint (or you are hitting a non-TLS service). Note that 3306 is MySQL's normal protocol port, not a dedicated SSL port — MySQL 8 negotiates TLS opportunistically over that same connection rather than using a separate SSL port. So this signature usually points to a wrong host/port or a server without TLS enabled, not to a missing 'SSL port'.
CR_SOCKET_CREATE_ERROR (2001): Can't create UNIX socket
How to fix "CR_SOCKET_CREATE_ERROR (2001): Can't create UNIX socket" in MySQL
EE_WRITE (3): Error writing file
How to fix "EE_WRITE (3): Error writing file" in MySQL
CR_PARAMS_NOT_BOUND (2031): No data supplied for parameters
How to fix "CR_PARAMS_NOT_BOUND (2031): No data supplied for parameters" in MySQL
CR_DNS_SRV_LOOKUP_FAILED (2070): DNS SRV lookup failed
How to fix "CR_DNS_SRV_LOOKUP_FAILED (2070): DNS SRV lookup failed" in MySQL
ERROR 1146: Table 'database.table' doesn't exist
How to fix "ERROR 1146: Table doesn't exist" in MySQL