TL;DR - MySQL supports TLS encryption for all client-server traffic. Since MySQL 8.0, the server auto-generates self-signed certificates on first startup if none are present, but production deployments should use properly generated or CA-signed certificates. - To set up SSL manually: generate a CA certificate, server certificate, and client certificate using OpenSSL. Configure
my.cnfwithssl-ca,ssl-cert, andssl-keydirectives. Restart MySQL. - Enforce encryption per user withALTER USER 'appuser'@'%' REQUIRE SSL— connections without TLS are rejected entirely. - Verify it is working:SHOW STATUS LIKE 'Ssl_cipher'returns the active cipher suite. If it is empty, the connection is not encrypted. - Managed database providers (DigitalOcean, AWS RDS, Google Cloud SQL) handle certificate generation and enforce TLS by default. You only need to configure your client to use SSL. - DBEverywhere connects to your database over TLS automatically when the server supports it — no certificate management on your end.
Table of Contents
- Why Encrypt MySQL Connections
- How MySQL TLS Works
- Step 1: Generate Certificates with OpenSSL
- Step 2: Configure MySQL Server (my.cnf)
- Step 3: Enforce SSL Per User (REQUIRE SSL)
- Step 4: Verify the Connection Is Encrypted
- Client Connection Strings for SSL
- Managed Database Providers and SSL
- FAQ
- Conclusion
Encrypting Database Connections: SSL/TLS Setup Guide for MySQL
If your application connects to a MySQL database over a network — between servers in a data center, across a VPC, or from a developer's laptop to a cloud instance — that traffic is unencrypted by default unless you have configured TLS. Credentials, queries, and result sets travel as plaintext. Anyone with access to the network path can read them. A proper MySQL SSL setup turns that plaintext into an encrypted channel, and the process is more straightforward than most developers expect.
This guide walks through the full setup: generating certificates with OpenSSL, configuring my.cnf, enforcing encryption per user, verifying it works, and connecting clients. It also covers managed providers like AWS RDS and DigitalOcean where TLS is handled for you.
Why Encrypt MySQL Connections
The case for encrypting database traffic is not theoretical. Network-level attacks against database connections are well-documented, and the consequences of unencrypted traffic are concrete:
Credential exposure. MySQL's default authentication protocol (mysql_native_password) sends a hashed password during the handshake, but the hash is derived from a challenge-response that can be captured and replayed in certain conditions. The newer caching_sha2_password plugin (default since MySQL 8.0) is more resistant to replay but still benefits from TLS to protect the full handshake. Without TLS, the initial connection exchange is visible to any packet sniffer on the network.
Query and data interception. Every SELECT, INSERT, and UPDATE travels as plaintext. A 2023 report from Imperva found that 46% of all databases globally contained at least one known vulnerability, and unencrypted connections are among the most common findings in security audits. If your database holds user data, payment records, or anything subject to GDPR, HIPAA, or PCI DSS, unencrypted connections are a compliance gap.
Man-in-the-middle attacks. On shared networks, cloud VPCs with broad routing, or any environment where an attacker can position themselves between client and server, unencrypted MySQL traffic can be intercepted and modified. TLS with certificate verification prevents both eavesdropping and tampering.
Regulatory requirements. PCI DSS Requirement 4.1 mandates encryption of cardholder data in transit. HIPAA's Technical Safeguard 164.312(e)(1) requires encryption for electronic protected health information transmitted over networks. SOC 2 Type II audits routinely flag unencrypted database connections. These are not edge cases — they affect any company handling sensitive data.
The MySQL documentation states it plainly: "It is highly recommended that you use TLS for client connections." Since MySQL 8.0.28, the --ssl-mode=PREFERRED default on clients means they will attempt TLS if the server supports it — but "attempt" is not the same as "require."
How MySQL TLS Works
MySQL's TLS implementation follows the standard X.509 certificate model. Three files are involved:
- CA certificate (
ca.pem) — the Certificate Authority that signs both server and client certificates. The client uses this to verify the server's identity. The server uses this to verify the client's identity (if client certificates are required). - Server certificate + key (
server-cert.pem,server-key.pem) — installed on the MySQL server. Presented to clients during the TLS handshake. - Client certificate + key (
client-cert.pem,client-key.pem) — optional, installed on the client. Used when the server requires mutual TLS (mTLS) viaREQUIRE X509.
When a client connects with --ssl-mode=REQUIRED or higher:
- The client initiates a TLS handshake with the server
- The server presents its certificate
- The client verifies the certificate against the CA (if
--ssl-mode=VERIFY_CAorVERIFY_IDENTITY) - If the server requires a client certificate, the client presents one
- A symmetric session key is negotiated using the agreed cipher suite
- All subsequent traffic — authentication, queries, results — is encrypted
MySQL 8.0+ supports TLS 1.2 and TLS 1.3. Support for TLS 1.0 and 1.1 was removed in MySQL 8.0.28 due to known vulnerabilities in those protocol versions.
Step 1: Generate Certificates with OpenSSL
If you are managing your own MySQL server (not a managed cloud database), you need to generate certificates. MySQL ships a utility called mysql_ssl_rsa_setup that does this automatically, but generating certificates manually with OpenSSL gives you more control and helps you understand what each file does.
Generate the CA Key and Certificate
# Create a directory for the certificates
mkdir -p /etc/mysql/ssl && cd /etc/mysql/ssl
# Generate the CA private key (4096-bit RSA)
openssl genrsa 4096 > ca-key.pem
# Generate the CA certificate (valid for 10 years)
openssl req -new -x509 -nodes -days 3650 \
-key ca-key.pem \
-out ca.pem \
-subj "/CN=MySQL-CA/O=MyOrganization"
The CA certificate is the root of trust. Both server and client certificates will be signed by this CA.
Generate the Server Certificate
# Generate the server private key
openssl genrsa 4096 > server-key.pem
# Generate a certificate signing request (CSR)
openssl req -new -key server-key.pem \
-out server-req.pem \
-subj "/CN=mysql-server/O=MyOrganization"
# Sign the server certificate with the CA (valid for 3 years)
openssl x509 -req -in server-req.pem \
-days 1095 \
-CA ca.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out server-cert.pem
# Clean up the CSR
rm server-req.pem
Generate the Client Certificate (Optional)
Client certificates are only needed if you plan to use REQUIRE X509 on user accounts for mutual TLS. For basic encryption (REQUIRE SSL), the client does not need its own certificate.
# Generate the client private key
openssl genrsa 4096 > client-key.pem
# Generate a certificate signing request
openssl req -new -key client-key.pem \
-out client-req.pem \
-subj "/CN=mysql-client/O=MyOrganization"
# Sign the client certificate with the same CA
openssl x509 -req -in client-req.pem \
-days 1095 \
-CA ca.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out client-cert.pem
# Clean up
rm client-req.pem
Set File Permissions
# Restrict key files to root/mysql only
chmod 600 ca-key.pem server-key.pem client-key.pem
chmod 644 ca.pem server-cert.pem client-cert.pem
chown mysql:mysql /etc/mysql/ssl/*
This is important. If the key files are world-readable, MySQL will log a warning and some security scanners will flag the configuration. The private keys should only be readable by the MySQL process and root.
Verify the Certificates
# Verify the server certificate against the CA
openssl verify -CAfile ca.pem server-cert.pem
# Expected output: server-cert.pem: OK
# Verify the client certificate against the CA
openssl verify -CAfile ca.pem client-cert.pem
# Expected output: client-cert.pem: OK
# Check the server certificate details
openssl x509 -in server-cert.pem -noout -text | head -20
If openssl verify returns anything other than OK, the certificate chain is broken. The most common cause is generating the server/client certificates with a different CA than the one you intend to configure in my.cnf.
Step 2: Configure MySQL Server (my.cnf)
Add the certificate paths to your MySQL configuration file. On most Linux distributions, this is /etc/mysql/my.cnf, /etc/mysql/mysql.conf.d/mysqld.cnf, or /etc/my.cnf.
[mysqld]
# SSL/TLS Configuration
ssl-ca=/etc/mysql/ssl/ca.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem
# Require TLS 1.2 or higher (recommended)
tls_version=TLSv1.2,TLSv1.3
# Optional: require all connections to use SSL
# require_secure_transport=ON
Restart MySQL to apply:
sudo systemctl restart mysql
After restart, verify that SSL is active:
SHOW VARIABLES LIKE '%ssl%';
You should see output similar to:
+---------------+-----------------------------------+
| Variable_name | Value |
+---------------+-----------------------------------+
| have_openssl | YES |
| have_ssl | YES |
| ssl_ca | /etc/mysql/ssl/ca.pem |
| ssl_cert | /etc/mysql/ssl/server-cert.pem |
| ssl_key | /etc/mysql/ssl/server-key.pem |
+---------------+-----------------------------------+
If have_ssl shows DISABLED, MySQL found the certificate files but could not use them. Check the MySQL error log (/var/log/mysql/error.log) for details — the most common issues are incorrect file paths, wrong permissions, or certificates that do not form a valid chain.
The require_secure_transport option deserves special attention. Setting it to ON at the server level means every connection must use TLS — no exceptions. This is the nuclear option. It is effective but can lock you out if your client does not support TLS. A safer approach is enforcing SSL per user (next section), which gives you granular control.
Step 3: Enforce SSL Per User (REQUIRE SSL)
Configuring the server to support TLS is not the same as requiring it. By default, MySQL allows both encrypted and unencrypted connections. A client that does not request TLS will connect in plaintext, even if the server has certificates configured.
To enforce encryption for specific users:
-- Require TLS for the application user
ALTER USER 'appuser'@'%' REQUIRE SSL;
-- Require TLS with a specific cipher (stricter)
ALTER USER 'appuser'@'%' REQUIRE CIPHER 'ECDHE-RSA-AES256-GCM-SHA384';
-- Require mutual TLS (client must present a certificate)
ALTER USER 'appuser'@'%' REQUIRE X509;
-- Require a specific client certificate subject
ALTER USER 'appuser'@'%' REQUIRE SUBJECT '/CN=mysql-client/O=MyOrganization';
After applying REQUIRE SSL, any connection attempt by appuser without TLS is rejected:
ERROR 1045 (28000): Access denied for user 'appuser'@'...' (using password: YES)
The access denied error is intentional but can be confusing — it looks like a password issue, not a TLS issue. If you see this after enabling REQUIRE SSL, check whether the client is actually using TLS.
Best practice for production: Apply REQUIRE SSL to every user that connects from outside localhost. Keep a local-only admin account without SSL requirements as a recovery path in case of certificate issues:
-- Remote users: require SSL
ALTER USER 'appuser'@'%' REQUIRE SSL;
ALTER USER 'readonly'@'%' REQUIRE SSL;
-- Local admin: no SSL requirement (localhost connections only)
ALTER USER 'root'@'localhost' REQUIRE NONE;
To check which users have SSL requirements:
SELECT user, host, ssl_type FROM mysql.user WHERE ssl_type != '';
Step 4: Verify the Connection Is Encrypted
Connecting to MySQL and running SHOW STATUS LIKE 'Ssl_cipher' is the definitive test.
-- Connect and check the cipher
mysql -h your-server-ip -u appuser -p --ssl-mode=REQUIRED
-- Once connected:
SHOW STATUS LIKE 'Ssl_cipher';
Expected output for an encrypted connection:
+---------------+-------------------------------+
| Variable_name | Value |
+---------------+-------------------------------+
| Ssl_cipher | TLS_AES_256_GCM_SHA384 |
+---------------+-------------------------------+
If the Value column is empty, the connection is not encrypted. This means either the client did not request TLS or the server does not have it configured.
Additional verification queries:
-- Check the TLS version in use
SHOW STATUS LIKE 'Ssl_version';
-- Expected: TLSv1.2 or TLSv1.3
-- Check if the current session is encrypted
SHOW SESSION STATUS LIKE 'Ssl%';
-- Shows cipher, version, and other TLS session details
-- Count encrypted vs unencrypted connections
SELECT ssl_cipher, count(*) FROM performance_schema.threads
WHERE type = 'FOREGROUND' GROUP BY ssl_cipher;
That last query is useful for auditing. If any rows show an empty ssl_cipher, those connections are unencrypted. Cross-reference with PROCESSLIST to identify which clients are connecting without TLS.
Client Connection Strings for SSL
Every MySQL client and driver has its own syntax for enabling TLS. Here are the most common ones.
MySQL CLI
# Minimum: require TLS (don't verify server certificate)
mysql -h db.example.com -u appuser -p --ssl-mode=REQUIRED
# Better: verify server certificate against CA
mysql -h db.example.com -u appuser -p \
--ssl-mode=VERIFY_CA \
--ssl-ca=/path/to/ca.pem
# Strictest: verify CA and hostname match
mysql -h db.example.com -u appuser -p \
--ssl-mode=VERIFY_IDENTITY \
--ssl-ca=/path/to/ca.pem
Python (mysql-connector-python)
import mysql.connector
conn = mysql.connector.connect(
host="db.example.com",
user="appuser",
password="secret",
ssl_ca="/path/to/ca.pem",
ssl_verify_cert=True,
ssl_verify_identity=True
)
Python (PyMySQL / SQLAlchemy)
import pymysql
conn = pymysql.connect(
host="db.example.com",
user="appuser",
password="secret",
ssl={"ca": "/path/to/ca.pem"}
)
# SQLAlchemy connection string
# mysql+pymysql://appuser:secret@db.example.com/mydb?ssl_ca=/path/to/ca.pem
PHP (PDO)
$pdo = new PDO(
'mysql:host=db.example.com;dbname=mydb',
'appuser',
'secret',
[
PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca.pem',
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true,
]
);
Node.js (mysql2)
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'db.example.com',
user: 'appuser',
password: 'secret',
ssl: {
ca: fs.readFileSync('/path/to/ca.pem'),
rejectUnauthorized: true
}
});
Connection URI Format
Many tools accept a connection URI. The SSL parameter varies:
mysql://appuser:secret@db.example.com:3306/mydb?sslmode=required
mysql://appuser:secret@db.example.com:3306/mydb?ssl-mode=VERIFY_CA&ssl-ca=/path/to/ca.pem
The exact parameter name (sslmode, ssl-mode, useSSL) depends on the client. Check your driver's documentation.
Managed Database Providers and SSL
If you use a managed database service, the provider handles certificate generation and rotation. You do not need to run the OpenSSL commands above. Here is what you need to know for the major providers.
DigitalOcean Managed Databases
DigitalOcean enforces TLS on all managed MySQL connections by default since 2022. You cannot disable it. The CA certificate is downloadable from the database cluster's control panel.
# Download the CA cert from DO's dashboard, then:
mysql -h your-db-host.db.ondigitalocean.com -u doadmin -p \
--ssl-mode=VERIFY_CA \
--ssl-ca=~/ca-certificate.crt
All connections from DBEverywhere to DigitalOcean databases are automatically encrypted — no CA certificate configuration needed on your side.
AWS RDS
RDS supports TLS on all MySQL instances. Since January 2023, new RDS instances enforce TLS by default via the rds.force_ssl parameter set to 1. AWS provides a combined CA bundle for all regions.
# Download the AWS RDS CA bundle
wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
# Connect with CA verification
mysql -h myinstance.abc123.us-east-1.rds.amazonaws.com -u admin -p \
--ssl-mode=VERIFY_CA \
--ssl-ca=global-bundle.pem
AWS rotates RDS CA certificates periodically. The most recent rotation cycle completed in 2024, and instances that did not update to the new CA bundle (rds-ca-rsa2048-g1) experienced connection failures. Monitor the AWS RDS CA rotation schedule if you pin to a specific CA.
Google Cloud SQL
Cloud SQL for MySQL supports TLS and provides per-instance server certificates. Client certificates can be generated through the Cloud Console or gcloud CLI. Google recommends using the Cloud SQL Auth Proxy for connections, which handles TLS automatically.
# Using the Cloud SQL Auth Proxy (handles TLS transparently)
./cloud-sql-proxy myproject:us-central1:myinstance
# Direct connection with server CA
mysql -h cloud-sql-ip -u root -p \
--ssl-mode=VERIFY_CA \
--ssl-ca=server-ca.pem \
--ssl-cert=client-cert.pem \
--ssl-key=client-key.pem
PlanetScale
PlanetScale enforces TLS on every connection. No configuration is needed — the platform does not allow unencrypted connections. Connection strings from the PlanetScale dashboard include sslmode=verify_identity and point to a publicly trusted CA.
The Pattern Across Providers
Every major managed database provider has moved toward TLS-by-default or TLS-enforced. The trend is clear: as of 2026, 73% of cloud MySQL instances use encrypted connections according to Percona's State of Open Source Databases survey, up from 52% in 2022. The remaining 27% are almost entirely self-managed instances where TLS was never configured.
FAQ
What is the difference between SSL and TLS in MySQL?
MySQL's documentation and configuration directives still use the term "SSL" (--ssl-mode, REQUIRE SSL, ssl-cert), but the actual protocol has been TLS since MySQL 5.7.10 removed SSLv3 support. SSLv2 and SSLv3 are deprecated and known to be insecure. When you see "SSL" in MySQL contexts, it means TLS. The terminology is legacy — the encryption is modern.
Does SSL/TLS slow down MySQL connections?
There is a measurable overhead, but it is smaller than most developers expect. The TLS handshake adds latency to the initial connection — typically 2-5 milliseconds for TLS 1.2 and 1-3 milliseconds for TLS 1.3 (which completes the handshake in one round trip instead of two). Ongoing data transfer encryption adds roughly 3-8% CPU overhead according to MySQL performance benchmarks from Percona. For the vast majority of workloads, this is imperceptible. Connection pooling (which reuses established connections) further reduces the impact since the handshake only happens once per pool connection.
How do I rotate MySQL SSL certificates without downtime?
MySQL 8.0 introduced ALTER INSTANCE RELOAD TLS, which reloads certificates without restarting the server. The process: (1) Generate new certificates. (2) Replace the files on disk. (3) Run ALTER INSTANCE RELOAD TLS. (4) Existing connections continue with the old certificates until they reconnect. (5) New connections use the new certificates. This is zero-downtime rotation. On MySQL 5.7, a server restart is required.
My client says "SSL connection error" — how do I debug it?
Start with the server: check SHOW VARIABLES LIKE 'have_ssl' (should be YES). Then check the error log for certificate-related messages. On the client side, the most common causes are: (1) CA certificate file not found or wrong path. (2) Certificate expired — run openssl x509 -in server-cert.pem -noout -dates to check. (3) Hostname mismatch when using VERIFY_IDENTITY — the certificate CN or SAN must match the hostname you are connecting to. (4) Client and server have no overlapping TLS versions — ensure both support TLS 1.2 or 1.3.
Do I need client certificates (mutual TLS)?
For most applications, no. REQUIRE SSL ensures the connection is encrypted and the server's identity is verifiable, which is sufficient. REQUIRE X509 (mutual TLS) adds client-side certificate verification, which is useful in zero-trust environments or when you need per-client identity at the transport layer. The trade-off is certificate distribution — every client needs its own cert and key, which adds operational complexity. Use it when compliance requires it; otherwise, REQUIRE SSL combined with strong passwords or IAM authentication is adequate.
Conclusion
A complete MySQL SSL setup involves four steps: generate certificates, configure my.cnf, enforce REQUIRE SSL on user accounts, and verify with SHOW STATUS LIKE 'Ssl_cipher'. On managed providers like DigitalOcean, AWS RDS, and Google Cloud SQL, the first two steps are handled for you — you only need to ensure your client connects with TLS enabled and, ideally, with CA verification.
The direction of the ecosystem is clear. MySQL 8.0 auto-generates certificates. Managed providers enforce TLS by default. The caching_sha2_password plugin works best over encrypted connections. Leaving connections unencrypted is increasingly an active choice rather than a default, and it is a choice that creates compliance risk, security exposure, and audit findings.
If you need to connect to MySQL databases from a browser without managing certificates, client configurations, or local tooling, DBEverywhere handles TLS automatically. It connects to your database over an encrypted channel using the provider's CA, and you access the session through phpMyAdmin or Adminer in your browser. Free tier includes 5 sessions per month; the paid tier ($5/mo) adds unlimited sessions, saved connections, and SSH tunnel support for databases on private networks.
Try DBEverywhere Free
Access your database from any browser. No installation, no Docker, no SSH tunnels.
Get Started