問題の概要
Nginxのerror.logを確認した際に、不可解なハンドシェイクの失敗が見つかった場合、それは証明書の信頼性の問題である可能性が高いです。この特定のエラーは、通常、NginxがHTTPSを必要とするバックエンドサーバーのリバースプロキシとして動作しているときに発生します。ログのエントリは通常、以下のようになります。
[error] 12345#0: *678 SSL_do_handshake() failed (SSL: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed) while SSL handshaking to upstream, client: 1.2.3.4, server: example.com, request: "GET /api HTTP/1.1", upstream: "https://10.0.0.5:443/api", host: "example.com"
問題の根本的な原因は、Nginxがバックエンド(アップストリーム)と通信する際にウェブブラウザのように振る舞うことにあります。Nginxにバックエンドの身元を確認するように設定しているにもかかわらず、信頼された認証局のリストと照らし合わせて証明書を検証できない場合、データの安全性を保つために接続を即座に切断します。
要約:クイックフィックス
セキュリティが最優先事項ではないローカル開発や内部テストの場合は、Nginxのチェックを緩和させることができます。locationブロックにproxy_ssl_verify off;を追加することで、検証をスキップできます。
location / {
proxy_pass https://backend_upstream;
proxy_ssl_verify off;
}
警告: 本番環境では絶対にこれを使用しないでください。SSL検証をバイパスすると、内部トラフィックが中間者攻撃(man-in-the-middle attacks)に対して脆弱になります。
ハンドシェイクが失敗する理由
- **信頼されていない証明書:** バックエンドが自己署名証明書や、Nginxが認識していないプライベートCA(社内ルートCAなど)を使用している。
- **CAチェーンの欠落:** Nginxに、完全な証明書パスを検証するために必要な特定のルートCAバンドルが不足している。
- **SNIの不一致:** AWS、Cloudflare、Kubernetes上のものなど、最近のバックエンドはServer Name Indication (SNI) を期待しています。Nginxがホスト名を送信しない場合、バックエンドは誤った証明書を返します。
- **証明書の期限切れ:** バックエンドの証明書が有効期限を過ぎているか、Nginxサーバーのシステムクロックが同期されていません。
ステップバイステップの解決策
方法1:バックエンド証明書を信頼する(推奨)
バックエンドがプライベートCAを使用している場合、Nginxにその資格情報を見せる必要があります。まず、バックエンドのCA証明書を.crtまたは.pemファイルとしてエクスポートします。次に、その特定のファイルを信頼するようにNginxに明示的に指示します。
location / {
proxy_pass https://backend_server;
proxy_ssl_trusted_certificate /etc/nginx/certs/internal-ca.crt;
proxy_ssl_verify on;
proxy_ssl_verify_depth 2;
}
方法2:SNIサポートを有効にする
デフォルトでは、NginxはバックエンドとのSSLハンドシェイク中にホスト名を渡しません。これにより、AWS Application Load Balancer (ALB) や Kubernetes Ingressコントローラーなどのサービスにプロキシする際に、しばしば失敗が発生します。これは proxy_ssl_server_name を有効にすることで解決できます。
location / {
proxy_pass https://api.internal.service;
# ハンドシェイク中にホスト名をバックエンドに送信する
proxy_ssl_server_name on;
proxy_ssl_name api.internal.service;
}
方法3:システムのCAストアを更新する
設定ではなく、OSに問題がある場合もあります。バックエンドが標準的な証明書(Let's Encryptなど)を使用していても、Nginxサーバーが古いOSで動作している場合、ルート証明書が期限切れになっている可能性があります。これは2021年のDST Root CA X3の期限切れで有名になった問題です。
# Ubuntu 22.04 または Debian の場合
sudo apt-get update && sudo apt-get install --only-upgrade ca-certificates
# CentOS 7 または RHEL 8 の場合
sudo yum update ca-certificates
修正の確認
Nginxを再起動してうまくいくことを祈るだけではいけません。ダウンタイムを避けるために、まずは構文を確認してください。
nginx -t
構文が有効であれば、サービスをリロードして、アクティブな接続を切断することなく変更を適用します:
systemctl reload nginx
ブラウザを更新しながら、tail -f /var/log/nginx/error.log を使用してログを監視します。14090086 エラーが消え、アクセスログに成功を示す 200 OK エントリが表示されるはずです。
プロのヒント:ファイルの整合性を確認する
SCPやコピー&ペーストでサーバー間を証明書ファイルを移動する際、ファイルが破損したり途切れたりすることがあります。PEMファイルの文字が1つ欠けているだけで、ハンドシェイクプロセス全体が失敗します。
ハッシュ値を比較して.crtファイルの整合性を確認することをお勧めします。ToolCraftのハッシュジェネレーターのようなツールを使用すると、ブラウザ上でローカルにSHA-256ハッシュを生成できます。ローカルマシンのハッシュがNginxサーバー上のファイルのハッシュと一致すれば、ファイルが完全であることを100%確信できます。これにより、実体のない設定ミスを調査する時間を何時間も節約できます。

