Database audit fix

How to Enforce TLS/SSL Connections in MySQL Server

MySQL accepts plaintext TCP connections unless require_secure_transport is ON, so credentials and query results cross the network in clear text. Set it to ON in my.cnf or with SET PERSIST require_secure_transport=ON.

Hiren KalariyaLast reviewed: Sep 28, 2026Check mysql-tls-enforcement
Medium
severity
Yes
needs root or sudo
5
results it can return
1 of 5
checks in this audit

Every threshold on this page is transcribed from the database-security-transport-encryption audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 5 Transport Encryption checks, or how the audit runs.

Medium severityNeeds sudo

What this check reads

Queries the running server over the Unix socket as root and reads three variables: @@require_secure_transport, have_ssl (falling back to have_openssl when the first query returns nothing) and @@skip_networking. The branches are evaluated in that order. require_secure_transport reading 1 or ON is a PASS, and so is skip_networking reading 1 or ON, because a server with TCP switched off has no network traffic left to encrypt. If neither is set, have_ssl reading exactly YES means TLS exists but is optional and gives a WARN; anything else, an empty value included, is a FAIL that says TLS is not available.

When it applies

Runs only when MySQL/MariaDB is detected (HAS_MY=1) and reachable as root over the socket (MYOK=1); otherwise SKIP. It reads live server variables, so it reflects the running configuration. It judges only whether TLS is available and required for the server as a whole - per-account requirements (CREATE USER … REQUIRE SSL) are not examined, so a server can FAIL or WARN here while every account is individually forced onto TLS. skip_networking short-circuits to PASS because socket-only access makes network TLS irrelevant.

Known accuracy issue: the FAIL branch depends on have_ssl/have_openssl, which were deprecated in MySQL 8.0.26 and removed in MySQL 8.4.0. On MySQL 8.4 and 9.x both queries return nothing, so the check reports FAIL "TLS not available" even when TLS is working. This is recorded in the README's known-issues list.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
PASS`require_secure_transport=ON`The server refuses plaintext TCP connections, so every network client is already on TLS.
PASS`skip_networking` onThe server accepts no TCP connections at all, so there is no wire traffic left to encrypt.
WARNTLS available but optionalTLS works but nothing insists on it, so a client that asks for plaintext still gets plaintext.
FAIL`have_ssl` not `YES`The server reports no usable TLS, so clients authenticate and query in clear text - but confirm this by hand on MySQL 8.4 and later, where the variable this branch reads no longer exists.
SKIPNot accessibleNothing was verified: MySQL was either not detected or not reachable as root, so its transport is unknown rather than safe.

Why it matters

Without TLS, MySQL credentials (mysql_native_password challenge/response is replayable offline; caching_sha2_password falls back to RSA or sends plaintext over non-TLS in some paths) and every query result cross the network in clear. require_secure_transport refuses non-TLS TCP connections while still allowing the Unix socket. OWASP: only allow encrypted connections. CIS MySQL "Ensure 'require_secure_transport' is set to 'ON'". MySQL auto-generates a self-signed CA and server cert at first start (8.0+), so TLS is normally available but not required.

Why it fails, and when it is wrong

  • False FAIL on MySQL 8.4 and 9.x. have_ssl and have_openssl were removed in 8.4.0 (deprecated since 8.0.26). The query returns no row, HS is empty, and the check reports "TLS not available". Verify with SHOW VARIABLES LIKE 'tls_version'; (non-empty means TLS is on) or SHOW STATUS LIKE 'Ssl_server_not_after';. MariaDB still has have_ssl.
  • MariaDB with TLS compiled but not configured reports have_ssl=DISABLED → FAIL is correct: set ssl_cert/ssl_key/ssl_ca.
  • Local-only deployments (bind-address=127.0.0.1) get WARN even though TLS on loopback adds little; the check only passes loopback via skip_networking.
  • Enforcing TLS breaks clients that connect with --ssl-mode=DISABLED or old drivers. Test with mysql --ssl-mode=REQUIRED first and check SELECT * FROM performance_schema.session_status WHERE variable_name='Ssl_cipher'; from the app.

How to fix it

Check what the server can do today

Read the current state before you change anything. require_secure_transport reading OFF means plaintext TCP connections are still accepted, and TLS being available is not the same as TLS being required: most client libraries take plaintext whenever the server allows it. On MySQL 8.4 and later have_ssl and have_openssl no longer exist, so read tls_version instead to tell whether TLS is available at all. The commands are in Verify the fix below.

Configure the certificates

Point the server at a certificate, key and CA under the [mysqld] section, and pin the protocol versions while you are there. The key must be mode 0600 and owned by the mysql user, or the server will not load it.

[mysqld]
ssl_ca   = /etc/mysql/ssl/ca.pem
ssl_cert = /etc/mysql/ssl/server-cert.pem
ssl_key  = /etc/mysql/ssl/server-key.pem
tls_version = TLSv1.2,TLSv1.3

Turn on enforcement

Add the enforcement switch to the same section:

[mysqld]
require_secure_transport = ON

Or at runtime: SET PERSIST require_secure_transport=ON; (MySQL 8), which applies to the running server and survives a restart, so no restart is needed to make it take effect. Per-account: ALTER USER 'app'@'10.0.0.5' REQUIRE SSL;.

Update the clients before you restart, not after

From the moment enforcement is on, every TCP client that does not negotiate TLS is refused with ERROR 3159. Clients on the Unix socket are unaffected. Change the application connection settings first, test each client with --ssl-mode=REQUIRED, confirm the CA bundle those clients use is right, and only then flip enforcement on.

Verify the fix

# Modern, reliable substitutes for the removed have_ssl variable:
sudo mysql -N -e "SHOW VARIABLES LIKE 'tls_version'"     # non-empty means TLS is available
sudo mysql -N -e "SELECT @@ssl_cert, @@require_secure_transport"

# Prove an unencrypted connection is actually refused:
mysql --ssl-mode=DISABLED -u app -p -h 127.0.0.1 -e 'SELECT 1'
# expected: ERROR 3159 (Connections using insecure transport are prohibited)

# And that an encrypted one works, showing the negotiated cipher:
mysql --ssl-mode=REQUIRED -u app -p -h 127.0.0.1 -e "SHOW STATUS LIKE 'Ssl_cipher'"
# expected: a cipher name, not an empty value

# Per-account requirements, which this check does not look at:
sudo mysql -N -e "SELECT user, host, ssl_type FROM mysql.user WHERE ssl_type <> ''"

Debugging

Almost certainly the known false positive above. Confirm with SHOW VARIABLES LIKE 'tls_version'; if it returns a list of protocols, TLS is available and the finding is wrong. Record it as a known script issue; there is no configuration change that clears it.

MySQL 8 auto-generates self-signed certificates at first start, so have_ssl=DISABLED usually means the auto-generated files were removed or ssl=0 was set. Check @@ssl_cert, @@ssl_key, and the error log at startup.

MYOK=0; see the Authentication debugging notes for socket and sudo causes.

The check tests the global switch only. Per-account REQUIRE SSL (visible in the ssl_type query above) is a valid alternative control; the clean fix that also clears the finding is require_secure_transport=ON.

Clients connecting over the Unix socket are unaffected, but any TCP client without TLS support or with a certificate-verification failure now fails. Test each client with --ssl-mode=REQUIRED first, and check the CA bundle those clients use.

Sources

How the script reads this

Next

Re-run the Transport Encryption audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 5 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 5 Transport Encryption fixes
Same audit

Other Transport Encryption checks

Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Windows

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals