更新履歴
2021年10月01日
Let's Encryptルート証明書期限切れで更新エラー(curl error code: 60)が出る場合の対処法
概要
- とある有償SSLサーバ証明書の有効期限がそろそろ切れるので、これを機に、これまでの有償+手動更新から、Let's Encrypt を利用した 無償+自動更新 で幸せになろうと思ったけど、インストールするサーバが Amazon Linux 1 でしかも古いバージョンのため、Let's Encrypt の SSLサーバ証明書をインストールするのにいろいろ試行錯誤した記録。
環境
- Let's Encrypt の SSLサーバ証明書を入れるサーバ
- Amazon Linux 1(AMI release 2015.09)←古すぎ!
$ cat /etc/system-release
Amazon Linux AMI release 2015.09
-
Webサーバ
-
Nginx(BASIC認証でアクセス制限している)
-
ドキュメントルート /usr/share/nginx/html
-
ドメイン
-
foo.staging.co.jp(今回の説明用のダミードメイン)
-
IPアドレス/ポート番号制限
-
任意のIPアドレスから http://foo.staging.co.jp, https://foo.staging.co.jp への接続は全開放
Certbot の断念
無償のSSLサーバ証明書といえば『Let's Encrypt』ということで、公式サイトのGetting Startedを読むと、Certbot ACME client を推奨している。ということで、まずはサーバに Certbot のインストールを試みる。
Amazon Linux 1 は CentOS 6 のパッケージを使えるので、Let's Encrypt 総合ポータル サイトの "CentOS 6 / RHEL 6" のインストール方法 に書いてある通り、EPEL6 リポジトリから入れようと思ったら、certbot パッケージが無いというエラー。確かに、EPEL 6: x86_64 リポジトリ にcertbotパッケージが無い。。
なので、Let's Encrypt 総合ポータル サイトの "その他の UNIX 系 OS" のインストール方法に書いてある通り実行すると・・・FATAL: Amazon Linux support is very experimental at present...
$ wget https://dl.eff.org/certbot-auto
$ chmod a+x certbot-auto
$ ./certbot-auto
Requesting to rerun ./certbot-auto with root privileges...
FATAL: Amazon Linux support is very experimental at present...
if you would like to work on improving it, please ensure you have backups
and then run this script again with the --debug flag!
Alternatively, you can install OS dependencies yourself and run this script
again with --no-bootstrap.
「--debug」オプションを付けて再度実行してみると、次のパッケージの Install/Update をしろと、、
$ ./certbot-auto --debug
〜略〜
Installing:
augeas-libs
gcc48
replacing libquadmath48-devel.x86_64 4.8.3-9.109.amzn1
gcc48-c++
replacing libstdc++48-devel.x86_64 4.8.3-9.109.amzn1
libffi-devel
python27-tools
Updating:
ca-certificates
gcc
openssl
openssl-devel
python27-devel
python27-pip
python27-virtualenv
system-rpm-config
Installing for dependencies:
libgfortran
Updating for dependencies:
cpp48
gcc-c++
gcc-gfortran
gcc48-gfortran
libffi
libgcc48
libgomp
libstdc++48
libtool
python27
python27-libs
Let's Encrypt 総合ポータル サイトに、しれっと注意書きがある。。
うーん、、 Install/Update するのは怖いよね。。
ということで、certbot は諦めて、別の ACME client を使ってみようということで、ACME v2 Compatible Clientsからacme.sh を選択。acme.sh はシェルスクリプトで書かれていて、シェルが動く環境であれば導入が楽ですぐに使えるらしい。
acme.sh を使って Let's Encrypt SSLサーバ証明書をインストールする
acme.sh をインストールする
公式サイトの通りに実行、
# cd /root
# curl https://get.acme.sh | sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
curl: (35) Cannot communicate securely with peer: no common encryption algorithm(s).
あれ、curl が古い。。nssをアップグレードする。
# yum upgrade nss
再度、、
# cd /root
# curl https://get.acme.sh | sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 705 0 705 0 0 4617 0 --:--:-- --:--:-- --:--:-- 4607
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 184k 100 184k 0 0 492k 0 --:--:-- --:--:-- --:--:-- 492k
[Fri Jul 19 12:11:23 JST 2019] Installing from online archive.
[Fri Jul 19 12:11:23 JST 2019] Downloading https://github.com/Neilpang/acme.sh/archive/master.tar.gz
[Fri Jul 19 12:11:24 JST 2019] Extracting master.tar.gz
[Fri Jul 19 12:11:24 JST 2019] It is recommended to install socat first.
[Fri Jul 19 12:11:24 JST 2019] We use socat for standalone server if you use standalone mode.
[Fri Jul 19 12:11:24 JST 2019] If you don't use standalone mode, just ignore this warning.
[Fri Jul 19 12:11:24 JST 2019] Installing to /root/.acme.sh
[Fri Jul 19 12:11:24 JST 2019] Installed to /root/.acme.sh/acme.sh
[Fri Jul 19 12:11:24 JST 2019] Installing alias to '/root/.bashrc'
[Fri Jul 19 12:11:24 JST 2019] OK, Close and reopen your terminal to start using acme.sh
[Fri Jul 19 12:11:24 JST 2019] Installing alias to '/root/.cshrc'
[Fri Jul 19 12:11:24 JST 2019] Installing alias to '/root/.tcshrc'
[Fri Jul 19 12:11:24 JST 2019] Installing cron job
[Fri Jul 19 12:11:24 JST 2019] Good, bash is found, so change the shebang to use bash as preferred.
[Fri Jul 19 12:11:24 JST 2019] OK
[Fri Jul 19 12:11:24 JST 2019] Install success!
入った。
crontab (/var/spool/cron/root) に次のような設定(自動更新用)が追加される。
24 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
Nginx mode で SSLサーバ証明書を発行してみる
- acme.sh のオプション
- --issue・・・SSLサーバ証明書発行
- --nginx・・・Nginx mode
- -d・・・SSLサーバ証明書を発行するドメイン
# cd /root/.acme.sh
# ./acme.sh --issue --nginx -d foo.staging.co.jp
[Fri Jul 19 12:17:47 JST 2019] Create account key ok.
[Fri Jul 19 12:17:47 JST 2019] Registering account
[Fri Jul 19 12:17:48 JST 2019] Registered
[Fri Jul 19 12:17:48 JST 2019] ACCOUNT_THUMBPRINT='1W2zh17CQyWXgr3TCI5DVgl_728ut2X7oi5r4RByFwg'
[Fri Jul 19 12:17:48 JST 2019] Creating domain key
[Fri Jul 19 12:17:48 JST 2019] The domain key is here: /root/.acme.sh/foo.staging.co.jp/foo.staging.co.jp.key
[Fri Jul 19 12:17:48 JST 2019] Single domain='foo.staging.co.jp'
[Fri Jul 19 12:17:48 JST 2019] Getting domain auth token for each domain
[Fri Jul 19 12:17:49 JST 2019] Getting webroot for domain='foo.staging.co.jp'
[Fri Jul 19 12:17:49 JST 2019] Verifying: foo.staging.co.jp
[Fri Jul 19 12:17:49 JST 2019] Nginx mode for domain:foo.staging.co.jp
[Fri Jul 19 12:17:49 JST 2019] Found conf file: /etc/nginx/nginx.conf
[Fri Jul 19 12:17:49 JST 2019] Backup /etc/nginx/nginx.conf to /root/.acme.sh/foo.staging.co.jp/backup/foo.staging.co.jp.nginx.conf
[Fri Jul 19 12:17:49 JST 2019] Check the nginx conf before setting up.
[Fri Jul 19 12:17:49 JST 2019] OK, Set up nginx config file
[Fri Jul 19 12:17:49 JST 2019] nginx conf is done, let's check it again.
[Fri Jul 19 12:17:49 JST 2019] Reload nginx
[Fri Jul 19 12:17:54 JST 2019] foo.staging.co.jp:Verify error:Invalid response from http://foo.staging.co.jp/.well-known/acme-challenge/6Tb5KM3d0jhtiRJ6eoqfa68u_0BFBF1eqgdvtH7g4sU [x.x.x.x]: 401
[Fri Jul 19 12:17:54 JST 2019] Restoring from /root/.acme.sh/foo.staging.co.jp/backup/foo.staging.co.jp.nginx.conf to /etc/nginx/nginx.conf
[Fri Jul 19 12:17:54 JST 2019] Reload nginx
[Fri Jul 19 12:17:54 JST 2019] Please add '--debug' or '--log' to check more details.
[Fri Jul 19 12:17:54 JST 2019] See: https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh
エラー出た。。
Verify error:Invalid response from http://foo.staging.co.jp/.well-known/acme-challenge/6Tb5KM3d0jhtiRJ6eoqfa68u_0BFBF1eqgdvtH7g4sU [x.x.x.x]: 401
そもそも、何やっているの?
acme.sh は SSLサーバ証明書を発行する際に HTTP-01 challenge で検証を行っている。
acme.sh --issue 実行時に、acme.sh は Nginx の "ルートディレクトリ/.well-known/acme-challenge/" ディレクトリ内にトークンファイルを一時的に作成して、acme.sh がトークンファイルの準備が出来たことを Let's Encrypt に伝えたら、Let's Encrypt は "http://foo.staging.co.jp/.well-known/acme-challenge/トークン" にアクセスを試みる。Let's Encrypt が正しい応答を得られたら検証は成功とみなされ、SSLサーバ証明書が発行される。
Nginx mode は、自動的に nginx コンフィグファイルを探して、"ルートディレクトリ/.well-known/acme-challenge/トークンファイル" へのアクセス設定を施して、検証が終わったら、設定を元に戻す。
今回は、Nginx で Basic 認証を行っているため、Let's Encrypt からのアクセスが 401 となり、検証が失敗してしまった。
ちなみに、HTTP-01 challenge アクセスは、Let's Encryptの接続元IPアドレスは公開していないため、クライアント側は HTTP ポート80番で任意のIPアドレスからの接続を許可しておく必要がある。
トークンへのアクセスはBASIC認証対象外として、ノーマルモードで SSLサーバ証明書を発行してみる
まずは、HTTP-01 challenge 用のディレクトリを手動で作成する。
# cd /usr/share/nginx/html
# mkdir -p .well-known/acme-challenge
# chmod -R 755 .well-known/acme-challenge
# chown -R nginx:nginx /usr/share/nginx/html/.well-known/acme-challenge
"http://foo.staging.co.jp/.well-known/acme-challenge/トークン" へのアクセスは BASIC認証対象外とする
/etc/nginx/nginx.conf 設定例
〜
location /.well-known {
satisfy any;
allow all;
}
location /.well-known/acme-challenge {
satisfy any;
allow all;
}
〜
nginx 再起動
# service nginx configtest
# service nginx restart
ノーマルモードでSSLサーバ証明書を発行する
- acme.sh のオプション
- -w・・・ドキュメントルート
# cd /root/.acme.sh
# ./acme.sh --issue -d foo.staging.co.jp -w /usr/share/nginx/html
[Fri Jul 26 12:48:13 JST 2019] Create account key ok.
[Fri Jul 26 12:48:13 JST 2019] Registering account
[Fri Jul 26 12:48:14 JST 2019] Registered
[Fri Jul 26 12:48:14 JST 2019] ACCOUNT_THUMBPRINT='6k1I3oh7R3iEIrnWIEWMsRt0rmUzf4T2CXgn9TaXvoE'
[Fri Jul 26 12:48:14 JST 2019] Creating domain key
[Fri Jul 26 12:48:14 JST 2019] The domain key is here: /root/.acme.sh/foo.staging.co.jp/foo.staging.co.jp.key
[Fri Jul 26 12:48:14 JST 2019] Single domain='foo.staging.co.jp'
[Fri Jul 26 12:48:14 JST 2019] Getting domain auth token for each domain
[Fri Jul 26 12:48:15 JST 2019] Getting webroot for domain='foo.staging.co.jp'
[Fri Jul 26 12:48:15 JST 2019] Verifying: foo.staging.co.jp
[Fri Jul 26 12:48:18 JST 2019] Success
[Fri Jul 26 12:48:18 JST 2019] Verify finished, start to sign.
[Fri Jul 26 12:48:18 JST 2019] Lets finalize the order, Le_OrderFinalize: https://acme-v02.api.letsencrypt.org/acme/finalize/62039946/782487887
[Fri Jul 26 12:48:20 JST 2019] Download cert, Le_LinkCert: https://acme-v02.api.letsencrypt.org/acme/cert/03d9bc51c44775de73e376f19b890cc6f5bf
[Fri Jul 26 12:48:20 JST 2019] Cert success.
-----BEGIN CERTIFICATE-----
MIIFcjCCBFqgAwIBAgISA9m8UcRHdd5z43bxm4kMxvW/MA0GCSqGSIb3DQEBCwUA
〜略〜
5Kh5eZhP
-----END CERTIFICATE-----
[Fri Jul 26 12:48:20 JST 2019] Your cert is in /root/.acme.sh/foo.staging.co.jp/foo.staging.co.jp.cer
[Fri Jul 26 12:48:20 JST 2019] Your cert key is in /root/.acme.sh/foo.staging.co.jp/foo.staging.co.jp.key
[Fri Jul 26 12:48:20 JST 2019] The intermediate CA cert is in /root/.acme.sh/foo.staging.co.jp/ca.cer
[Fri Jul 26 12:48:20 JST 2019] And the full chain certs is there: /root/.acme.sh/foo.staging.co.jp/fullchain.cer
おぉ、発行された!
発行されたSSLサーバ証明書を確認する。
# ./acme.sh --list
Main_Domain KeyLength SAN_Domains Created Renew
foo.staging.co.jp "" no Fri Jul 26 03:48:20 UTC 2019 Tue Sep 24 03:48:20 UTC 2019
発行されたSSLサーバ証明書を Nginx にインストールする
cert keyファイルと、full chain certs ファイルをを置くディレクトリを作成する。
# mkdir -p /etc/nginx/certs/letsencrypt/foo.staging.co.jp
/etc/nginx/certs/letsencrypt/foo.staging.co.jp ディレクトリに、foo.staging.co.jp.key ファイルと、fullchain.cer ファイルをインストール (--install-cert) する。
# cd /root/.acme.sh/
# ./acme.sh --install-cert -d foo.staging.co.jp --key-file /etc/nginx/certs/letsencrypt/foo.staging.co.jp/foo.staging.co.jp.key --fullchain-file /etc/nginx/certs/letsencrypt/foo.staging.co.jp/fullchain.cer
nginx コンフィグファイルで証明書のパスを設定する
/etc/nginx/conf.d/ssl.conf 設定例
〜
ssl_certificate /etc/nginx/certs/letsencrypt/foo.staging.co.jp/fullchain.cer;
ssl_certificate_key /etc/nginx/certs/letsencrypt/foo.staging.co.jp/foo.staging.co.jp.key;
〜
nginx 再起動
# service nginx configtest
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# service nginx restart
Stopping nginx: [ OK ]
Starting nginx: [ OK ]
ブラウザから https://foo.staging.co.jp へアクセスして、SSLサーバ証明書を確認する。
インストール出来た。
自動更新設定
自動更新では、SSLサーバ証明書発行後に、"--install-cert でインストールしてnginxをリロード" させる。
そのジョブを実行する。
# /root/.acme.sh
# ./acme.sh --install-cert -d foo.staging.co.jp --key-file /etc/nginx/certs/letsencrypt/foo.staging.co.jp/foo.staging.co.jp.key --fullchain-file /etc/nginx/certs/letsencrypt/foo.staging.co.jp/fullchain.cer --reloadcmd "service nginx force-reload"
[Fri Jul 19 17:18:49 JST 2019] Installing key to:/etc/nginx/certs/letsencrypt/foo.staging.co.jp/foo.staging.co.jp.key
[Fri Jul 19 17:18:49 JST 2019] Installing full chain to:/etc/nginx/certs/letsencrypt/foo.staging.co.jp/fullchain.cer
[Fri Jul 19 17:18:49 JST 2019] Run reload cmd: service nginx force-reload
Reloading nginx: [ OK ]
これで、cronによる自動更新ジョブで、SSLサーバ証明書発行後に、"--install-cert でインストールしてnginxをリロード" するようになる。
acme.sh インストール時に crontab に自動更新のコマンドが追加されているので、
crontab (/var/spool/cron/root)
24 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
これを使って、手動で強制的 (--force) に更新処理を実行してみる。
# "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" --force
[Fri Jul 26 13:24:13 JST 2019] ===Starting cron===
[Fri Jul 26 13:24:13 JST 2019] Renew: 'foo.staging.co.jp'
[Fri Jul 26 13:24:14 JST 2019] Single domain='foo.staging.co.jp'
[Fri Jul 26 13:24:14 JST 2019] Getting domain auth token for each domain
[Fri Jul 26 13:24:15 JST 2019] Getting webroot for domain='foo.staging.co.jp'
[Fri Jul 26 13:24:15 JST 2019] foo.staging.co.jp is already verified, skip http-01.
[Fri Jul 26 13:24:15 JST 2019] Verify finished, start to sign.
[Fri Jul 26 13:24:15 JST 2019] Lets finalize the order, Le_OrderFinalize: https://acme-v02.api.letsencrypt.org/acme/finalize/62039946/782634405
[Fri Jul 26 13:24:16 JST 2019] Download cert, Le_LinkCert: https://acme-v02.api.letsencrypt.org/acme/cert/037597c36c53830c77a547a4c949f57d0974
[Fri Jul 26 13:24:17 JST 2019] Cert success.
-----BEGIN CERTIFICATE-----
MIIFcjCCBFqgAwIBAgISA3WXw2xTgwx3pUekyUn1fQl0MA0GCSqGSIb3DQEBCwUA
〜略〜
7AFa7o7V
-----END CERTIFICATE-----
[Fri Jul 26 13:24:17 JST 2019] Your cert is in /root/.acme.sh/foo.staging.co.jp/foo.staging.co.jp.cer
[Fri Jul 26 13:24:17 JST 2019] Your cert key is in /root/.acme.sh/foo.staging.co.jp/foo.staging.co.jp.key
[Fri Jul 26 13:24:17 JST 2019] The intermediate CA cert is in /root/.acme.sh/foo.staging.co.jp/ca.cer
[Fri Jul 26 13:24:17 JST 2019] And the full chain certs is there: /root/.acme.sh/foo.staging.co.jp/fullchain.cer
[Fri Jul 26 13:24:17 JST 2019] Installing key to:/etc/nginx/certs/letsencrypt/foo.staging.co.jp/foo.staging.co.jp.key
[Fri Jul 26 13:24:17 JST 2019] Installing full chain to:/etc/nginx/certs/letsencrypt/fullchain.pem
[Fri Jul 26 13:24:17 JST 2019] Installing full chain to:/etc/nginx/certs/letsencrypt/foo.staging.co.jp/fullchain.cer
Reloading nginx: [ OK ]
[Fri Jul 26 13:24:17 JST 2019] Reload success
[Fri Jul 26 13:24:17 JST 2019] ===End cron===
SSLサーバ証明書発行して、インストールして、nginx 再起動してる。
ブラウザからSSLサーバ証明書の有効期限を確認する。
有効期限が新しく発行したSSLサーバ証明書になっていれば、成功。
ちなみに、Let's Encrypt の SSLサーバ証明書の有効期限は90日だけど、
acme.sh は cronで毎日更新処理を試行して、SSLサーバ証明書発行から60日経過している場合に、更新処理を実行する。
補足
バージョン管理している場合は、"ルートディレクトリ/.well-known/acme-challenge/"の追加を忘れずに。
デプロイしたら"ルートディレクトリ/.well-known/acme-challenge/"ディレクトリが消えないように。
運用時に発生した問題
1. 「It seems the CA server is busy now, let's wait and retry. Sleeping 1 seconds.」
いざ、更新時に発生したエラー。
[2020年 4月 22日 水曜日 00:16:02 JST] ===Starting cron===
[2020年 4月 22日 水曜日 00:16:02 JST] Renew: 'ドメイン名'
[2020年 4月 22日 水曜日 00:16:03 JST] Single domain='ドメイン名'
[2020年 4月 22日 水曜日 00:16:03 JST] Getting domain auth token for each domain
[2020年 4月 22日 水曜日 00:21:04 JST] It seems the CA server is busy now, let's wait and retry. Sleeping 1 seconds.
[2020年 4月 22日 水曜日 00:26:05 JST] It seems the CA server is busy now, let's wait and retry. Sleeping 1 seconds.
[2020年 4月 22日 水曜日 00:31:07 JST] It seems the CA server is busy now, let's wait and retry. Sleeping 1 seconds.
[2020年 4月 22日 水曜日 00:36:08 JST] It seems the CA server is busy now, let's wait and retry. Sleeping 1 seconds.
〜
[2020年 4月 22日 水曜日 01:56:36 JST] Create new order error. Le_OrderFinalize not found. {
"type": "urn:ietf:params:acme:error:badNonce",
"detail": "JWS has an invalid anti-replay nonce: \"0002dbS1IRfgFLnk4wAEVzwHbe7-BRBS5HFBRDVqyho6Ri4\"",
"status": 400
}
acme.sh をアップグレードしたら、正常に更新処理が走った。
# acme.sh --upgrade
手動更新
# "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh"
crontab に upgrade を追加
6 0 * * * cd /root/.acme.sh && ./acme.sh --upgrade
Let's Encryptルート証明書期限切れで更新エラー(curl error code: 60)が出る場合の対処法
-
2021年09年30日に、Let's Encryptルート証明書の有効期限が切れた。
【参考】2021年にLet’s Encryptのルート証明書が変更!影響や備えておくべきこととは? -
古いOS(OpenSSL)の場合、そのサーバ内にあるCA証明書ファイルの内容も古い。
新しいルート証明書に対応していない場合、証明書更新処理で次のような CURL エラーが発生する。
https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 60
# "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh"
[Thu Sep 30 23:22:41 UTC 2021] ===Starting cron===
[Thu Sep 30 23:22:41 UTC 2021] Renew: '***.***.***.jp'
[Thu Sep 30 23:23:00 UTC 2021] Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 60
[Thu Sep 30 23:23:02 UTC 2021] Can not init api for: https://acme-v02.api.letsencrypt.org/directory.
[Thu Sep 30 23:23:02 UTC 2021] Sleep 10 and retry.
- 対処法として、新しいCA証明書ファイルに置き換える方法がある
- 最新のCA証明書ファイルをダウンロードして、対象サーバの /etc/pki/tls/certs/ ディレクトリ配下に置く
https://curl.haxx.se/ca/cacert.pem - 対象サーバにSSHログインして、現存の ca-bundle.crt をバックアップとり、cacert.pem を ca-bundle.crt にリネームし、パーミッションを変更する。
# cd /etc/pki/tls/certs/
# mv ca-bundle.crt ca-bundle.crt_20211001
# mv cacert.pem ca-bundle.crt
# chmod 644 ca-bundle.crt
- 手動で強制自動更新を実行して動作確認
# "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" --force