概要
Let's Encryptの証明書自動更新をcronで設定していたにも関わらず、3ヶ月間更新が失敗し続け、証明書の有効期限当日に気づいて慌てて対応した話です。原因はcronジョブのPATH環境変数という、よくある落とし穴でした。
問題の発生
ある日、サイトのSSL証明書を確認すると、なんと有効期限が当日になっていました。
$ sudo certbot certificates
Certificate Name: point.stock-lab.com
Expiry Date: 2025-08-26 00:23:31+00:00 (INVALID: EXPIRED)
おかしい。確かにcronで自動更新を設定したはずなのに...
調査開始
1. cron設定の確認
$ sudo crontab -l
0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx" >> /var/log/certbot_renew.log 2>&1
設定は存在している。毎日午前3時に実行されるはず。
2. ログを確認して愕然
$ sudo cat /var/log/letsencrypt/letsencrypt.log | tail -20
2025-08-26 03:02:52,906:ERROR:certbot._internal.renewal:Failed to renew certificate point.stock-lab.com with error:
The nginx plugin is not working; there may be problems with your existing configuration.
The error was: NoInstallationError("Could not find a usable 'nginx' binary.
Ensure nginx exists, the binary is executable, and your PATH is set correctly.")
「nginxが見つからない」だと? エラーメッセージに従って、nginxの存在を確認してみる。
3. nginxのパスを調査
# nginxの実行ファイルの場所を確認
$ which nginx
/usr/sbin/nginx
$ whereis nginx
nginx: /usr/sbin/nginx /usr/lib64/nginx /etc/nginx /usr/share/nginx
# nginxが実際に存在し、実行可能か確認
$ ls -la /usr/sbin/nginx
-rwxr-xr-x 1 root root 1401224 Dec 7 2023 /usr/sbin/nginx
nginxは確実に存在する。 では、なぜcronでは見つからないのか?
4. 手動実行でテスト
# 手動でcertbot renewを実行してみる
$ sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded
手動実行では成功する! ということは、cronの実行環境に何か違いがあるはず...
5. PATH環境変数の違いを発見
# 現在のPATH環境変数を確認
$ echo $PATH
/home/ec2-user/.local/bin:/home/ec2-user/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
待てよ、/usr/sbin
がPATHに含まれている。もしかしてcronではPATHが違うのでは?
原因判明:cronのPATH問題
cronジョブの罠
実はcronジョブは最小限のPATH環境変数で実行されます。
通常のSSHログイン時のPATH:
$ echo $PATH
/home/ec2-user/.local/bin:/home/ec2-user/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
cronのデフォルトPATH:
PATH=/usr/bin:/bin
見事に/usr/sbin
(nginxの場所)が含まれていません!
なぜテストで見つからなかった?
# この通常のテストは、SSHセッションのPATHを使うので成功する
$ sudo certbot renew --dry-run # ✅ 成功
# cronと同じ環境でテストすると失敗する
$ sudo env PATH=/usr/bin:/bin certbot renew --dry-run # ❌ 失敗
つまり、テスト環境と実行環境が異なっていたのが見落としの原因でした。
解決方法
1. 緊急対応:手動で証明書更新
# webrootモードで即座に更新
$ sudo certbot renew --webroot -w /var/www/html --force-renewal
Congratulations, all renewals succeeded:
/etc/letsencrypt/live/point.stock-lab.com/fullchain.pem (success)
2. crontabの修正
$ sudo crontab -e
# PATHを明示的に設定
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/bin/certbot renew --webroot -w /var/www/html --quiet --deploy-hook "/usr/sbin/nginx -s reload" >> /var/log/certbot_renew.log 2>&1
変更点:
- PATH環境変数を追加
--webroot
モードを明示的に指定-
nginxのフルパスを指定(
/usr/sbin/nginx
)
3. 動作確認
$ sudo certbot renew --webroot -w /var/www/html --dry-run
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/point.stock-lab.com/fullchain.pem (success)
教訓とベストプラクティス
1. cronジョブでは必ずPATHを設定する
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# または、コマンドはフルパスで記述
2. cronジョブのテストは実環境で
# 方法1: 環境変数を制限してテスト
$ sudo env PATH=/usr/bin:/bin your-command
# 方法2: 実際にcronで実行してログ確認
* * * * * /path/to/command >> /tmp/test.log 2>&1
3. ログの定期確認を忘れない
# 月1回は確認
$ sudo tail /var/log/certbot_renew.log
$ sudo certbot certificates
4. 監視・アラートの設定
証明書の有効期限を監視するスクリプトを作成:
#!/bin/bash
# check-ssl-expiry.sh
DAYS_BEFORE_EXPIRY=30
DOMAIN="point.stock-lab.com"
expiry_date=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry_date" +%s)
current_epoch=$(date +%s)
days_left=$(( ($expiry_epoch - $current_epoch) / 86400 ))
if [ $days_left -lt $DAYS_BEFORE_EXPIRY ]; then
echo "WARNING: SSL certificate expires in $days_left days!"
# メール送信やSlack通知など
fi
よくある落とし穴チェックリスト
- cronにPATH環境変数を設定したか?
- コマンドはフルパスで記述したか?
-
ログ出力先を指定したか?(
2>&1
を忘れずに) - 実環境でテストしたか?
- ログの定期確認体制はあるか?
- webrootディレクトリの権限は適切か?
- ファイアウォールでポート80が開いているか?
まとめ
「cronで設定したから安心」と思い込んでいましたが、実際には3ヶ月間失敗し続けていました。原因は単純なPATH環境変数の問題でしたが、手動テストでは発見できない罠でした。
この経験から学んだのは:
- cronジョブは特殊な環境で動くことを意識する
- テストは本番と同じ環境で行う
- ログの確認を習慣化する
Let's Encryptの証明書は90日で期限切れになるため、60日ごとに更新されることを確認する仕組みも重要です。自動化は便利ですが、自動化したものが正しく動いているかの確認も自動化すべきですね。
参考リンク
この記事が同じ問題で悩む方の助けになれば幸いです。