本文目录导读:

在 PHP 中实现证书自动轮换,主要有以下几种方案,我会从简单到复杂逐一介绍:
使用文件系统监控(最简单)
定期检查证书文件是否更新:
class CertificateRotator {
private $certPath;
private $keyPath;
private $lastModified = [];
public function __construct($certPath, $keyPath) {
$this->certPath = $certPath;
$this->keyPath = $keyPath;
$this->loadLastModified();
}
public function checkAndReload() {
$certModified = filemtime($this->certPath);
$keyModified = filemtime($this->keyPath);
if ($this->lastModified['cert'] != $certModified ||
$this->lastModified['key'] != $keyModified) {
// 重新加载证书
$this->reloadCertificate();
// 更新记录
$this->lastModified = [
'cert' => $certModified,
'key' => $keyModified
];
return true;
}
return false;
}
private function loadLastModified() {
$this->lastModified = [
'cert' = filemtime($this->certPath),
'key' = filemtime($this->keyPath)
];
}
private function reloadCertificate() {
// 重新加载证书的逻辑
// 重新创建SSL上下文、更新内存中的证书等
}
}
// 使用示例
$rotator = new CertificateRotator('/path/to/cert.pem', '/path/to/key.pem');
// 在每次请求或定时任务中调用
if ($rotator->checkAndReload()) {
echo "证书已更新";
}
使用 crontab 定时任务
创建一个证书轮换脚本:
// rotate_cert.php
<?php
class SwooleCertRotator {
public function rotate() {
// 1. 获取新证书
$newCert = $this->fetchNewCertificate();
if (!$newCert) {
throw new Exception('无法获取新证书');
}
// 2. 更新证书文件
file_put_contents('/path/to/cert.pem', $newCert['cert']);
file_put_contents('/path/to/key.pem', $newCert['key']);
// 3. 如果是Swoole,发送重载信号
$this->reloadSwoole();
// 4. 如果是Workerman,发送重载信号
$this->reloadWorkerman();
// 5. 记录日志
$this->logRotation();
}
private function fetchNewCertificate() {
// 从Let's Encrypt或其他CA获取新证书
// 示例:使用certbot或ACME协议
$result = shell_exec('certbot renew --dry-run 2>&1');
if (strpos($result, 'success') !== false) {
return [
'cert' => file_get_contents('/etc/letsencrypt/live/example.com/fullchain.pem'),
'key' => file_get_contents('/etc/letsencrypt/live/example.com/privkey.pem')
];
}
return null;
}
private function reloadSwoole() {
// Swoole 4.5+
if (function_exists('swoole_get_local_ip')) {
$manager = new Swoole\Server('0.0.0.0', 9501);
$manager->reload();
}
}
private function reloadWorkerman() {
// 发送信号给Workerman主进程
$pid = file_get_contents('/var/run/workerman.pid');
posix_kill(intval($pid), SIGUSR1);
}
private function logRotation() {
$log = date('Y-m-d H:i:s') . " - 证书已轮换\n";
file_put_contents('/var/log/cert_rotation.log', $log, FILE_APPEND);
}
}
在 PHP-FPM 中使用动态证书加载
对于高并发的场景,更推荐使用内存缓存:
class CertificateManager {
private $cacheDir;
private $certContent;
private $keyContent;
private $expiration;
public function __construct() {
$this->cacheDir = '/tmp/cert_cache/';
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
public function getCertificate() {
// 检查缓存是否过期
if ($this->isCacheExpired()) {
$this->loadNewCertificate();
}
return [
'cert' => $this->certContent,
'key' => $this->keyContent
];
}
private function isCacheExpired() {
// 检查配置文件的缓存时间
$cacheFile = $this->cacheDir . 'cert.cache';
if (!file_exists($cacheFile)) {
return true;
}
$cacheTime = unserialize(file_get_contents($cacheFile))['time'];
// 如果超过2小时,认为过期
return (time() - $cacheTime > 7200);
}
private function loadNewCertificate() {
// 从配置获取新证书
$config = require 'cert_config.php';
$this->certContent = file_get_contents($config['cert_path']);
$this->keyContent = file_get_contents($config['key_path']);
$this->expiration = strtotime('+2 hours');
// 写入缓存
$this->writeToCache();
}
private function writeToCache() {
$cacheData = [
'time' => time(),
'name' => 'mypem',
'cert_content' => $this->certContent,
'key_content' => $this->keyContent
];
file_put_contents(
$this->cacheDir . 'cert.cache',
serialize($cacheData)
);
}
}
使用 Swoole/Workerman 实现热更新(生产推荐)
Swoole 4.5+ 版本:
<?php
use Swoole\Coroutine\Http\Server;
class CertificateRotator {
public function start() {
$server = new Server('0.0.0.0', 9501, SWOOLE_PROCESS);
// 设置初始证书
$server->set([
'ssl_cert_file' => '/path/to/cert.pem',
'ssl_key_file' => '/path/to/key.pem',
]);
// 开启证书轮换机制
$server->on('Start', function ($server) {
// 创建协程定期检查证书更新
go(function () {
while (true) {
usleep(3600000); // 每小时检查一次
$this->checkAndRotate($server);
}
});
});
$server->on('Request', function ($request, $response) use ($server) {
$response->end("Hello World");
});
$server->start();
}
private function checkAndRotate($server) {
// 检查证书是否需要更新
if ($this->shouldRotate()) {
// 获取新证书
$newCert = $this->getNewCertificate();
// 写入临时文件
$tmpCert = '/tmp/cert_new.pem';
$tmpKey = '/tmp/key_new.pem';
file_put_contents($tmpCert, $newCert['cert']);
file_put_contents($tmpKey, $newCert['key']);
// 更新服务器证书(Swoole 4.5+ 支持)
$server->set([
'ssl_cert_file' => $tmpCert,
'ssl_key_file' => $tmpKey,
]);
}
}
}
Workerman 版本:
<?php
require_once __DIR__ . '/workerman/autoload.php';
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
class CertRotator {
public function rotate() {
$worker = new Worker('http://0.0.0.0:443');
$worker->transport = 'ssl';
$worker->ssl = [
'local_cert' => '/path/to/cert.pem',
'local_pk' => '/path/to/key.pem',
];
// 处理更新信号
$worker->onMessage = function (TcpConnection $connection, $data) {
$connection->send('Hello World');
};
// 监听SIGUSR1信号进行证书重载
$worker->onWorkerStart = function ($worker) {
// 设置信号处理
pcntl_signal(SIGUSR1, function () use ($worker) {
// 重新加载证书
$newCert = file_get_contents('/path/to/new-cert.pem');
file_put_contents('/path/to/cert.pem', $newCert);
// Workerman 会自动检测到证书文件变化
$this->reloadCertificates($worker);
});
};
}
private function reloadCertificates($worker) {
// 重新加载SSL上下文
$worker->ssl = [
'local_cert' => '/path/to/cert.pem',
'local_pk' => '/path/to/key.pem',
];
// 通知所有连接重新握手
foreach ($worker->connections as $connection) {
$connection->destroy();
}
}
}
容器化环境中的证书轮换
如果使用 Docker/Kubernetes,可以结合 Volume 和 Sidecar 模式:
// sidecar.php - 在Kubernetes Sidecar容器中运行
class CertSync {
public function watchAndSync() {
$watchPaths = '/mnt/certs/';
// 使用inotify监听文件变化
$inotify = inotify_init();
$watch = inotify_add_watch(
$inotify,
$watchPaths,
IN_CLOSE_WRITE | IN_DELETE | IN_MOVE
);
// 事件循环
while (true) {
$events = inotify_read($inotify);
foreach ($events as $event) {
if ($event['name'] == 'cert.pem' || $event['name'] == 'key.pem') {
$this->notifyApplication($event['name']);
}
}
}
}
}
推荐的最佳实践
- 使用环境变量:不要在代码中硬编码证书路径
- 加入重试机制:更新失败时要重试或回滚
- 监控告警:监控证书过期时间
- 日志记录:记录所有证书轮换操作
- 测试环境验证:先在小流量环境验证
// 监控证书过期时间
class CertMonitor {
public function checkExpiry() {
$certPath = '/path/to/cert.pem';
$cert = openssl_x509_read(file_get_contents($certPath));
$certData = openssl_x509_parse($cert);
$expiryDate = $certData['validTo_time_t'];
$daysToExpiry = ($expiryDate - time()) / 86400;
if ($daysToExpiry < 30) {
// 发起告警,准备轮换
$this->triggerRotation();
return "证书将在 {$daysToExpiry} 天后过期";
}
return "证书正常,剩余 {$daysToExpiry} 天";
}
}
选择哪种方案取决于你的部署环境、PHP框架和性能要求,对于生产环境,推荐结合方案3和4,使用内存缓存配合优雅重启机制。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播