hyperf 多数据库启用 ssl 加密需同时满足服务端强制、客户端配置正确、证书路径可信、连接池不复用四条件;每个实例须单独配置,不可共用 options,且必须验证运行时 ssl_cipher 非空。

Hyperf 多数据库连接启用 SSL 加密,核心不是“加个选项就行”,而是要同时满足服务端强制、客户端配置正确、证书路径可信、连接池不复用这四个条件。尤其在多库场景下,不同 MySQL 实例(比如一个 RDS、一个本地 8.0、一个 ProxySQL 后端)SSL 策略可能不同,必须为每个连接单独配置,不能共用同一套 options。
服务端必须开启并强制 SSL
这是前提,否则客户端配再全也走明文。确认方式:
- 登录 MySQL 执行 SHOW VARIABLES LIKE 'have_ssl',返回 YES
- 执行 SHOW VARIABLES LIKE 'require_secure_transport',值必须是 ON
- 执行 SHOW VARIABLES LIKE '%ssl_%',检查 ssl_ca、ssl_cert、ssl_key 是否指向可读的绝对路径(如 /var/lib/mysql/ca.pem),且属主为 mysql、权限合规(server-key.pem 必须是 600)
- 对应用户需显式授权 REQUIRE SSL:ALTER USER 'your_user'@'%' REQUIRE SSL;
Hyperf 中为每个数据库独立配置 SSL 参数
不能复用 default 连接池,也不能靠运行时修改。必须在 config/autoload/databases.php 中为每个需要 SSL 的库定义完整连接项,例如:
'mysql_rds_ssl' => [
'driver' => 'mysql',
'host' => 'xxx.mysql.rds.aliyuncs.com',
'port' => 3306,
'database' => 'test',
'username' => 'user',
'password' => 'pass',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'options' => [
\PDO::MYSQL_ATTR_SSL_MODE => \PDO::MYSQL_ATTR_SSL_MODE_REQUIRED,
\PDO::MYSQL_ATTR_SSL_CA => base_path('certs/rds-ca-bundle.pem'),
// 如需双向认证,再加 CERT 和 KEY(一般云数据库只需 CA)
],
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => 60.0,
],
],
注意:
- 证书路径必须是 绝对路径,推荐用 base_path(),不要用相对路径或 $_SERVER['PWD']
- 使用 PDO::MYSQL_ATTR_SSL_* 常量(不是 PDO::ATTR_SSL_*),否则无效
- 若服务端要求严格验证(如校验域名),需补上 PDO::MYSQL_ATTR_SSL_CERT 和 PDO::MYSQL_ATTR_SSL_KEY,并确保 client-key.pem 权限为 600
Docker 或生产环境证书文件加载失败
本地跑通、上线就报 failed to load ca certificate,大概率是容器内路径或权限问题:
- 确认证书已挂载进容器,且路径与配置中完全一致(建议挂载到 /app/certs/ 这类明确路径)
- 检查 PHP 进程用户(如 www-data 或 nobody)是否有读取权限:ls -l /app/certs/
- 避免挂载到 /root/ 或 /home/ 下——PHP 进程通常无权访问
- 在 Dockerfile 中可加一句:RUN chmod 644 /app/certs/*.pem && chown www-data:www-data /app/certs/
验证连接是否真走 SSL
别信配置,要看运行时状态。在 Hyperf 中执行:
$connection = DB::connection('mysql_rds_ssl');
$result = $connection->select("SHOW STATUS LIKE 'Ssl_cipher'");
// 返回类似 [['Variable_name' => 'Ssl_cipher', 'Value' => 'TLS_AES_256_GCM_SHA384']] 才算成功
如果 Value 为空字符串,说明没走加密通道,此时应检查:
- 服务端 require_secure_transport 是否真为 ON(重启过 mysqld 吗?)
- Hyperf 连接名是否拼写错误,导致 fallback 到默认无 SSL 的连接
- 证书文件是否被 SELinux/AppArmor 拦截(Linux 容器常见)











