Node.jsにおけるDEPTH_ZERO_SELF_SIGNED_CERTの解決方法(SSL/TLSガイド)

intermediate🔒 SSL/TLS2026-05-01| Node.js (v14以降)、Linux/macOS/Windows、Axios、Node Fetch、またはネイティブのHTTPSモジュール

Error Message

Error: self signed certificate (DEPTH_ZERO_SELF_SIGNED_CERT)
#nodejs#https#ssl#自己署名証明書#fetch#axios

コンテキスト

最近、マイクロサービスを社内の在庫システムに接続しようとした際、行き詰まった障害にぶつかりました。このレガシーAPIは、自己署名SSL証明書を使用してローカルサーバー上の 10.0.0.50:8443 で動作していました。ブラウザでは「詳細設定」から「(安全ではありませんが)続行する」をクリックできますが、Node.jsはそれほど甘くありません。常に利便性よりもセキュリティを優先します。

Axiosリクエストがその社内エンドポイントに到達した瞬間、アプリケーションは停止しました。残されたのは次のエラーメッセージだけでした:

Error: self signed certificate (DEPTH_ZERO_SELF_SIGNED_CERT)

Node.jsは、ハードコードされた信頼された認証局(CA)のリストに依存しています。Let's Encryptのような主要なプロバイダーによって署名されていない証明書を持つサーバーにアクセスすると、Node.jsは接続を遮断します。これは中間者攻撃を受けているとみなすためです。これは本番環境では不可欠ですが、社内開発においては大きな苦労の種となります。

デバッグプロセス

DEPTH_ZERO_SELF_SIGNED_CERT というコードが決定的な証拠です。これは、チェーンの最下層(デプスゼロ)にある証明書が自己署名されていることを意味します。そのサーバーが自称通りの存在であることを証明するルートCAが存在しないのです。

サーバー側の設定ミスを除外するために、curl を使ってエンドポイントをテストしました:

curl -I https://internal-api.local/data

出力結果は私の疑いを裏付けました。Curlも同様に「信頼できない証明書」という警告を出したのです。しかし、curl -k を実行すると正常に動作しました。これにより、サーバー自体は健全であり、問題は純粋にNode.jsクライアントが検証をどのように処理するかにあることが証明されました。

解決策

これに対処する方法は3つあります。手っ取り早い「核の選択肢」から、本番環境に対応した設定まで多岐にわたります。

1. 「核の選択肢」(開発環境のみ)

迅速に作業を進める必要があり、完全にサンドボックス化されたローカル環境で作業している場合は、Node.jsにすべてのSSLエラーを無視させる強制的な手段があります。これは環境変数を介して処理されます。

オプションA:コード内で設定する

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

// 以降にAxios/Fetchのコードを記述
axios.get('https://internal-api.local/data');

オプションB:ターミナル経由で設定する

export NODE_TLS_REJECT_UNAUTHORIZED=0
node app.js

警告: これを使用する際は細心の注意を払ってください。これはすべてのリクエストに対してSSL検証を無効にします。アプリはStripe、AWS、その他の外部APIへの接続も検証しなくなり、データ盗難に対して無防備な状態になります。

2. サージカル・アプローチ(特定のクライアントに限定)

より良い方法は、HTTPクライアントに特定の証明書を信頼させることです。グローバルなセキュリティは維持したまま、社内サーバーに対してのみ単一の例外を作成します。

証明書ファイルを使用してAxiosを使う場合:

const https = require('https');
const fs = require('fs');

const agent = new https.Agent({
  ca: fs.readFileSync('./certs/internal-server.pem')
});

axios.get('https://internal-api.local/data', { httpsAgent: agent });

.pem ファイルを持っていないが、リスクを単一のクライアントインスタンスに限定したい場合は、そのエージェントに対してのみ認証を無効にすることができます:

const agent = new https.Agent({
  rejectUnauthorized: false
});

axios.get('https://internal-api.local/data', { httpsAgent: agent });

3. 企業の標準:NODE_EXTRA_CA_CERTS

企業環境では、社内のルートCAが存在する可能性があります。すべての https.Agent を修正する代わりに、Node.jsに対して信頼された認証局のリストをグローバルに拡張するように指示できます。

export NODE_EXTRA_CA_CERTS="/path/to/company-root-ca.pem"
node app.js

これはチームにとって最もクリーンな方法です。Node.jsに社内インフラを信頼させつつ、インターネットの他の部分に対しては標準のセキュリティチェックを維持させることができます。

検証手順

エラーが消えたからといって、修正されたと思い込まないでください。以下の手順に従って、セキュリティに穴を開けていないか確認しましょう:

  • ステップ1: 環境から NODE_TLS_REJECT_UNAUTHORIZED 変数を削除します。
  • ステップ2: ca プロパティを使用して https.Agent の修正を適用します。
  • ステップ3: アプリが社内APIからデータを取得できることを確認します。
  • ステップ4(決定的なテスト): 追加していない別の社内HTTPSサイトへの接続を試みます。それは引き続き失敗するはずです。これにより、セキュリティフィルターが依然として機能していることが証明されます。

学んだ教訓

このエラーはバグではなく、Node.jsが正しく仕事をしている結果です。このデバッグセッションからの主な教訓は以下の通りです:

  • 環境変数は残り続ける: ターミナルセッションで NODE_TLS_REJECT_UNAUTHORIZED=0 を設定したまま忘れてしまうのは簡単です。これはセキュリティが不十分なコードをリリースすることにつながります。
  • 整理が重要: 社内証明書は専用の /certs フォルダに保存しましょう。ただし、.gitignore に追加するのを忘れないでください。
  • ピンポイントで対処する: グローバルなフラグよりも、常に https.AgentNODE_EXTRA_CA_CERTS を優先してください。高いセキュリティと開発者の生産性は、必ずしも相反するものではありません。

Related Error Notes