ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MySQL SSL连接配置实战:从自签名CA到useSSL=true完整指南

2026/9/17 19:00:15 拓冰建站 浏览量
MySQL SSL连接配置实战:从自签名CA到useSSL=true完整指南 前一阵子帮一个业务方排查数据库连接慢的问题发现他们的应用日志里有一行告警Establishing SSL connection without servers identity verification is not recommended.说白了就是应用连MySQL时虽然加了useSSLtrue但服务端证书压根没校验CA证书和密钥也没配。这种“形同虚设”的SSL配置在内部网络里可能问题不大一旦数据库暴露到不可信网络中间人攻击分分钟能把账号密码和业务数据剥得干干净净。这篇文章我把自己的实操过程完整梳理一遍从证书体系的概念讲起到用OpenSSL生成CA证书和密钥再到MySQL服务端启用SSL、客户端用useSSLtrue并配置CA证书和密钥完成双向/单向认证连接。整个过程不依赖商业CA用自签名证书就能跑通适用于开发环境、测试环境也适用于中小团队内网生产环境。无论你是DBA、后端开发还是运维只要你的业务里有“MySQLSSL”这几个字这篇都能给你省下不少试错时间。1. 为什么要在MySQL连接中使用SSL加密1.1 明文传输的风险不止是“怕被偷看”MySQL协议默认不加密账号密码在认证阶段虽然做了哈希处理但后续的SQL语句、查询结果、业务数据都是明文传输的。你想想一条select * from user where ...如果经过交换机、路由器、云平台内部网络任何能抓包的人都能直接看到里面的敏感字段。更严重的是如果网络里有人做了ARP欺骗、DNS劫持或者端口镜像他可以伪造一个MySQL服务端把你应用发来的请求“原样转发”到真实数据库同时记录所有流量。这种中间人攻击对数据完整性、账号安全的破坏是悄无声息的。我在实际处理过一个案子业务方反馈某台应用服务器上的数据库账号突然被锁后来翻网络抓包才发现问题出在应用服务器和数据库之间的网络被做了ARP劫持攻击者拿拦截到的认证信息去重放登录。其实那一层网络并不是公网但因为跨了多个Vlan链路里不可控的节点太多明文的风险就暴露出来了。1.2 useSSLtrue到底管住了什么useSSLtrue是JDBC连接串里的参数作用是让客户端与MySQL服务端之间建立TLS加密通道。但要注意它本身只是“开启协议协商”并不等于“安全”。如果你的连接串里只有useSSLtrue而没有指定CA证书客户端通常会跳过服务端证书校验或者仅做主机名校验这就回到了开头那句告警。真正完整的做法是客户端持有CA根证书用于校验服务端证书的真实性服务端持有由该CA签发的服务器证书和私钥如果服务端要求客户端也出示证书客户端还需要持有自己的证书和私钥。这样做的价值在于客户端能够确认自己连的确实是目标MySQL实例而不是别人伪造的冒牌服务器。同时所有数据在传输过程中被加密即使被截获攻击者拿到的也是密文。一句话总结useSSLtrue是开关CA证书和密钥才是让开关真正生效的“钥匙”。只有开关没有钥匙等于给保险柜贴了张封条一撕就开。2. 证书体系与密钥的基本概念2.1 CA证书、服务器证书、客户端证书的分工很多人一接触证书就头大其实这套体系可以类比成“身份证体系”。CA根证书相当于公安局签发的“根身份凭证”。它自签自证用来给下级证书签发和背书。在MySQL场景里客户端必须信任CA根证书才能验证服务器证书是不是由这个可信CA签发的。服务器证书相当于MySQL服务器的“身份证”。里面包含了服务器的主机名或IP、公钥、签发者等信息。客户端连接时服务端会把这张证书发给客户端客户端用CA根证书验证证书签发者是否合法、证书有没有过期、主机名是否匹配。服务器私钥对应服务器证书里的公钥存放在服务端用于TLS握手时的解密和签名。私钥一旦泄露整个加密体系就崩塌了。客户端证书相当于客户端的“身份证”。当MySQL配置了REQUIRE X509或REQUIRE SSL AND ISSUER ...时服务端会要求客户端出示由指定CA签发的证书用于确认“谁在连接我”。客户端私钥客户端持用用于证明自己持有证书对应的私钥。在单向SSL认证场景下只需要CA根证书、服务器证书和服务器私钥客户端只配置CA根证书来验证服务端。双向SSL认证则额外需要客户端证书和客户端私钥同时MySQL用户需要配置REQUIRE X509。2.2 自签名证书 vs 商业证书商业CA证书的优势在于“普遍信任”比如浏览器访问HTTPS网站时系统内置了各大CA根证书用户不需要手动安装。但MySQL连接不是浏览器场景客户端库一般不会预置某个商业CA的根证书无论你用的是商业证书还是自签名证书基本都需要手动把CA根证书配置到客户端。所以在MySQL内部数据库连接场景中自签名CA完全够用而且更加灵活0成本不需要申请域名也不需要等待审核可以给内网IP、主机名、私有域名签发证书证书有效期可以自己控制想签多久签多久所有客户端都是可控的只要把根证书分发给内部应用即可。需要注意自签名证书虽然“自签”但在签发服务器证书时仍然要遵循正规流程先创建CA根证书再用CA私钥去签发服务器证书。如果你用同一个私钥既当CA又当服务器证书证书链会不完整客户端可能报self-signed certificate或者unable to verify之类的问题。2.3 证书链是怎么被验证的TLS握手阶段客户端会收到服务端发来的证书链。在MySQL场景里证书链通常是服务器证书 - CA根证书。客户端验证时做的事情可以拆解为三步检查服务器证书的签发者是否与本地信任的CA证书匹配用CA公钥验证服务器证书上的数字签名是否有效检查服务器证书是否过期、是否被吊销、主机名是否匹配。只有三步全部通过TLS握手才继续。如果中间任何一步失败客户端就会终止连接报出对应的错误信息。理解这个流程后排查证书问题时思路就清晰了报错里如果有unknown certificate八成是CA根证书没配好如果有certificate expired就是有效期问题如果有hostname mismatch则是证书里的主机名和连接地址不一致。3. 使用OpenSSL生成CA证书与密钥3.1 环境准备生成证书我推荐用OpenSSLLinux/macOS系统自带Windows下可以用Git自带的OpenSSL工具也可以单独安装Win64 OpenSSL。本文命令在Linux和macOS下直接可用Windows下需要把openssl加入PATH。先准备一个工作目录所有证书文件都放这里方便管理mkdir -p ~/mysql-ssl cd ~/mysql-ssl export DIR$(pwd)生成的整个过程中会涉及以下文件ca-key.pemCA根私钥必须严格保管不要泄露ca-cert.pemCA根证书需要分发到客户端server-key.pemMySQL服务器私钥server-cert.pemMySQL服务器证书client-key.pem客户端私钥双向认证需要client-cert.pem客户端证书双向认证需要。在开始之前建议确认OpenSSL版本不同版本默认算法略有差异本教程基于OpenSSL 1.1.1及以上版本。openssl version3.2 生成CA根证书与私钥首先生成CA根私钥这里使用RSA 2048位密钥。如果你对安全性要求更高可以使用RSA 4096但握手性能会有所下降。就MySQL内部连接来说2048位已经足够。openssl genrsa -out ca-key.pem 2048然后基于这个私钥生成自签名的CA根证书有效期我习惯设10年避免频繁续期。证书中的国家、省份、组织等字段按自己公司情况填但要注意Common Name最好填一个明确标识比如MySQL Internal CA。openssl req -new -x509 -days 3650 -key ca-key.pem -out ca-cert.pem \ -subj /CCN/STBeijing/LBeijing/ODemoOrg/CNMySQL Internal CA此时当前目录下就有了ca-key.pem和ca-cert.pem。你可以通过下面的命令查看证书详情确认字段和有效期是否正确openssl x509 -in ca-cert.pem -noout -text3.3 生成MySQL服务器证书并签名服务器证书的第一步是生成服务器私钥和证书签名请求CSR。openssl genrsa -out server-key.pem 2048 openssl req -new -key server-key.pem -out server.csr \ -subj /CCN/STBeijing/LBeijing/ODemoOrg/CNmysql-server这里有一个非常关键的步骤为了让客户端能够校验“主机名”我们需要在CSR里加入subjectAltName也就是SAN扩展。如果证书里没有SAN某些新的客户端库会直接报No subject alternative DNS name。因此不能用简单的openssl req -new ...方式直接签发需要先创建一个扩展配置文件。继续在当前目录创建server-ext.cnf[ v3_ca ] subjectAltName alt_names [alt_names] IP.1 127.0.0.1 IP.2 192.168.1.100 DNS.1 mysql.example.com DNS.2 mysql-server上面的IP地址和域名要根据你实际的MySQL连接地址来填。如果你用127.0.0.1测试那IP.1 127.0.0.1必须有如果应用通过内网IP连接就填那个内网IP如果通过主机名连接则填DNS名称。接下来用CA私钥为服务器证书签名openssl x509 -req -days 730 -in server.csr \ -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \ -out server-cert.pem -extfile server-ext.cnf -extensions v3_ca-CAcreateserial会生成ca-cert.srl序列号文件用于保证每次签发的证书序列号唯一。证书有效期这里设置2年实际生产建议1年到期后在维护窗口操作替换。签发完成后你可以校验一下证书和私钥是否匹配openssl x509 -in server-cert.pem -noout -modulus | openssl md5 openssl rsa -in server-key.pem -noout -modulus | openssl md5两条命令输出的MD5值一致说明证书和私钥成对可以继续。3.4 生成客户端证书并签名如果需求是单向认证即只验证服务端证书这一段可以跳过。但如果MySQL用户配置了REQUIRE X509那么必须为客户端也生成证书。客户端证书和服务器证书的生成过程几乎一样openssl genrsa -out client-key.pem 2048 openssl req -new -key client-key.pem -out client.csr \ -subj /CCN/STBeijing/LBeijing/ODemoOrg/CNmysql-client openssl x509 -req -days 730 -in client.csr \ -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \ -out client-cert.pem客户端证书通常不需要绑定IP或域名因为它是用来标识“客户端身份”的不是用来做主机名校验的。但如果你要开启更严格的身份验证可以在签发时也加入SAN并在MySQL授权里通过SUBJECT做限制。到这里工作目录下已经有6个核心文件。建议调整权限chmod 600 ca-key.pem server-key.pem client-key.pem chmod 644 ca-cert.pem server-cert.pem client-cert.pem私钥文件权限必须是600或400否则MySQL启动时可能拒绝加载。别问为什么我踩过这个坑后面排查部分会细说。4. MySQL服务端启用SSL配置4.1 配置文件参数说明MySQL启用SSL的方式很简单不需要编译插件只要在配置文件my.cnfLinux或my.iniWindows的[mysqld]段指定证书和私钥路径即可。我以MySQL 8.0为例在配置文件里加上[mysqld] ssl-ca/etc/mysql/ssl/ca-cert.pem ssl-cert/etc/mysql/ssl/server-cert.pem ssl-key/etc/mysql/ssl/server-key.pem如果开启了双向SSL认证还需要加上客户端证书签发者的CA信息一般与ssl-ca一致以及客户端证书验证开关。不过实际配置里只需要上面的CA、证书、私钥三项客户端证书的验证是通过用户在授权时的REQUIRE X509触发的MySQL服务端本身不需要配置客户端CA的路径——它默认使用ssl-ca来验证客户端证书。需要注意的是MySQL 5.7及以上版本里这些配置项也可以不写因为MySQL会自动检测数据目录下的ca.pem、server-cert.pem、server-key.pem。但这种方式不利于证书管理和分发建议显式配置在/etc/mysql/ssl/目录下便于替换和排查。4.2 重启与验证SSL状态配置文件改完后重启MySQL服务systemctl restart mysqld重启完成后登录MySQL并查看SSL相关状态变量SHOW VARIABLES LIKE %ssl%;如果配置正常输出类似这样have_ssl | YES ssl_ca | /etc/mysql/ssl/ca-cert.pem ssl_cert | /etc/mysql/ssl/server-cert.pem ssl_key | /etc/mysql/ssl/server-key.pem这里要强调一下have_ssl显示YES并不代表所有连接都走SSL了它只表示服务端支持SSL。客户端是否使用SSL取决于客户端连接参数和用户账号的SSL要求。执行下面这条SQL可以查看当前会话是否使用了SSLSHOW STATUS LIKE Ssl_cipher;如果返回空值说明当前会话没有使用SSL如果返回类似TLS_AES_256_GCM_SHA384这样的值说明SSL加密已生效。另外可以通过查询performance_schema来查看所有连接中使用SSL的比例SELECT COUNT(*) AS total_connections, SUM(CASE WHEN ssl_cipher IS NOT NULL THEN 1 ELSE 0 END) AS ssl_connections FROM performance_schema.session_status s JOIN performance_schema.threads t USING (THREAD_ID) WHERE VARIABLE_NAME Ssl_cipher;4.3 强制启用SSL的用户授权服务端开启了SSL支持但用户仍然可以不走SSL。要强制某个用户必须走SSL需要在授权时加上REQUIRE SSL或更严格的REQUIRE X509。比如强制用户app_user在连接时必须使用SSL加密ALTER USER app_user% REQUIRE SSL;如果希望客户端必须提供有效证书也就是双向SSL认证ALTER USER app_user% REQUIRE X509;甚至还可以指定证书的签发者或主题ALTER USER app_user% REQUIRE ISSUER /CCN/STBeijing/LBeijing/ODemoOrg/CNMySQL Internal CA;REQUIRE X509是比REQUIRE SSL更严格的要求它同时要求客户端证书必须存在且能被CA验证。在实际业务中如果应用能管理证书文件建议直接用REQUIRE X509如果只是想让数据加密不被抓包REQUIRE SSL就够了毕竟维护客户端证书也是一份额外工作。执行授权后记得刷新授权表或等它自动生效FLUSH PRIVILEGES;5. 客户端使用useSSLtrue连接5.1 命令行客户端连接方式MySQL官方命令行客户端不支持useSSLtrue这种参数它使用的是--ssl-mode。不过底层逻辑一致只是参数名不同。单向SSL连接mysql -h 192.168.1.100 -u app_user -p \ --ssl-modeVERIFY_CA \ --ssl-ca/path/to/ca-cert.pem如果证书中的主机名和连接地址一致还可以使用VERIFY_IDENTITY它在验证CA的基础上还会检查主机名mysql -h 192.168.1.100 -u app_user -p \ --ssl-modeVERIFY_IDENTITY \ --ssl-ca/path/to/ca-cert.pem双向SSL连接则加上客户端证书和私钥mysql -h 192.168.1.100 -u app_user -p \ --ssl-modeVERIFY_CA \ --ssl-ca/path/to/ca-cert.pem \ --ssl-cert/path/to/client-cert.pem \ --ssl-key/path/to/client-key.pem连接成功后在MySQL里执行STATUS;能看到SSL: Cipher in use is ...字样说明加密已生效。5.2 JDBC连接串配置Java应用是使用useSSLtrue最多的场景。以MySQL Connector/J 8.x为例完整的JDBC连接串可以这样写jdbc:mysql://192.168.1.100:3306/appdb?useSSLtruerequireSSLtrueverifyServerCertificatetruetrustCertificateKeyStoreUrlfile:/path/to/truststore.jkstrustCertificateKeyStorePasswordchangeit但很多人注意到从Connector/J 8.0.13开始verifyServerCertificate和useSSL已经被标记为废弃更推荐的做法是使用sslMode参数。连接串可以写成jdbc:mysql://192.168.1.100:3306/appdb?sslModeVERIFY_CAtrustCertificateKeyStoreUrlfile:/path/to/truststore.jkstrustCertificateKeyStorePasswordchangeitsslMode有这几个值DISABLED禁用SSLPREFERRED优先使用SSL如果服务端不支持则退化为明文REQUIRED必须使用SSL但不校验证书VERIFY_CA必须使用SSL并校验服务端证书是由可信CA签发的VERIFY_IDENTITY在VERIFY_CA基础上额外校验主机名。如果你配了useSSLtrue但没指定sslModeConnector/J 8.x 默认会使用PREFERRED这就是为什么有些人只加了useSSLtrue仍然没有真正验证服务端证书。所以最稳妥的办法是显式设sslModeVERIFY_CA或VERIFY_IDENTITY。Java客户端信任库推荐使用JKS或PKCS12格式。从ca-cert.pem导入truststore的方法如下keytool -importcert -alias mysql-ca \ -file ca-cert.pem \ -keystore truststore.jks \ -storepass changeit \ -noprompt如果是双向认证还需要把客户端证书和私钥导入到keystore。Connector/J中可以使用clientCertificateKeyStoreUrl和clientCertificateKeyStorePassword指定客户端证书库。生成PKCS12格式的客户端证书库openssl pkcs12 -export \ -in client-cert.pem \ -inkey client-key.pem \ -out client-keystore.p12 \ -name client-cert \ -passout pass:changeit然后在JDBC连接串里追加clientCertificateKeyStoreUrlfile:/path/to/client-keystore.p12clientCertificateKeyStorePasswordchangeitclientCertificateKeyStoreTypePKCS12这样Java应用就能以双向SSL方式连接MySQL了。5.3 Python连接方式Python连接MySQL一般用PyMySQL或mysql-connector-python。以PyMySQL为例它底层基于ssl模块参数和标准库保持一致。单向SSL连接import pymysql conn pymysql.connect( host192.168.1.100, userapp_user, passwordyour_password, databaseappdb, ssl{ ca: /path/to/ca-cert.pem, check_hostname: True, } )双向SSL连接则增加客户端证书和密钥conn pymysql.connect( host192.168.1.100, userapp_user, passwordyour_password, databaseappdb, ssl{ ca: /path/to/ca-cert.pem, cert: /path/to/client-cert.pem, key: /path/to/client-key.pem, check_hostname: True, } )需要注意check_hostnameTrue时连接的主机名必须与服务器证书SAN中的信息一致否则会抛ValueError。如果你的MySQL服务器是通过IP连接的而证书里只签了主机名需要把check_hostname设为False或者重新签发证书加入对应IP地址。5.4 导航工具连接及证书导入像Navicat、DBeaver这类图形化工具都提供了SSL标签页。一般只需要在TLS/SSL配置里勾选“使用SSL”然后选择CA证书文件即可。Navicat连接属性里找到“SSL”选项卡勾选“使用SSL”然后分别选择CA证书、客户端证书、客户端密钥。如果只做单向认证客户端证书可以不填。DBeaver在连接编辑器的“SSL”标签页里勾选“启用SSL”然后在“CA证书”处选择ca-cert.pem文件。DBeaver还支持“验证主机名”选项按需开启。图形化工具最大的坑是证书格式和编码问题。有些工具只接受PEM格式有些则要求PKCS12。如果导入时报错“证书格式不正确”可以把PEM转成PKCS12试试。另外Windows系统上如果你把ca-cert.pem手动安装到了“受信任的根证书颁发机构”工具可能不再要求手动选择CA文件但安装时一定要选“当前用户”还是“本地计算机”否则容易权限混乱。6. 常见问题与排查技巧6.1 SSL连接失败原因清单我整理了一份高频的失败原因速查表基本覆盖了日常遇到的各种情况错误现象可能原因解决办法Access denied ... SSL connection error用户被要求SSL但客户端没启用SSL在客户端开启SSL如果是JDBC检查连接串参数Unable to verify server certificateCA根证书未配置或配置错误检查客户端CA路径用openssl verify校验证书链No subject alternative DNS name服务器证书缺少SAN扩展或SAN与连接地址不匹配重新签发证书加入正确的IP/DNS或关闭主机名校验不推荐Certificate expired服务器证书或CA证书过期重新生成并分发证书Key file permissions are too openMySQL私钥文件权限过大执行chmod 600 server-key.pemSSL connection error: unknown error number证书格式问题或MySQL版本兼容问题确认PEM文件格式检查MySQL错误日志Public Key Retrieval is not allowed使用caching_sha2_password认证且未配置SSL连接串加allowPublicKeyRetrievaltrue或使用RSA公钥最后一个问题比较典型。MySQL 8.0默认认证插件是caching_sha2_password如果连接没有走SSL客户端首次连接时需要通过RSA公钥交换密码。某些客户端连接串没有放行allowPublicKeyRetrieval就会报这个错。最优雅的解决方式是直接启用SSL证书校验通过后密码传输本身就是加密的不再依赖公钥检索。6.2 证书不受信任的处理客户端报this CA root certificate is not trusted是Windows环境里的经典窗口提示。这是因为你把CA证书安装到了“不信任的证书存储区”或者在Java的信任库里没导入。Windows下正确的处理流程是双击ca-cert.pem选择“安装证书”存储位置选“本地计算机”选择“将所有证书放入下列存储”浏览并选择“受信任的根证书颁发机构”点完成。Java环境下则重申一下不要双击安装PEM文件而是通过keytool导入到专用的JKS或PKCS12 truststore再在JDBC连接串里指定。很多Java应用把CA证书导入到了系统JRE的cacerts里如果应用使用的是自带JDK路径不对也会报不受信任。另外openssl verify命令是排查证书链问题的利器openssl verify -CAfile ca-cert.pem server-cert.pem如果输出server-cert.pem: OK说明证书链没问题问题基本出在客户端配置上。6.3 密钥权限与格式问题MySQL服务端加载私钥失败绝大多数是权限问题。server-key.pem如果其他用户可读MySQL会直接拒绝启动SSL错误日志里通常写[ERROR] SSL error: Unable to get private key from ...解决方法很简单chmod 600 server-key.pem chown mysql:mysql server-key.pem如果是主从复制环境MASTER和SLAVE两边的证书建议分别独立签发不要用同一张证书。虽然MySQL允许同一个证书用于多个实例但如果之后要吊销其中某一台会发现无从下手。我习惯给每台MySQL实例单独生成一张服务器证书SAN里只包含这台实例的连接地址。还有一个容易踩的坑是PEM格式混用。有些工具导出的证书是DER格式二进制内容直接配给MySQL会报“bad PEM format”。用OpenSSL确认一下格式openssl x509 -in ca-cert.pem -text -noout如果报“Unable to load certificate”说明文件很可能不是PEM编码。这时可以转换openssl x509 -inform DER -in ca-cert.der -out ca-cert.pem私钥同理openssl rsa -inform DER -in server-key.der -out server-key.pem结尾这套自签名CA加SSL连接的方案我已经在好几个项目里用过了从单机MySQL到一主两从都跑得很稳。最后一件事想提醒大家证书快过期时不少团队要么忘了续期要么在业务高峰临时换证书导致一堆客户端连不上。建议在部署SSL时就留一个监控项每天检查证书有效期剩余不足30天就发告警。我现在的做法是在运维平台里写了一个定时任务把证书到期时间直接推到群机器人里省得再去翻配置文件。证书管理这种事提前一分钟规划后续能省一整天的加班时间。