SSL証明書の自動更新設定を『Cloudflare』管理下で実施した話
はじめに
あの 格闘の時間 がつい昨日の様にも感じられますね。 そして『2ヶ月』と言えば例の『積み残し案件』ですよね。そう、
『SSL証明書の再発行』です。
② [DNS] > [レコード]と選択
③ 一覧から《タイプA》のレコードを「編集」押下
④ [プロキシステータス]を切り替えると、雲がグレーになるので「保存」押下
⑤ この状態で1分後くらいに自身のサーバから証明書を更新
# certbot renew
⑥ クラウドフレアに戻り雲をオレンジにする
この手順を自動化するのはもっと面倒くさいです。
実はSSL証明書は2通必要
クラウドフレアの役割は超ざっくり言うと『プロキシによるネットからの強力な防壁(セキュリティ+高速化)』です。 イメージとしては、自分のサイトの前に「超優秀な警備員兼受付」を置くようなものです。
ネットからのアクセスをすべてこの警備員(クラウドフレア)がチェックし、
怪しい攻撃や大量の迷惑アクセスを遮断して、安全なアクセスだけを自身のサーバーに届けてくれます。 直接サーバーを晒さずに済む事は、
更に言い換えればクレジットカードの番号を晒さずにショッピングができる『d払い』と少し似ていますね。
本題に戻ります。
SSL証明書が何故2通必要か?
それはユーザーがあなたのサイトにアクセスする際に、以下の様なプロセスが発生する為です。
『ユーザー』> 通信① >『クラウドフレア』> 通信② >『あなたのサイト』 もうお分かりですね。
通信①②でそれぞれSSLによる暗号化が必要です。 そして私の場合は『さくらのサーバー側で発行・設定した証明書』を使用しているため『通信②』が暗号化されています。
では『通信①』は?
はい、クラウドフレアが自動で証明書を発行して暗号化してくれています。ありがたや。。
SSL証明書の有効期限を「cURL」で確認する
ここで1つ注意が必要です。 有効期限はブラウザからも確認できます(アドレスバーの左側の鍵アイコンから)
しかしこの有効期限、自身のサーバで発行した期限とズレてる事に気づいた方いますか?
実はこの有効期限は『[ユーザー]⇔[クラウドフレア]』間のSSL通信の有効期限となります。 しかもこちらの有効期限は、クラウドフレアが自動的に更新してくれるので『放置案件』となります。 そこで肝心な、更新する必要のある有効期限の確認方法を『cURL』通信で取得したいと思います。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
use Illuminate\Support\Facades\Mail;
use App\Mail\SslCheckMail;
class BatchSslCheck extends Command {
protected $signature = 'app:batch-sslCheck';
protected $description = 'Command description';
// バッチ実行
public function handle() {
// ドメイン
$domain = "reixxxxx.net";
// IPアドレス
$originIp = "xxx.xxx.xx.xxx";
Log::info("【BatchSslCheck】実行(対象ドメイン:{$domain}、対象IP:{$originIp})", []);
// 証明書確認通信を2回実施する
$subject_list = [0 => 'さくら', 1 => 'クラウドフレア'];
foreach ($subject_list as $idx => $val) {
// cURLの基本設定
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://{$domain}:443");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// ヘッダーのみ(通信削減)
curl_setopt($ch, CURLOPT_NOBODY, true);
// 証明書情報を取得
curl_setopt($ch, CURLOPT_CERTINFO, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
// [curl_setopt]の再利用を禁止する
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
if ($idx == 0) {
// 自身のサーバで発行した証明書を参照
curl_setopt($ch, CURLOPT_RESOLVE, ["{$domain}:443:{$originIp}"]);
} else {
// クラウドフレア側の証明書を参照
curl_setopt($ch, CURLOPT_RESOLVE, ["-{$domain}:443"]);
}
// 通信実行
curl_exec($ch);
$certInfo = curl_getinfo($ch, CURLINFO_CERTINFO);
curl_close($ch);
// 通信結果を整理して格納
$certList = $certInfo[0] ?? [];
$pem = null;
// 配列の中から『PEM形式』の証明書テキストを探す
foreach ($certList as $item) {
if (str_contains($item, '-----BEGIN CERTIFICATE-----')) {
$pem = $item;
break;
}
}
$parsedCert = "";
$sslLimit = "";
$countDay = "";
// 証明書テキストが見つかった場合
if ($pem) {
// OpenSSL関数で直接証明書を解析
$parsedCert = openssl_x509_parse($pem);
$validTo = \Carbon\Carbon::createFromTimestamp($parsedCert['validTo_time_t'])->timezone('Asia/Tokyo');
$daysRemaining = (int) \Carbon\Carbon::now('Asia/Tokyo')->diffInDays($validTo, false);
$parsedCert = $parsedCert['issuer']['O'] ?? 'Unknown';
$sslLimit = $validTo->toDateTimeString();
$countDay = $daysRemaining;
Log::info("【証明書発行元】" . $parsedCert, []);
Log::info("【SSL有効期限】" . $sslLimit, []);
Log::info("【SSL残日数】" . $countDay, []);
} else {
Log::warn("{$domain} の証明書を取得できませんでした。", []);
return;
}
// メール送信設定
$mail_flg = false;
switch ($countDay) {
case '90':
case '30':
case '20':
case '10':
$mail_flg = true;
break;
default:
if ($countDay < 10) {
$mail_flg = true;
}
}
// 該当しない残日数の場合はメール送信しない
if (!$mail_flg) {
continue;
}
// メール送信
$mailTo = config('mail.from.address');
$content = [
'mailTo' => $mailTo,
'subject' => $val . ' SSL通信有効期限確認',
'parsedCert' => $parsedCert,
'sslLimit' => $sslLimit,
'countDay' => $countDay,
'view' => 'sslCheck',
];
try {
Mail::to($mailTo)->send(new SslCheckMail($content));
} catch(Exception $e) {
throw new CustomException(
['code'=>'xxxx-xxxxx-xxxxx', 'message'=>'メールの送信に失敗しました。', 'status'=>500]
);
}
}
}
}
また特定の残日数で『メール送信』します。
Cloudflareから『APIトークン』を発行する
そこである期待が心をよぎります。
『参照ができるんなら設定もできるのでは?』 ちょっと相棒(AI)にご相談。
するとあっけなく『CloudflareのAPIトークンを使用して「acme.sh」経由でできるぞ』との回答。 むむむ。
何はともあれ『CloudflareのAPIトークン』が必要なのか。 当然会員だし、サクッと頂きますか!
② 左メニューから「APIトークン」を選択
③ 画面右上にある「+トークンを作成する」を押下
④「ゾーン DNS を編集する」の「テンプレートを使用する」を押下
⑤ ゾーン リソースで自身のドメインを選択する
⑥「概要に進む」押下
⑦「トークンを作成する」押下
cfut_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxde8 また同時に『テストスクリプト』も表示されるので、ターミナルから実行してみましょう。
curl "https://api.cloudflare.com/client/v4/user/tokens/verify" -H "Authorization: Bearer cfut_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxde8" ここまで想定通りにできた方は、この章は読み飛ばして下さい。 問題は『APIトークン』を発行出来なかった方となります。 手順①~⑦でメールで送信される『確認キー』で認証しろ的なダイアログがPOPしましたか?
そして何度認証キーを入れても画面左下に、いかにも怪しいメッセージが出ませんでしたか?
実はクラウドフレアに入会した際に『メールアドレス』を登録したハズです。
すると返す刀でクラウドフレアから以下のメールが送信されているハズです。
これを押下しないと『APIトークン』は発行出来ないのです。 さぁ今すぐ探すのですっ!!
※ 最悪メールアドレスを変更する手段もあります
SSL更新を『acme.sh』経由で実行する準備
[Let's Encrypt]などの無料SSL証明書を、コマンド1つで自動発行・更新してくれる軽量な便利ツール
find ~ -name "acme.sh" 何も表示されなければインストールされていません。
$ sudo dnf install -y tar
トランザクションを実行しています
準備中 : 1/1
インストール中 : tar-2:1.30-11.el8_10.x86_64 1/1
scriptletの実行中 : tar-2:1.30-11.el8_10.x86_64 1/1
検証中 : tar-2:1.30-11.el8_10.x86_64 1/1
インストール済み:
tar-2:1.30-11.el8_10.x86_64
$
$ curl https://get.acme.sh | sh -s email=[email protected]
[2026年 8月 10日 月曜日 21:52:40 JST] Installing cron job
[2026年 8月 10日 月曜日 21:52:40 JST] bash has been found. Changing the shebang to use bash as preferred.
[2026年 8月 10日 月曜日 21:52:41 JST] OK
[2026年 8月 10日 月曜日 21:52:41 JST] Install success!
$
※「[email protected]」はご自身の環境に合わせます
SSL更新を『acme.sh』経由で実行する
$ source ~/.bashrc
$ acme.sh --issue --dns dns_cf -d あなたのドメイン名 -d www.あなたのドメイン名
[2026年 8月 10日 月曜日 21:57:04 JST] Using CA: https://acme.zerossl.com/v2/DV90
[2026年 8月 10日 月曜日 21:57:04 JST] Account key creation OK.
[2026年 8月 10日 月曜日 21:57:04 JST] No EAB credentials found for ZeroSSL, let's obtain them
~ 省略 ~
[2026年 8月 10日 月曜日 21:58:35 JST] Le_LinkCert='https://acme.zerossl.com/v2/DV90/cert/xxxxxxxxxxxxxxxxxxxxx'
[2026年 8月 10日 月曜日 21:58:37 JST] Cert success.
-----BEGIN CERTIFICATE-----
MIIDjTCCAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwRjEL
MAkGA1UEBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxXWmVy
~ 省略 ~
ZWlkcmVhbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx15C5
oYiL+Wif/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxPQBq
Dg==
-----END CERTIFICATE-----
~ 省略 ~
[2026年 8月 10日 月曜日 21:58:38 JST] ARI suggestedWindow: 2026-10-24T23:59:59Z to 2026-10-26T23:59:59Z
[2026年 8月 10日 月曜日 21:58:38 JST] Next renewal time picked from ARI window: 2026-10-26T12:58:37Z
$
これは、次回証明書の推奨更新期間となります。
つまり『今回証明書発行したので、次回はこの期間で再度発行してね』って事です。 初回に取得した『Let's Encrypt』での更新タイミングは60日後だったので少し長くなってますね。
$ sudo grep -riE "SSLCertificateFile|SSLCertificateKeyFile" /etc/httpd/
/etc/httpd/conf.d/reidream-le-ssl.conf:SSLCertificateFile /etc/letsencrypt/live/xxxxx/fullchain.pem
/etc/httpd/conf.d/reidream-le-ssl.conf:SSLCertificateKeyFile /etc/letsencrypt/live/xxxxx/privkey.pem
$
acme.sh --install-cert -d あなたのドメイン名 -d www.あなたのドメイン名 \
--key-file SSLCertificateKeyFile のパス \
--fullchain-file SSLCertificateFile のパス \
--reloadcmd "sudo systemctl reload httpd"
今後の自動更新を考慮して一般ユーザーで作業ができるかがポイントとなります。
cd /etc/letsencrypt/live 移動が出来ない場合は権限が不足していますので以下で変更します。
sudo chmod 755 /etc/letsencrypt
sudo chmod 755 /etc/letsencrypt/live これで移動は出来ましたが、以下コマンドで書き込み権限がある確認します
ll
drwxr-xr-x ~ 上記の様な感じだと一般には書き込み権限がないので、以下コマンドで付与します
sudo chown -R $USER:$USER /etc/letsencrypt/
複数行ありますが、1度に実行ですよ。
成功すると最後の行に以下の文字が表示されるハズです。
『Reload successful』 これは証明書を上書きして『Apache』のリロードもしたよって意味です。
$ acme.sh --install-cronjob
[2026年 8月 10日 月曜日 22:54:21 JST] Installing cron job
40 4,10,16,22 * * * "/home/xxxxx/.acme.sh"/acme.sh --cron --home "/home/xxxxx/.acme.sh" > /dev/null
$
$ openssl x509 -in /etc/letsencrypt/live/xxxxx.net/fullchain.pem -noout -dates
notBefore=Aug 10 00:00:00 2026 GMT
notAfter=Nov 8 23:59:59 2026 GMT
$
※ 2026年11月8日 または『SSL証明書の有効期限を「cURL」で確認する』の章で紹介した方法でPHP経由でも確認できます。
自動更新の成功可否
でも今までの作業は『全て手作業』でしたよね。
本来の真価を発揮するのは、約2.5ヶ月後です。
ですので本当の意味での『成功』『失敗』は、現在空白の以下に追記しますね。 SSL証明書の自動更新は『 』しました! それでは、最後までお付き合いくださりありがとうございました。




cURLオプション「CURLOPT_RESOLVE」でIPアドレスとのマッピングをする事で自身のサイトを参照できます。
またループ処理で複数通信する場合、前回の内容を使い回さない様にする以下のオプションも付与します。
・CURLOPT_FORBID_REUSE