TL;DR
問題の原因に応じた3つの修正方法:
- レジストリの証明書が期限切れ → 更新する(Let's Encrypt またはお使いのCA)
- システムクロックがずれている → NTPで同期する(
timedatectl set-ntp true) - 開発・テスト環境で気にしない場合 → Dockerデーモン設定の
insecure-registriesにレジストリを追加する
エラーの全文
Get "https://registry.example.com/v2/": x509: certificate has expired or is not yet valid: current time 2025-01-01T00:00:00Z is after 2024-12-31T23:59:59Z
このエラーは docker pull、docker push、または docker login — レジストリへのTLS接続を開くあらゆる操作で発生します。エラーメッセージに含まれるタイムスタンプが最初の手がかりです。マシンで date を実行し、Dockerが検出した時刻と比較してください。この30秒のチェックで大抵の原因がわかります。
根本原因
すべてのTLS証明書には notBefore と notAfter という2つのフィールドで定義された有効期間があります。DockerのGo TLSスタックは、どちらかの境界を越えた瞬間に接続を拒否します:
- 証明書の
notAfterが過去の日時 — レジストリ証明書が本当に期限切れになっている - ローカルクロックが
notAfterより進んでいる — クロックのズレにより有効な証明書が期限切れに見える - ローカルクロックが
notBeforeより遅れている — 証明書がまだ有効でないように見える
クロックのズレは見た目より厄介です。数時間サスペンドされたVM、NTPのないコンテナホスト、5分ほどずれたノートPC — これらのいずれも、本当に期限切れになった証明書とまったく同じエラーを引き起こす可能性があります。
ステップ1 — 証明書の有効期限を確認する
推測せず、証明書を直接取得して日付を確認してください:
# レジストリの証明書を直接確認する
openssl s_client -connect registry.example.com:443 -servername registry.example.com 2>/dev/null \
| openssl x509 -noout -dates
# 出力例:
# notBefore=Dec 1 00:00:00 2024 GMT
# notAfter=Dec 31 23:59:59 2024 GMT
notAfter が今日より前であれば、証明書は期限切れです — 修正Aへ進んでください。日付が問題なく見える場合は、システムクロックがDockerに誤った情報を伝えています。
ステップ2 — システムクロックのズレを確認する
# 現在時刻とNTP同期ステータスを確認する
timedatectl status
# または単純に:
date -u
1〜2分以上ずれている場合は修正してください:
# NTP同期を有効にする(systemdベースのLinux)
timedatectl set-ntp true
# 即時ステップ補正を強制する
sudo chronyc makestep
# または
sudo ntpdate -u pool.ntp.org
サスペンドされたVMがここでの主な原因です — ホストがスリープ中はクロックが止まり、起動時には数時間遅れている可能性があります。同期後に docker pull を再試行してください。クロックが原因だった場合はすぐに動作します。
修正A — レジストリ証明書を更新する
レジストリを管理していて証明書が切れている場合は、置き換えが必要です。
Let's Encrypt(Certbot)
# すべての証明書を更新する
sudo certbot renew
# 期限が近くなくても強制更新する
sudo certbot renew --force-renewal --cert-name registry.example.com
その後、レジストリサービスを再起動します:
# Dockerコンテナとして実行している場合
docker restart registry
# またはsystemd
sudo systemctl restart docker-registry
自己署名証明書(新しいものを生成する)
openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout /certs/domain.key \
-x509 -days 365 \
-out /certs/domain.crt \
-subj "/CN=registry.example.com" \
-addext "subjectAltName=DNS:registry.example.com"
新しい証明書パスをマウントしてレジストリコンテナを再起動してください。すべてのDockerクライアントマシンも新しい証明書を信頼する必要があります — 修正Bを参照してください。
修正B — 自己署名証明書または内部CA証明書を信頼する
企業のレジストリや内部CAはDockerのデフォルトのトラストストアに含まれていません。修正方法は、Dockerのレジストリ専用ディレクトリに証明書を配置することです:
# このレジストリ用ディレクトリを作成する
sudo mkdir -p /etc/docker/certs.d/registry.example.com:5000
# CA証明書(または自己署名証明書)をコピーする
sudo cp ca.crt /etc/docker/certs.d/registry.example.com:5000/ca.crt
デーモンの再起動は不要です — Dockerは接続のたびにこのディレクトリを読み込みます。
curl、openssl、その他すべてが証明書を受け入れるようにシステム全体で信頼させたい場合は:
# Ubuntu/Debian
sudo cp ca.crt /usr/local/share/ca-certificates/registry-ca.crt
sudo update-ca-certificates
# RHEL/CentOS/Fedora
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/registry-ca.crt
sudo update-ca-trust
修正C — 安全でないレジストリ(開発・テスト環境のみ)
CIパイプラインやローカル開発ループで今すぐ動かす必要がある場合は、レジストリを安全でないものとしてマークしてください。本番環境での使用は絶対に避けてください。
/etc/docker/daemon.json を編集します:
{
"insecure-registries": ["registry.example.com:5000"]
}
デーモンを再起動します:
sudo systemctl restart docker
Dockerはそのレジストリに対してHTTPで接続するか、TLS検証をスキップします。設定は簡単ですが、本番環境では本当に危険です — 警告しました。
修正の確認
# プルを再試行する
docker pull registry.example.com/myimage:latest
# 証明書が少なくとも24時間以上有効であることを確認する
openssl s_client -connect registry.example.com:443 -servername registry.example.com 2>/dev/null \
| openssl x509 -noout -checkend 86400
# 有効な場合:"Certificate will not expire" と出力される
# レジストリv2 APIに直接アクセスする
curl -v https://registry.example.com/v2/
再発防止策
- 証明書の更新を自動化する:cronまたはsystemdタイマーで
certbot renewを実行し、期限切れの当日ではなく30日前に実施する ssl-cert-checkまたはPrometheusのssl_expiryエクスポーターで有効期限を監視し、14日前に通知を受け取る- すべてのDockerホスト、特にVMでNTPを常時稼働させる —
timedatectl show | grep NTPSynchronizedで素早く確認できる - 365日ローテーションの自己署名証明書は思ったより早く期限が切れる。ローテーションを自動化するか、Let's Encryptに切り替えることを検討する
クイックリファレンス
# 証明書の有効期限を確認する
openssl s_client -connect HOST:PORT 2>/dev/null | openssl x509 -noout -dates
# クロックを同期する
timedatectl set-ntp true && sudo chronyc makestep
# CA証明書を信頼する(Dockerのみ)
sudo mkdir -p /etc/docker/certs.d/HOST:PORT
sudo cp ca.crt /etc/docker/certs.d/HOST:PORT/ca.crt
# 安全でないレジストリのフォールバック(開発環境のみ)
# /etc/docker/daemon.json → { "insecure-registries": ["HOST:PORT"] }
sudo systemctl restart docker

