問題点
単一のDNSエントリが原因でウェブサーバーが起動を拒否することほど、イライラすることはありません。Nginxをリロードした際に、以下のようなブロッキングエラーが表示されることがあります。
[error] 1234#0: host not found in upstream "backend.example.com" in /etc/nginx/conf.d/default.conf:15
デフォルトでは、Nginxの動作は非常に厳格です。起動時に、upstreamブロックやproxy_passディレクティブに含まれるすべてのドメイン名を解決しようとします。DNSサーバーが遅い、バックエンドコンテナの起動が完了していない、あるいはドメインがまだ存在しないといった場合、Nginxはパニックを起こし、起動に失敗します。これにより、多くの自動化セットアップで「鶏が先か、卵が先か」という問題が発生します。
要約:クイックフィックス
バックエンドが到達不能な場合にNginxがクラッシュするのを防ぐには、バックエンドのURLを変数に格納し、resolverを指定します。これにより、Nginxはリクエストが実際に届いた時にのみIPアドレスを検索するようになります。
location / {
resolver 8.8.8.8 valid=30s;
set $backend_servers "http://backend.example.com";
proxy_pass $backend_servers;
}
なぜNginxはバックエンドホストの解決に失敗するのか
Nginxは通常、設定の読み込み時に一度だけDNSルックアップを行います。このルックアップに失敗すると、Nginxはそれを致命的な構文エラーとして扱います。この動作は、いくつかのシナリオで大きな悩みの種となります。
- **Docker Composeのレースコンディション:**Nginxが先に起動してしまい、内部のDockerネットワークが`backend`コンテナを完全に登録する前に、そのコンテナを見つけようとすることがよくあります。
- **動的スケーリング:**バックエンドのIPが頻繁に変わる場合(AWS Auto ScalingやKubernetesで一般的)、サービスを手動でリロードするまで、Nginxはキャッシュされた古いIPにトラフィックを送信し続けます。
- **ネットワークの瞬断:**自動デプロイ中に2秒間のDNS不具合が発生するだけで、本番サイト全体がオフラインになる可能性があります。
ステップバイステップの解決策
方法1:変数とリゾルバの使用(推奨)
proxy_passディレクティブで変数を使用すると、Nginxの動作が変わります。起動時のチェックをスキップし、ユーザーがリクエストを送信するまでDNSルックアップを待機します。
**1. リゾルバの定義:**どのDNSサーバーに問い合わせるかをNginxに伝える必要があります。Docker環境の場合は、127.0.0.11にある内部DNSを使用します。公開サーバーの場合は、Google(8.8.8.8)やCloudflare(1.1.1.1)が標準的な選択肢です。
2. ホストを変数にマッピングする:setディレクティブを使用して、バックエンドのURLを保持します。
server {
listen 80;
server_name proxy.example.com;
# 10秒のキャッシュでDockerの内部DNSを使用する
resolver 127.0.0.11 valid=10s;
location / {
# URLを変数に格納することで動的な解決がトリガーされる
set $upstream_endpoint http://backend.example.com:8080;
proxy_pass $upstream_endpoint;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
方法2:/etc/hosts ファイルへのハードコーディング
バックエンドが変更されることのない静的IPアドレスを使用している場合は、DNSを完全にバイパスできます。ローカルのhostsファイルでホスト名をマッピングすることは、小規模なセットアップにおいて信頼できる「ローテクな」解決策です。
- 設定を開く:`sudo nano /etc/hosts`
- マッピングを追加する:`10.0.0.50 backend.example.com`
- 構文を確認する:`sudo nginx -t`
警告:DockerやKubernetesではこれを避けてください。コンテナのIPは、ポッドやコンテナが再起動するたびに変更されます。
方法3:Dockerの起動ロジックの調整
Docker環境では、docker-compose.ymlのdepends_onフラグによってコンテナが順番に起動するように設定できます。しかし、コンテナ内のアプリケーションが「準備完了」になるまでは待機しません。それでもNginxが失敗する場合は、depends_onと方法1の変数のテクニックを組み合わせて、バックエンドの状態に関わらずNginxが起動するようにします。
検証:修正の確認
設定を更新したら、Nginxが脆弱でなくなったことを確認します。
1. 設定をテストする:
sudo nginx -t
方法1を使用した場合、バックエンドサーバーの電源が完全に切れていても、このコマンドは test is successful と報告するようになります。
2. ライブログを監視する:
curlでリクエストを送信しながら、ログを監視します。
sudo tail -f /var/log/nginx/error.log
バックエンドが停止している場合、ブラウザには 502 Bad Gateway が表示されますが、Nginx自体は実行され続けます。これは、サービス全体が起動に失敗するよりもはるかに良い結果です。
3. ランタイム更新を検証する:
valid=5s のように短いTTLを設定します。DNSプロバイダー側でバックエンドのIPを変更します。Nginxを再起動せずに5秒待ってから、ログを確認してください。Nginxは自動的に新しい宛先を認識するはずです。
よくある落とし穴
- **「No Resolver」エラー:**`proxy_pass`で変数を使用しながら`resolver`行を省略すると、Nginxはドメインを検索する方法がないというエラーをスローします。
- **ポート53のブロック:**ファイアウォールがポート53(UDPおよびTCP)の送信トラフィックを許可していることを確認してください。Nginxがリゾルバに到達できない場合、すべてのリクエストがタイムアウトになります。
- **URIのフォーマット:**変数を使用する場合、Nginxは末尾のスラッシュの扱いが異なります。プロキシロジックが複雑な場合は、`/api/v1` が誤って `/v1` にならないよう、URLパスを慎重にテストしてください。

