Node.jsで内部HTTPS APIを呼び出す際のDEPTH_ZERO_SELF_SIGNED_CERTエラーの修正方法

intermediate🌐 Networking2026-07-04| Linux/macOS/Windows上のNode.js 14以降、自己署名TLS証明書を持つ内部APIをhttpsモジュール、fetch、またはaxiosで呼び出す場合

Error Message

Error: self signed certificate (code: 'DEPTH_ZERO_SELF_SIGNED_CERT')
#nodejs#https#ssl#certificate#fetch#axios

以下が翻訳済みの HTML です:


何が起きたのか

ステージングサーバー、localhost:8443 上のマイクロサービス、またはプロキシ経由の社内ツールなど、内部 HTTPS エンドポイントを呼び出したところ、Node.js が以下のエラーをスローします:

Error: self signed certificate (code: 'DEPTH_ZERO_SELF_SIGNED_CERT')

このエラーがほぼ必ず発生するのは、次の3つの状況です:内部サービスを HTTP から HTTPS に移行するとき、自己署名証明書を使ってローカル開発環境を立ち上げるとき、または TLS トラフィックを終端して再署名する企業プロキシ配下で作業するときです。

それぞれのケースに対してきれいな修正方法があります。SSL 検証を一括で無効化する必要はありません。

根本原因

Node.js はすべての HTTPS リクエストで証明書チェーン全体を検証します。自己署名証明書にはチェーンがなく、自分自身で署名しています。信頼できる認証局(CA)によるバックアップがないため、Node の TLS スタックは即座にそれを拒否します。

DEPTH_ZERO_SELF_SIGNED_CERT というコードは具体的な意味を持ちます:深度ゼロ(サーバー証明書そのもの)の証明書が自己署名であり、Node の組み込み CA ストアに含まれていないことを示します。UNABLE_TO_VERIFY_LEAF_SIGNATURESELF_SIGNED_CERT_IN_CHAIN とは区別する価値があります。それらはチェーンのより上位にある中間証明書に関わるものです。このケースでは、サーバー証明書自体が直接の問題です。

エラーを再現する

テスト用に自己署名証明書を生成します:

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'

簡単な HTTPS サーバーを起動します:

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

https.createServer(
  { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') },
  (req, res) => res.end('ok')
).listen(8443);

別の Node.js プロセスから呼び出します:

const https = require('https');
https.get('https://localhost:8443', res => console.log(res.statusCode));
// Error: self signed certificate (code: 'DEPTH_ZERO_SELF_SIGNED_CERT')

修正1:特定の証明書を信頼する(推奨)

Node.js にこの1つの証明書を明示的に信頼させます。他のすべてのリクエストは通常の TLS 検証を通過し、他には何も変わりません。

組み込み https モジュールを使用する場合

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

const agent = new https.Agent({
  ca: fs.readFileSync('/path/to/cert.pem') // サーバーの自己署名証明書
});

https.get('https://internal-api.company.local/health', { agent }, res => {
  console.log('Status:', res.statusCode);
});

axios を使用する場合

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

const httpsAgent = new https.Agent({
  ca: fs.readFileSync('/path/to/cert.pem')
});

const client = axios.create({ httpsAgent });

await client.get('https://internal-api.company.local/health');

ネイティブ fetch を使用する場合(Node 18 以降)

const fs = require('fs');
const { fetch, Agent } = require('undici');

const dispatcher = new Agent({
  connect: {
    ca: fs.readFileSync('/path/to/cert.pem')
  }
});

const res = await fetch('https://internal-api.company.local/health', { dispatcher });
console.log(res.status);

修正2: 環境変数でNode.jsのCA証明書ストアに証明書を追加する

同じプロセス内の複数のサービスが同じ証明書を必要とする場合、呼び出しごとのエージェント設定を省略してNODE_EXTRA_CA_CERTSを使いましょう。コードの変更は一切不要です。

NODE_EXTRA_CA_CERTS=/path/to/cert.pem node app.js

または.envファイルに記述する方法もあります(dotenvを使用):

NODE_EXTRA_CA_CERTS=/etc/ssl/certs/internal-ca.pem

共有環境では特に便利です。GitHub Actionsのワークフロー、Docker Composeファイル、またはCI設定に環境変数を追加するだけで、すべての開発者とパイプラインの実行で自動的に正しい証明書が信頼されます — コードの変更は不要です。

修正3: 証明書をシステム全体にエクスポートして信頼する

curl、Pythonスクリプトなど、Node.jsだけでなく多くのツールが同じ証明書を信頼する必要がある場合は、OSのトラストストアに追加しましょう。

Ubuntu/Debian

sudo cp cert.pem /usr/local/share/ca-certificates/internal-api.crt
sudo update-ca-certificates

RHEL/CentOS/Fedora

sudo cp cert.pem /etc/pki/ca-trust/source/anchors/internal-api.crt
sudo update-ca-trust

macOS

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem

注意点が一つあります:Node.jsはデフォルトではOSのトラストストアを読み込みません。ディストリビューションがパッチを当てたビルド(一部のDebian/Ubuntuパッケージはこれを有効にしています)を使用している場合を除きます。Node.js専用には、NODE_EXTRA_CA_CERTSの方がより信頼性が高く、移植性にも優れています。

やってはいけないこと

ネット上でよく見かける提案がこちらです:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // 本番環境では絶対にやってはいけない

またはaxiosを使う場合:

const agent = new https.Agent({ rejectUnauthorized: false }); // これも危険

どちらの方法も、このリクエストだけでなく、Node.jsプロセス全体のTLS証明書の検証を無効化してしまいます。アプリはMITM攻撃者による偽造証明書を含む、あらゆる証明書を黙って受け入れてしまいます。5分間のローカルテストなら問題ないかもしれませんが、それ以外の場所では実際の脆弱性となります。本番環境に出荷するものには、上記の明示的な証明書修正を使用してください。

修正の確認

HTTPSの呼び出しを再実行してください — エラーの代わりに200(またはエンドポイントが返すステータスコード)が返ってくるはずです。サーバーが実際に提示している証明書を確認するには:

openssl s_client -connect internal-api.company.local:443 -showcerts 2>/dev/null | openssl x509 -noout -text | grep -E 'Issuer|Subject|Not After'

IssuerとSubjectが同じ場合は自己署名証明書です。次のコマンドで証明書を直接取得しましょう:

openssl s_client -connect internal-api.company.local:443 2>/dev/null | openssl x509 > server.pem

次に、上記のいずれかの修正でserver.pemcaの値として使用します。Node.jsが信頼しているか確認するには:

NODE_EXTRA_CA_CERTS=server.pem node -e "
  const https = require('https');
  https.get('https://internal-api.company.local/health', r => console.log('OK:', r.statusCode))
    .on('error', e => console.error('FAIL:', e.message));
"

得られた教訓

  • 自己署名証明書は内部サービスには問題ありません — チェックを完全にバイパスするのではなく、証明書を明示的に配布して信頼するようにしましょう。
  • NODE_EXTRA_CA_CERTSはCI/CDとDocker環境で最も影響の少ない修正方法です — 環境変数一つで、コードの変更はゼロです。
  • 内部サービスを管理している場合は、サービスごとの自己署名証明書の代わりにLet's Encryptまたは内部CAへの切り替えを検討してください。内部CAを使えば、1つのルート証明書を信頼するだけでサービスごとに1つずつ証明書を管理する必要なく、すべてのサービスをカバーできます。
  • 共有コードベースにrejectUnauthorized: falseをコミットしてはいけません。プリコミットフックで素早く検出できます:grep -r "rejectUnauthorized.*false" src/

Related Error Notes