WordPressのcURL error 60の原因と解決方法。.user.iniで直らないケースをmu-pluginsで解決する
WordPressの管理画面からプラグインをインストールしようとして、これで止まったことはないでしょうか。
インストールに失敗しました: ダウンロードに失敗しました。
cURL error 60: SSL certificate problem: unable to get local issuer certificate
プラグインそのものの問題ではありません。サーバー側でSSL証明書の検証ができていないことが原因です。
この記事では、原因の仕組みから実際に効いた解決方法までを、再現できる手順として書きます。とくに、多くの記事で紹介されるphp.ini / .user.iniにcurl.cainfoを設定する方法で直らないケースがあるので、その場合にmu-pluginsを使って確実に解決する手順まで書きます。私自身が実案件でこの直らないケースにハマりました。
体感としては、更新がほとんどされていない古いサーバー環境で起きやすいです。長く手を入れられないまま動いているサイトほど、CA証明書のパスが設定されないまま取り残されている。実際に遭遇したのも、そういう環境のサイトを引き継いだときでした(ASJのサーバーなど)。
何年も前に作られてそのまま動いているサイトを引き継いだときは、一度確認しておくと安心です。あとで書きますが、更新チェックが静かに止まっている可能性もあります。
なお、この記事はレンタルサーバー上のWordPress(CGI版PHPで動作しているケース)を想定しています。パスやドメインはご自身の環境に読み替えてください。
エラーの意味:cURL error 60とは何か
WordPressがプラグインをインストールするとき、WordPress.orgの配布サーバーへHTTPS(cURL)で接続してファイルをダウンロードします。HTTPS通信では、接続先が本物か、なりすましでないかを証明書で検証します。
この検証には、信頼できる認証局(CA)の証明書リスト(cacert.pem)がサーバー側に必要です。いわば、本物と認めてよい発行元の名簿です。
unable to get local issuer certificate(発行元の証明書をたどれない)というメッセージは、この名簿がサーバーに存在しない、または場所が設定されていないことを意味します。相手が本物か確認できないため、安全のために通信を打ち切っている。これがcURL error 60の正体です。
エラーではありますが、動作としてはむしろ正しいと思います。サーバーは危険を避けて止まっているだけで、壊れているわけではありません。名簿を用意すれば正常に通ります。
プラグイン導入だけの問題ではない
根本原因は、サーバーが外部とHTTPS通信するときに相手を検証できないことです。つまり、プラグインのインストール以外にも影響します。
- WordPress本体・プラグイン・テーマの自動更新チェック
- 外部APIとの連携
- その他、外部へHTTPS通信するあらゆる処理
これらも同じ理由で、気づかないうちに失敗している可能性があります。プラグインインストールという目に見える操作で、たまたま表面化しただけ、というケースが多い。
裏を返すと、放置すると更新チェックも止まったままになります。見つけたときに直しておくべき類のエラーだと思います。
解決の全体像
やることは2つだけです。
- 信頼できるCA証明書(
cacert.pem)をサーバーに設置する - WordPressに「この証明書を使え」と教える
つまずきやすいのは、2の教え方のほう。一般的にはphp.ini / .user.iniでcurl.cainfoを設定しますが、環境によってはこれが効きません。順に見ていきましょう。
手順1:原因を確認する
最初に、CA証明書のパスが本当に未設定なのかをphpinfo()で確かめましょう。info.phpというファイルを作り、中身を次の1行にします。
<?php phpinfo();
これをドキュメントルート(WordPressのある場所)にアップロードし、ブラウザでhttps://(あなたのサイト)/info.phpを開きます。表示された一覧から、ブラウザの検索機能で次の項目を探してください。
curl.cainfoopenssl.cafile
これらの値がno value(未設定)になっていれば、原因が確定です。
このとき、あわせてDOCUMENT_ROOTの値もメモしておくと、あとで証明書の絶対パスを組み立てるのに使えます(例:/home/users/○○/アカウント名/web/example.com)。
確認が終わったら、info.phpは必ず削除してください。サーバーの内部パスや設定が丸見えになるファイルなので、公開状態のまま残すのはセキュリティ上ありえません。
手順2:CA証明書(cacert.pem)を用意する
信頼できる認証局の証明書リストは、cURL公式が配布しています。
取得元はhttps://curl.se/ca/cacert.pemです。
ブラウザで開き、そのページを「名前を付けて保存」して、ファイル名をcacert.pemにします。
よくある失敗が、ページをHTMLとして保存してしまうことです。中身がHTMLファイルになると、後の工程で通りません。保存後、ファイルサイズが200KB前後あるか、中身が##で始まる説明文や-----BEGIN CERTIFICATE-----を含むテキストになっているかを確認しておくと安心です。
手順3:サーバーに設置し、絶対パスを確定する
FTPでcacert.pemをアップロードします。置き場所は、外部からURLで直接ダウンロードされない場所が理想です。具体的には、公開ディレクトリ(webなど)の親階層にcertsフォルダを作って置きます。
/home/users/○○/アカウント名/certs/cacert.pem ← 公開領域の外(理想)
構成上どうしても公開ディレクトリの中にしか置けない場合は、ドキュメントルート直下に置いたうえで、同じディレクトリの.htaccessに次を追記して直接アクセスを禁止します。
<Files "cacert.pem">
Require all denied
</Files>
絶対パスを確実に確認する
このあと指定するのは、/home/...から始まる絶対パスです。FTP上の表示は相対的なことが多いので、次のスクリプトで実在とパスを確認すると確実です。
<?php
$p = '/home/users/○○/アカウント名/certs/cacert.pem';
var_dump(file_exists($p)); // 実在するか
echo "\n";
echo realpath($p); // 正確な絶対パス
ドキュメントルートに置いてブラウザで開き、bool(true)とパスが表示されれば、設置とパスが正しいことが確定します(確認後は削除)。bool(false)なら置いた階層がずれているので、certsの実際の位置を見直します。
手順4:.user.iniで直らないケースがある
多くの記事では、ここで.user.ini(または管理パネルのPHP設定)に次を書けば解決、と説明されています。
curl.cainfo = "/home/users/○○/アカウント名/certs/cacert.pem"
openssl.cafile = "/home/users/○○/アカウント名/certs/cacert.pem"
ところが、環境によってはこれが効きません。私が対応したケースでは、これを書いて反映を待ってもphpinfoのcurl.cainfoはno valueのままでした。
なぜ効かないのか
.user.ini自体が読まれていないのか、それともcurl.cainfoだけ効かないのか。切り分けるため、.user.iniに別の設定を一緒に書いて試します。
memory_limit = 512M
反映後にphpinfoを見ると、memory_limitは512Mに変わっているのに、curl.cainfoはno valueのまま。
これで原因が確定します。.user.iniはちゃんと効いている。にもかかわらずcurl.cainfoだけ反映されない。理由は、curl.cainfoの設定変更レベルがPHP_INI_SYSTEMであり、ユーザーレベルの.user.iniからは変更できないためです。memory_limitはPHP_INI_ALLなので変えられますが、curl.cainfoは権限レベルが上なので無視されていた、という仕組みでした。
サーバー全体のphp.iniを編集できる環境なら別ですが、共有レンタルサーバーではそこに触れないことがほとんどです。だからアプローチを切り替えます。
手順5:mu-pluginsでCA証明書パスを指定する(本命)
PHPの設定を変えるのをやめて、WordPress側から通信時に証明書のパスを渡します。この方法なら確実です。
テーマのfunctions.phpに書く手もありますが、テーマ更新で消える可能性があるため、mu-plugins(must-use plugins)に置くのがおすすめです。ここに置いたPHPは常に自動で有効になり、通常のプラグイン一覧にも出ないので、他の管理者に誤って無効化される心配もありません。
ca-fix.phpというファイルを作り、中身を次のようにします(パスはご自身の環境に置き換えてください)。
<?php
/**
* WordPressのHTTPリクエストで使用するCA証明書のパスを明示指定する
* サーバーのcurl.cainfoが未設定のため、WordPress側から証明書を指定する
*/
$ca_path = '/home/users/○○/アカウント名/certs/cacert.pem';
// WordPressのHTTP API本体に証明書パスを渡す(本命)
add_filter('http_request_args', function ($args) use ($ca_path) {
if (file_exists($ca_path)) {
$args['sslcertificates'] = $ca_path;
}
return $args;
}, 10, 1);
// cURLにも直接指定(二重の保険)
add_action('http_api_curl', function ($handle) use ($ca_path) {
if (file_exists($ca_path)) {
curl_setopt($handle, CURLOPT_CAINFO, $ca_path);
}
}, 10, 1);
FTPでwp-content/の中にmu-pluginsフォルダを作成し(無ければ新規作成)、その中に置きます。
wp-content/mu-plugins/ca-fix.php
mu-plugins内のPHPは、置いた瞬間から自動で有効になります。有効化操作は不要で、.user.iniのようなキャッシュ待ちもありません。
効いているかの確認
WordPress管理画面の「プラグイン」ページに「必須プラグイン」タブが出ていれば、ファイルは認識されています。あとは実際にプラグインのインストールを再度試して、cURL error 60が消えて通れば解決です。
一点補足しておくと、この対処の効果はphpinfoでは確認できません。PHPの設定値(curl.cainfo)はそのままで、WordPressの通信時にパスを渡しているだけだからです。確認は「実際にインストールが通るか」で行ってください。
なぜsslcertificatesが効くのか
http_api_curlフックでのCURLOPT_CAINFO指定だけでは効かない環境があります。一方、http_request_argsフィルタでsslcertificatesを指定すると、WordPressのHTTP API本体がこのパスを見て検証するため、確実に通ります。実際のケースでも、このsslcertificatesの指定を加えたことで解決しました。
FTPさえあれば完結する
この方法の利点は、サーバーの管理パネルに触れなくても解決できることです。
パネルのログイン情報をもらえていない、php.iniを編集する権限がない。引き継ぎ案件では珍しくない状況ですが、FTPさえ通っていれば対処できます。ASJのサーバーで発生したケースでも、cacert.pemとca-fix.phpをアップロードした時点で有効になり、そのままプラグインのインストールが通るようになりました。
やってはいけない対処:SSL検証の無効化
検索すると、次のような回避策が出てきます。採用しないでください。
// NG例:絶対に使わない
add_filter('https_ssl_verify', '__return_false');
SSL検証を無効化すると、通信の中間者攻撃に対して無防備になります。サイトのセキュリティを下げる対処であり、本番の顧客サイトでは論外だと考えています。
今回のmu-pluginsの方法は、検証は有効なまま、検証に使う正しい証明書の場所を教えているだけなので、セキュリティは一切下げていません。手間は少しかかりますが、こちらが正しい解決だと思います。
後片付け
解決したら、調査で置いた一時ファイルを削除します。
削除するもの
info.php(phpinfoの確認用)- パス確認用に置いたスクリプト(
checkca.phpなど)
残すもの(セットで解決を支えているので消さない)
certs/cacert.pem(証明書の実体)wp-content/mu-plugins/ca-fix.php(今回のコード)
.user.iniに書いたcurl.cainfo / openssl.cafileの行は、効かないと分かったので削除して構いません。
mu-plugins/ca-fix.phpはサイト固有の対処です。将来cacert.pemを移動・削除すると再発するので、証明書ファイルは触らないファイルとして認識しておいてください。サイトの引き継ぎや保守者の交代があるときは、この対処が入っていることをひと言申し送りしておくと、後の人が助かります。
まとめ
cURL error 60: unable to get local issuer certificateは、サーバーにCA証明書の場所が設定されていないことが原因- プラグイン導入だけでなく、更新チェックなど外部HTTPS通信全般に影響している可能性がある
- CA証明書(
cacert.pem)はcURL公式から取得し、公開領域の外に設置する curl.cainfoはPHP_INI_SYSTEMのため、.user.iniでは設定できないことがある(memory_limitが効くかどうかで切り分けられる)- その場合は
mu-pluginsでsslcertificatesにパスを渡すと確実に解決する - この方法はFTPだけで完結するので、管理パネルを触れない引き継ぎ案件でも対処できる
- SSL検証の無効化は絶対に避け、証明書を正しく設定して直す
.user.iniを書いても直らずに詰まっている方は、curl.cainfoの権限レベルを疑って、mu-pluginsからの指定を試してみてください。
こういう、表からは見えないところで気づかないうちに不具合が起きている状態は、サイトを長く運用しているとどうしても起きます。自社サイトで心当たりのある方は、一度チェックしてみてください。
納品して終わりではありません。サイト公開後も相談できる制作者をお探しの方は、まずはお話をお聞かせください。