What this check reads
Asks the running server for @@ssl_cert, the path to the certificate MySQL presents to clients. A value that does not begin with / is treated as relative and prefixed with @@datadir, which is where the auto-generated server-cert.pem normally sits. The resolved file is then opened as root and tested with openssl x509 -checkend 2592000, so the threshold between a pass and a warning is 2592000 seconds, 30 days, of remaining validity.
When it applies
Runs only when MySQL/MariaDB is detected and reachable as root over the socket (MYOK=1); otherwise SKIP. It only looks at a certificate if @@ssl_cert is set and non-NULL - a server with no configured certificate SKIPs rather than failing, and so does one whose configured file is missing on disk. Relative paths are resolved against @@datadir. Reading the file needs root. The only test is openssl x509 -checkend 2592000: 30 days of remaining validity. Nothing else about the certificate is examined - not the key size, the signature algorithm, the chain, the hostname, or whether clients trust it. An already-expired certificate also fails -checkend and so is reported as WARN, not FAIL.
What each result means
| Result | When | What it means |
|---|---|---|
| PASS | Valid for more than 30 days | The certificate the server presents is not about to expire, and nothing else about it was judged. |
| WARN | Expires within 30 days (or already expired) | Renewal is now on the clock: clients that verify the certificate will start refusing to connect on the expiry date. |
| SKIP | No `ssl_cert`, file not found, or not accessible | No certificate was examined at all, so its expiry date is unknown rather than fine. |
Why it matters
An expired server certificate makes clients with --ssl-mode=VERIFY_CA/VERIFY_IDENTITY refuse to connect, causing an outage, or pushes operators to disable verification, which defeats TLS. MySQL's auto-generated certificates are valid for 10 years; CA-issued ones are typically 1 year or shorter.
Why it fails, and when it is wrong
- Auto-generated certs live in the data directory as
server-cert.pem; the relative-path handling covers that. ssl_certpointing at a file theas_rootshell cannot read (SELinux context, NFS root-squash) gives SKIP "file not found".openssl x509 -checkendtreats an already-expired cert the same as one expiring soon; the message says "expires within 30 days" in both cases.- Only the server certificate is checked, not the CA (
ssl_ca) or client certs.
How to fix it
Find the file the server is using
Ask the running server for @@ssl_cert rather than trusting a config file on disk, because the two can disagree. A value that does not begin with / is relative and resolves against @@datadir, which is where the auto-generated server-cert.pem normally sits, so read that variable too and confirm you are looking at the same file the server is. The commands are in Verify the fix below.
Check the remaining validity
Test the resolved file with openssl x509 -checkend 2592000, which is the exact test the audit runs: 2592000 seconds is 30 days. A non-zero exit means under 30 days of validity remain, and an already-expired certificate fails the same way, so read the real dates with -dates before deciding how urgent this is.
Generate the replacement
Reissue the certificate (internal CA, or mysql_ssl_rsa_setup replacement: MySQL 8.4 generates missing files automatically at startup if you delete the old ones), set the key to mode 0600, chown both files to the mysql service account, and update ssl_cert/ssl_key if the paths changed.
Restart and confirm
MySQL loads certificates at startup, so a renewed file changes nothing until it is reloaded: run ALTER INSTANCE RELOAD TLS; (no restart needed on MySQL 8), or restart the server on an older release. Then re-run the -checkend test to confirm the server is presenting the new expiry rather than the old certificate it still has in memory.
Verify the fix
CERT=$(sudo mysql -N -e 'SELECT @@ssl_cert')
sudo openssl x509 -in "$CERT" -noout -subject -issuer -dates
# expected: notAfter comfortably in the future
# The exact test the check runs:
sudo openssl x509 -checkend 2592000 -noout -in "$CERT" && echo 'valid > 30 days'
# What clients actually negotiate, end to end:
openssl s_client -connect 127.0.0.1:3306 -starttls mysql -brief </dev/nullDebugging
@@ssl_cert is empty, so TLS is not set up at all. That is a TLS Enforcement finding, not a certificate one; fix that first.
The path in @@ssl_cert does not exist, or the audit could not read it even as root. Check with sudo ls -l "$(sudo mysql -N -e 'SELECT @@ssl_cert')". A relative path is resolved against @@datadir; confirm you are looking at the same file the server is.
MySQL loads certificates at startup. Restart the server, or run ALTER INSTANCE RELOAD TLS (MySQL 8.0+) to pick up new files without a restart, then re-check.
MySQL's auto-generated certificates are valid for one year and do age out. Regenerate them (mysql_ssl_rsa_setup, or provide your own CA-issued pair) rather than ignoring the warning; clients will start failing on the expiry date.
This check only tests the expiry date. Hostname mismatch, an untrusted issuer or a broken chain all pass here and fail in the client. Test end to end with the openssl s_client command above and with --ssl-mode=VERIFY_IDENTITY.
Sources
- MySQL 8.4: Creating SSL and RSA Certificates and Keys
- MySQL 8.4: ALTER INSTANCE RELOAD TLS
- MySQL 8.4: ssl_cert
- OpenSSL: openssl-x509 (
-checkend) - MariaDB KB: Certificate Creation with OpenSSL
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