Docker ComposeでNginxリバースプロキシ設定
はじめに:Docker Compose × Nginxでリバースプロキシを構築する理由
現代のWeb開発において、Dockerを用いたコンテナ化はもはや常識となりました。アプリケーション、データベース、キャッシュサーバーなど、それぞれの役割を独立したコンテナとして稼働させることで、開発環境の構築が劇的に簡単になりました。しかし、複数のWebサービスやアプリケーションを1つのサーバー(クラウドインスタンスやVPSなど)で運用しようとした場合、「ポートの重複」や「セキュリティの複雑化」といった新たな課題に直面します。この課題をスマートに解決し、インフラ全体の管理をシンプルにしてくれるのが、Webサーバーである「Nginx(エンジンエックス)」を利用したリバースプロキシの仕組みです。本記事では、Docker Composeを活用してこのリバースプロキシ環境を構築する理由と、その具体的なメリットについて解説します。
なぜリバースプロキシが必要なのか?
リバースプロキシとは、クライアント(Webブラウザなど)と実際のWebサーバー(バックエンドのコンテナ)の間に立つ「優秀な受付係」のような役割を果たすサーバーのことです。一般的なプロキシ(フォワードプロキシ)が社内ネットワークからインターネットへのアクセスを中継するのに対し、リバースプロキシはインターネットからのアクセスを社内の適切なサーバーへ中継します。これをDocker環境に導入することで、以下の3つの大きなメリットが得られます。
1. 1つのサーバー(IPアドレス)での複数サービスの効率的な運用
インターネット上でWebサービスを公開する際、通常は「ポート80番(HTTP)」や「ポート443番(HTTPS)」という決められたドアを使って通信を行います。もしリバースプロキシがなければ、1つのサーバー内で複数のコンテナ(例:ポート8080で動くWordPressと、ポート3000で動くチャットアプリ)を公開する際、ユーザーに http://example.com:8080 のようにポート番号を指定してアクセスしてもらう必要があります。これはユーザーにとって非直感的であり、企業のファイアウォールで特定のポートがブロックされるリスクもあります。
リバースプロキシをフロントに置けば、すべてのアクセスをポート80番または443番で受け付けることができます。Nginxがリクエストされたドメイン名(例:blog.example.com なのか chat.example.com なのか)を瞬時に判断し、内部の適切なコンテナへルーティングしてくれるのです。
2. SSL証明書の一元管理(SSLターミネーション) WebサイトをHTTPS化するには、SSL/TLS証明書を設定する必要があります。コンテナごとに証明書を管理・更新するのは非常に手間がかかります。しかし、リバースプロキシを導入すれば、Nginx側だけでSSLの暗号化・復号を処理できます。バックエンドのアプリケーションコンテナは常に平文のHTTP通信でNginxとやり取りするだけで済むため、開発者はアプリケーションのロジックに集中できます。
3. セキュリティの向上と負荷分散 バックエンドのコンテナを直接インターネットに晒す必要がなくなるため、悪意ある攻撃者からの直接攻撃を防ぐ盾(DMZ構成のような役割)として機能します。さらに、同じアプリケーションを複数のコンテナで起動しておき、Nginxがアクセスを均等に振り分ける(ロードバランサーとしての機能)ことで、トラフィックが集中した際の負荷分散も可能になります。
この記事で実現できること
本記事では、現代のインフラエンジニアやバックエンドエンジニアにとって必須のスキルである「Docker Compose × Nginx」を用いたリバースプロキシの構築手法を、ステップバイステップで徹底解説します。
具体的なゴールは以下の通りです。まず、compose.yaml という1つの設定ファイルを使って、Nginxのコンテナとバックエンドのアプリコンテナ(例えばApacheやNode.jsなど)を連動させます。読者は、Nginxの設定ファイル(default.conf)に数行記述を追加するだけで、特定のURLに来たアクセスを別のコンテナへ転送する proxy_pass の仕組みを完全に理解できるようになります。
さらに、単一のコンテナへのルーティングにとどまらず、パス(/app1 や /app2)やサブドメイン(app1.example.com)を使って、全く異なる複数のコンテナへ同時にアクセスを振り分ける実践的なルーティング設定も扱います。
そして最終的には、本番環境の運用に不可欠な「Let’s Encrypt」を用いた無料のSSL証明書の自動取得・更新の仕組み(HTTPS化)までを構築します。月額数百円の小さなVPSサーバーを借りても、エンタープライズ級の堅牢でスケーラブルなWebシステムを構築できるようになるため、この記事を読み終える頃には、Dockerを用いたモダンなWebインフラの基礎から本番運用に耐えうる知識までをしっかりと身につけることができるでしょう。
リバースプロキシの基礎知識とDocker環境での役割
Docker ComposeでNginxをリバースプロキシとして活用する前に、まずはリバースプロキシの基本的な仕組みと、Docker環境特有のネットワークについてしっかりと理解しておきましょう。これらの基礎知識があることで、後の設定作業で「なぜこのように記述するのか」が明確になり、トラブルシューティングもスムーズに行えるようになります。
リバースプロキシとは?
リバースプロキシとは、クライアント(Webブラウザなど)とWebサーバーの間に位置し、クライアントからのリクエストを適切なバックエンドサーバーへ転送する仕組みです。具体例をイメージしてみてください。
[クライアント(ブラウザ)] → [リバースプロキシ(Nginx)] → [WebサーバーA]
→ [WebサーバーB]
→ [WebサーバーC]
ユーザーが「example.com」にアクセスすると、まずリバースプロキシがそのリクエストを受け付けます。そして、アクセスされたURLのパスやドメイン名に基づいて、背後にある適切なWebサーバーへリクエストを転送します。
一般的な「プロキシ」(フォワードプロキシ)と混同されがちですが、動作する向きが全く異なります。フォワードプロキシは社内ネットワークからインターネットへアクセスする際に中継する「出口」の役割を持ちます。例えば、社内PCからWebサイトを閲覧する際にプロキシサーバーを経由することで、アクセス制限やログの記録を行うケースがこれに該当します。
一方、リバースプロキシはインターネットからのアクセスを社内サーバーへ導き込む「入口」として機能します。この違いを一言で表現すると、フォワードプロキシが「クライアント側の代理人」であるのに対し、リバースプロキシは「サーバー側の代理人」となります。
セキュリティ面でもリバースプロキシは重要な役割を果たします。システム構成において、リバースプロキシをDMZ(非武装地帯)と呼ばれる外部ネットワークに設置し、実際のWebサーバーはより安全な内部ネットワークに配置する設計がよく採用されます。これにより、悪意のある攻撃者が直接Webサーバーにアクセスすることを防ぎ、攻撃の影響をリバースプロキシの層でブロックできます。さらに、SSL/TLSによる暗号化通信の終端(SSLターミネーション)をリバースプロキシで一括処理することで、バックエンドのサーバー群は暗号化の負荷から解放され、本来のアプリケーション処理に専念できるようになります。
Dockerネットワークにおけるコンテナ間通信の基礎
Docker Composeでリバースプロキシを構築する際、最も重要な概念が「コンテナ間通信」です。Docker Composeを使って複数のコンテナを起動すると、デフォルトで専用のブリッジネットワークが作成され、同じネットワーク上のコンテナ同士は自由に通信できるようになります。
このネットワークの最大の特徴は、compose.yamlのservicesに定義した名前がそのままホスト名として解決される点です。例えば、次のような構成があったとします。
services:
nginx:
image: nginx:latest
backend:
image: node:18
この場合、nginxコンテナからbackendコンテナへ通信する際、IPアドレスを調べる必要はありません。http://backend:3000のように、サービス名をそのままホスト名として指定できます。これはDockerの内部DNS機能が自動的に名前解決を行ってくれるためです。コンテナが再起動されてIPアドレスが変わっても、サービス名でアクセスすれば常に正しい宛先に通信できます。
しかし、開発環境などで「Dockerコンテナからホストマシン(自分のPC)で動いているサービスにアクセスしたい」というケースもあるでしょう。例えば、ホスト側で既にポート8081でローカルサーバーを起動しており、Docker上のNginxコンテナからリバースプロキシでそこへ転送したい場合です。このような時はhost.docker.internalという特別なドメイン名を使用します。
# コンテナ内からホストマシンのポート8081へアクセスする例
location / {
proxy_pass http://host.docker.internal:8081;
}
host.docker.internalはDockerが自動的にホストマシンのIPアドレスに解決する特殊なドメインです。これにより、コンテナ内部からホスト側のデータベースやローカル開発サーバーに簡単にアクセスできます。ただし、本番環境のLinuxサーバーではこの機能がデフォルトで無効になっている場合があるため、本番運用時は各コンテナを同じDockerネットワークに配置し、サービス名で通信する設計が推奨されます。
このように、Docker Composeのネットワーク機能を活用することで、複雑なIPアドレス管理から解放され、シンプルで保守性の高いリバースプロキシ環境を構築できます。次章からは、実際に設定ファイルを記述し、Nginxコンテナをリバースプロキシとして動作させる具体的な手順を解説していきます。
【基本編】Docker ComposeでNginxのリバースプロキシを設定する手順
リバースプロキシの仕組みを理解したところで、さっそくDocker Composeを使ってNginxのリバースプロキシを構築してみましょう。ここでは、最もシンプルな「フロントエンドのNginxが、バックエンドのWebアプリコンテナへ通信を転送する」構成を実際に作ります。基本的な設定手順をマスターすれば、後ほど解説する複数コンテナのルーティングやSSL化にもスムーズに応用できます。
プロジェクトのディレクトリ構成
まずは、設定ファイルを整理するためのプロジェクトディレクトリを作成します。初心者の方がつまずきやすいのが「ホスト側(自分のPC側)のどのファイルが、コンテナ側のどのパスに紐づいているのか」という対応関係です。以下のようなツリー構造でファイルを準備してください。
my-reverse-proxy/
├── compose.yaml
├── nginx/
│ └── conf.d/
│ └── default.conf
└── backend/
└── index.html
ホスト側とコンテナ側の対応関係の重要ポイント
| ホスト側のファイルパス | コンテナ側のマウント先 | 役割 |
|---|---|---|
./nginx/conf.d/default.conf | /etc/nginx/conf.d/default.conf | Nginxのリバースプロキシ設定 |
./backend/index.html | (バックエンドコンテナ内のドキュメントルート) | 転送先となるテスト用Webページ |
./compose.yaml | (Dockerエンジンが直接読み込む) | コンテナの起動定義ファイル |
このように、ホスト側のローカルディレクトリに作成した設定ファイルを、コンテナ内部の所定のディレクトリ(例えばNginxの設定ファイルであれば/etc/nginx/conf.d/)にマウント(紐付け)することで、コンテナを再起動することなく設定を反映させることができます。
Nginxの設定ファイル(default.conf)の記述
続いて、リバースプロキシの心臓部となるNginxの設定ファイルを記述します。ホスト側の./nginx/conf.d/default.confに以下の内容を記述してください。
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://backend:80/;
}
# クライアントの情報をバックエンドに正しく渡すための定型設定
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
proxy_passによる転送先の指定
location /ブロックは、「ルートパス(/)以下へのすべてのアクセス」を意味します。その中にあるproxy_pass http://backend:80/;が、リバースプロキシのメイン機能です。ここで指定しているbackendというホスト名は、後ほどcompose.yamlで定義するバックエンド側のコンテナ名(サービス名)です。Dockerネットワーク内では、このサービス名がそのままホスト名として名前解決されるため、IPアドレスを直書きする必要はありません。
proxy_set_headerのおまじない(定型句)の意味
リバースプロキシを経由すると、バックエンドのアプリから見たアクセス元が「クライアントのPC」ではなく「プロキシであるNginxコンテナ」に変わってしまいます。これを防ぐため、以下の4つのヘッダー情報を書き換えてバックエンドに渡します。
Host $host:クライアントが要求した元のホスト名(例:localhost)X-Real-IP $remote_addr:クライアントのリアルIPアドレスX-Forwarded-For $proxy_add_x_forwarded_for:経由したプロキシのIPを含む追加情報X-Forwarded-Proto $scheme:元のリクエストがHTTPかHTTPSかの判別用
これらを設定しておくことで、バックエンドのアプリ側でアクセス解析やリダイレクト処理を正しく行うことができます。
compose.yamlの書き方とコンテナの起動
最後に、コンテナをまとめて管理するためのcompose.yamlを作成します。ホスト側の./compose.yamlに以下の内容を記述してください。
services:
# フロントエンド:リバースプロキシとして動作するNginx
proxy:
image: nginx:1.25-alpine
ports:
- "80:80"
volumes:
- ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- backend
# バックエンド:実際のコンテンツを返すWebサーバー(確認用)
backend:
image: nginx:1.25-alpine
volumes:
- ./backend:/usr/share/nginx/html:ro
ポイント解説
- イメージの指定:Nginxの公式イメージとして
nginx:1.25-alpineを指定しています。alpineはLinuxの軽量ディストリビューションであり、通常のイメージと比較して約20MB程度と非常にコンパクトで、起動速度も速いのが特徴です。 - ポートマッピング(
80:80):「ホスト側のポート80」へのアクセスを「コンテナ側のポート80」へ転送する設定です。 - バインドマウント(
volumes):先ほど作成した設定ファイルをコンテナ内に共有します。末尾の:roは「Read-Only(読み取り専用)」の意味で、コンテナ側から誤ってホストの設定ファイルを書き換えるのを防ぐためのセキュリティ上のベストプラクティスです。 depends_on:Nginxがバックエンドより先に起動して「転送先がない」エラーになるのを防ぐため、起動順序を制御しています。
コンテナの起動と動作確認
ファイルの準備ができたら、テスト用の./backend/index.htmlに簡単なHTML(例:<h1>Hello from Backend!</h1>)を記述して保存します。その後、ターミナルでプロジェクトディレクトリに移動し、以下のコマンドを実行します。
# コンテナをバックグラウンドで起動する
docker compose up -d
コンテナが正常に起動したら、ブラウザでhttp://localhostにアクセスしてみましょう。画面に「Hello from Backend!」と表示されれば、リバースプロキシが正しくバックエンドへ通信を転送できている証拠です。
設定を変更した場合は、docker compose downでコンテナを停止・削除してから、再度docker compose up -dを実行し直すことで変更が反映されます。
【応用編】複数のコンテナ(アプリ)をルーティングする設定方法
基本的なリバースプロキシの構築ができるようになったら、次は「1つのサーバー(1つのIPアドレス)で、複数のWebアプリケーションを同時に運用する」という実践的な構成にステップアップしましょう。開発環境や小規模な本番環境では、リソースを節約するために1つのマシンに複数のサービスを載せるケースがよくあります。
ここでは、Nginxをフロントに立たせ、URLのパスやサブドメインごとに裏側の別々のDockerコンテナへアクセスを振り分ける(ルーティングする)方法を解説します。
パスベースでのルーティング設定
1つ目のアプローチは、同じドメインの中でURLのパス(/app1や/app2など)を使って振り分ける「パスベースルーティング」です。例えば、example.com/app1/にアクセスした場合はAのコンテナへ、example.com/app2/にアクセスした場合はBのコンテナへ転送するように設定します。
Nginxの設定ファイル(default.confなど)には、以下のように複数のlocationブロックを記述します。
location /app1/ {
proxy_pass http://app_1:5001/;
}
location /app2/ {
proxy_pass http://app_2:5002/;
}
このように記述することで、NginxがURLのパスを判定し、それぞれ指定されたバックエンドのコンテナ(app_1やapp_2)へリクエストを転送してくれます。
静的ファイル(CSSやJS)のパスズレという落とし穴
パスベースのルーティングを導入する際、多くの初心者がつまずくのが静的ファイルのパスズレ問題です。多くのWebアプリケーションは、HTML内でCSSや画像を<link href="/style.css">のようにルート(/)からの絶対パスで読み込んでいます。
しかし、パスベースで/app1/にアクセスしている状態でルートパスを参照しようとすると、ブラウザは/style.cssにリクエストを送ってしまいます。結果として、本来/app1/style.cssにあるはずのファイルが見つからず、デザインが崩れる(404エラーになる)という事態が発生します。
この問題を解決するには、アプリケーション側の設定で「ベースパス(Base URL)」を/app1/として認識させる必要があります。Webフレームワークの設定ファイルや環境変数を使って、静的ファイルが相対パスで正しく読み込まれるように設計・調整することが重要です。
サブドメインを使ったバーチャルホスト設定
前述のパスベースルーティングは設定がシンプルですが、アプリ側の調整が必要になることが欠点です。この課題をスマートに解決し、本番環境の運用でもよく使われるのが「サブドメイン」を使ったルーティングです。
app1.example.comとapp2.example.comのように、サブドメイン単位で全く別のコンテナへルーティングします。この機能はNginxの「バーチャルホスト(Server Blocks)」と呼ばれる仕組みで実現します。
設定ではserver_nameディレクティブを使用し、アクセスされたドメイン名に応じて処理を振り分けます。ここでおすすめなのが、設定を1つのファイルにまとめるのではなく、アプリごとに.confファイルを分割する運用方法です。
app1.conf:
server {
listen 80;
server_name app1.example.com;
location / {
proxy_pass http://app_1:5001/;
}
}
app2.conf:
server {
listen 80;
server_name app2.example.com;
location / {
proxy_pass http://app_2:5002/;
}
}
Nginxはデフォルトで、/etc/nginx/conf.d/ディレクトリ配下にある.confという拡張子のファイルをすべて読み込みます。ファイルを分割することで、「管理画面の設定だけを変更したい」「特定のアプリのメンテナンス画面を出したい」といった場合でも、他のアプリの設定を誤って書き換えるリスクがなくなり、運用面での見通しが劇的に良くなります。
Docker Composeでの複数コンテナ連携
最後に、フロントエンド(Nginx)と複数のバックエンド(APIサーバー、Webアプリなど)を1つのcompose.yamlに定義して連携させるネットワーク設計を解説します。複数コンテナを協調させて動かす場合、起動順序とネットワークの分離が重要なポイントになります。
以下は、複数の独自アプリを連携させる実践的なcompose.yamlの構成例です。
services:
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
depends_on:
- app_1
- app_2
app_1:
build: ./app_1 # app_1ディレクトリにある個別のDockerfileをビルド
expose:
- "5001"
app_2:
build: ./app_2 # app_2ディレクトリにある個別のDockerfileをビルド
expose:
- "5002"
起動順序を制御するdepends_onの仕組み
depends_onは、指定したサービスが先に起動してから自身を起動するようDockerに指示する機能です。上記の例では、Nginxはapp_1とapp_2の起動を待ってから立ち上がります。これにより、Nginxが先に起動してしまい「接続先のバックエンドコンテナが見つからない」というエラー(502 Bad Gateway)を防ぐことができます。
セキュアなネットワーク設計とexposeの活用
バックエンドサービスの定義において、ホスト側のポートとのマッピング(例:"5001:5001")を行うportsではなく、exposeを使用している点に注目してください。exposeはコンテナ間の通信ポートのみを開放し、ホストマシンの外部からはアクセスできなくなります。
すべての通信はNginx(ポート80)を経由するようにし、バックエンドのアプリコンテナには直接アクセスさせないことで、システム全体のセキュリティを飛躍的に向上させることができます。また、各アプリのディレクトリに個別のDockerfileを配置してビルドする構成にすることで、将来的に別々のプログラミング言語(PythonやNode.jsなど)で作られたアプリを簡単に統合できる拡張性も生まれます。
【実践編】SSL/TLS対応(HTTPS化)とLet’s Encryptによる自動化
現代のWeb開発において、通信の暗号化(HTTPS化)はユーザーのプライバシー保護やSEO(検索エンジン最適化)の観点から必須の要件となっています。Docker Composeで構築したNginxリバースプロキシにおいても、SSL/TLSに対応させることで、バックエンドで動く複数のアプリケーションを個別に暗号化対応させる手間を省き、エッジ部分で一元的に安全な通信を提供できます。このセクションでは、開発環境向けの簡易的なHTTPS化から、本番環境で推奨される無料証明書の自動化、そしてさらに通信を強固にするためのセキュリティ設定までを解説します。
Nginxコンテナに自己署名証明書を適用する方法
ローカルの開発環境やステージング環境でHTTPSの挙動を確認したい場合、公的な認証局(CA)から正規のSSL証明書を発行するのは時間も手間もかかります。そこで利用されるのが「自己署名証明書(いわゆるオレオレ証明書)」です。自分自身で署名した証明書であるため、ブラウザでアクセスすると「接続がプライベートではありません」といった警告が表示されますが、システム開発のテスト用途としては十分に機能します。
ローカル環境で証明書を作成するには、以下のopensslコマンドを実行します。
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt
このコマンドでは、有効期限365日、2048ビットのRSA暗号鍵を使用して、秘密鍵(server.key)と公開鍵を含む証明書(server.crt)を生成します。作成されたこれらのファイルをDocker Composeのvolumes設定を用いて、Nginxコンテナ内の任意のディレクトリ(例:/etc/nginx/ssl/)にバインドマウントします。
さらに、Nginx公式のDockerイメージには、設定ファイルを動的に生成できるモダンな機能が備わっています。拡張子が.templateのファイル(例:default.conf.template)を用意し、コンテナ内の/etc/nginx/templates/ディレクトリにマウントすると、コンテナ起動時に環境変数が展開された本来の設定ファイルとして自動生成されます。これを利用すれば、証明書のパスやドメイン名を環境変数(.envファイル)で柔軟に切り替えることが可能になり、開発環境と本番環境で同一のテンプレートを使い回すことができます。
Let’s Encryptを用いた本番環境のSSL化
一般のユーザーが利用する本番環境では、ブラウザの警告を出さずにHTTPS通信を行う必要があります。現在、最も標準的に利用されているのが、非営利団体のISRGが運営する無料の認証局「Let’s Encrypt」です。Let’s Encryptの証明書の有効期限は90日と短く設定されていますが、自動更新ツールである「Certbot」を利用することで、手動での更新作業を完全に省略できます。
Docker Compose環境でこれを実現するには、Nginxコンテナに加えて、Certbotコンテナを1つ追加して連携させる構成が一般的です。しかし、ここで「鶏と卵の問題」という大きな課題に直面します。NginxをHTTPS通信で起動するには証明書ファイルが必要ですが、その証明書を発行するにはNginxが立ち上がっていて、外部からのHTTPリクエストに応答できる(ドメインの所有権を証明する)必要があるのです。
この課題を解決するために、init-letsencrypt.shのような初回セットアップ用スクリプトを利用する手法が、2025年現在でも推奨される確実なアプローチとして広く知られています。このスクリプトは、初回起動時のみ一時的なダミー証明書を自己生成してNginxを起動し、その後にCertbotコンテナを動かして正規の証明書を取得します。取得完了後、ダミー証明書を正規のものに置き換えてNginxをリロードする一連の作業を完全に自動化してくれます。一度この構成を構築してしまえば、Certbotコンテナが自動的に期限の近い証明書を検知して更新してくれるため、人為的ミスのない恒久的なHTTPS運用が可能になります。
セキュリティを強化するための追加設定
単にHTTPSのポート(443)を開放し、SSL証明書を適用しただけでは、古い暗号化方式への攻撃に対して脆弱なままです。Nginxの設定ファイルに数行のルールを追記することで、Webサイトの安全性を飛躍的に向上させることができます。
まず、使用するSSLプロトコルのバージョンを厳密に制限します。過去のプロトコルであるTLSv1.0やTLSv1.1には既知の脆弱性が存在するため、これらを明示的に排除し、安全なTLSv1.2以上のみに限定します。
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
また、ssl_ciphersディレクティブで安全性の高い暗号スイートのみを許可し、解読されやすい古い暗号方式(DESや3DESなど)を無効化します。
次に、HTTPへのアクセスをHTTPSへ強制的に切り替える設定です。ユーザーが誤って「http://」からアクセスした際、自動的に暗号化された「https://」へ301リダイレクト(恒久的な転送)させることで、平文での通信による盗聴リスクを防ぎます。これは、SEOの観点からもHTTPとHTTPSの重複コンテンツを防ぐために非常に重要な設定です。
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
さらに、セキュリティヘッダーの追加も効果的です。特にStrict-Transport-Security(HSTS)ヘッダーをレスポンスに含めることで、一度でもアクセスしたユーザーのブラウザに対して「今後このドメインへのアクセスは常にHTTPSを使用せよ」と強く指示することができます。これにより、ユーザーが偽サイトに誘導される中間者攻撃(MITM)のリスクを大幅に軽減できます。これらの設定を適用することで、本番環境にふさわしい強固なリバースプロキシ構成が完成します。
まとめ:Docker × NginxでスケーラブルなWeb環境を作ろう
ここまで、Docker Composeを使ってNginxをリバースプロキシとして構築する方法を、基礎から応用、さらにはHTTPS化まで幅広く解説してきました。最後に、今回の構成がWebアプリケーションの運用においてどれほど強力な武器になるのかを振り返り、実際の運用で直面しやすいトラブルの解決策と今後の学習ステップについてまとめます。
リバースプロキシ構築のメリットの振り返り
NginxをリバースプロキシとしてDocker上で稼働させる最大のメリットは、「複数サービスの集約」「SSL証明書の一元管理」「セキュリティの向上」という3つの柱を同時に実現できる点にあります。
例えば、1つのクラウドサーバー(グローバルIPアドレス)上で、WordPressによるコーポレートサイト、Node.jsで作られたAPIサーバー、Pythonの機械学習ダッシュボードなどを同時に動かしたいケースを考えてみましょう。リバースプロキシがない環境では、それぞれのサービスにアクセスさせるためにポート番号(例:8080番、3000番、5000番など)を分けて公開する必要があり、URLが http://example.com:3000 のように不格好になる上、ファイアウォールのポート開放設定も煩雑になります。
しかし、Nginxをリバースプロキシとしてフロントに置くことで、すべてのトラフィックをHTTP(80番)およびHTTPS(443番)ポートで一元的に受け付けることができます。ドメイン名(例:blog.example.comやapi.example.com)やパス(例:/dashboard/)に応じて、裏側(バックエンド)で稼働する適切なコンテナへ自動的に振り分けることが可能です。
さらに、SSL/TLS証明書による暗号化通信(HTTPS化)もNginxコンテナだけで完結します。各バックエンドのコンテナ一つひとつにSSL設定を施す必要がないため、管理の手間が劇的に軽減されます。また、外部からの直接のアクセスはNginxコンテナのみに限定し、バックエンドのアプリケーションは外部から見えないDockerの内部ネットワークに隠蔽できるため、システム全体のセキュリティを格段に向上させることができます。
このようなインフラ構成をDocker Composeを用いて「コード化(IaC:Infrastructure as Code)」しておくことで、開発環境と本番環境の差異をなくすことができます。不慮のサーバー障害が発生した際でも、compose.yaml と設定ファイルさえあれば、新しいサーバー上で docker compose up -d コマンド一つを叩くだけで、全く同じ環境を数分で完全に再現可能です。この再現性と保守性の高さこそが、モダンなインフラ構築における最大の強みと言えます。
トラブルシューティングとさらなる学習のすすめ
実際に自分で手を動かして環境を構築しようとした際、設定ファイルの書き方が少し違うだけでエラーに直面することがあります。ここでは、初心者が特に陥りやすい「よくあるつまずきポイント」とその解決策を2つ紹介します。
1. 502 Bad Gatewayエラーが出る場合
ブラウザに「502 Bad Gateway」と表示される場合、Nginx自体は正常に動いているものの、指定されたバックエンドのコンテナと通信できていないことが原因として最も考えられます。これは、コンテナ間のネットワーク設定に問題があるサインです。
Docker Composeで複数のservicesを定義している場合、デフォルトでは同じネットワークに属しますが、別々のcompose.yamlでコンテナを立ち上げている場合は、お互いが見えない別々のネットワークに隔離されてしまいます。この問題を解決するには、外部ネットワークを明示的に作成し、Nginx側とバックエンド側の両方のcompose.yamlでその同じネットワークを共有するよう定義する必要があります。また、単純にバックエンドコンテナが起動していなかったり、proxy_passに書かれたコンテナ名(ホスト名)にタイプミスがあったりするケースも多いため、docker compose psコマンドでコンテナが稼働しているかを確認することも大切です。
2. 404 Not Foundエラーや画面の表示崩れが起きる場合
トップページは表示されるのに、それ以下のページで404エラーが出たり、CSSや画像がうまく読み込まれなかったりする場合は、パス解決のミスが疑われます。特に、Nginxの設定ファイル(default.confなど)における proxy_pass の末尾の「スラッシュ(/)」の有無は非常に厄介な落とし穴です。
例えば、location /app1/ に対して proxy_pass http://backend; と末尾なしで設定すると、http://example.com/app1/index.html へのアクセスが、そのまま http://backend/app1/index.html に転送されてしまい、バックエンド側に /app1/ というルーティングが存在しなければ404エラーになります。正しくバックエンドのルート(/)に転送するには、proxy_pass http://backend/; のように末尾にスラッシュを付ける必要があります。この仕様を理解し、バックエンドのアプリケーション側が期待するパス構造とNginxの転送ルールをぴったり合わせることが重要です。
次のステップへ向けて 基本的なリバースプロキシの構築ができるようになったら、次はよりスケーラビリティ(拡張性)の高いシステムへとステップアップしてみましょう。例えば、1つのバックエンドコンテナでは処理しきれない大量のアクセスが予想される場合、同じアプリケーションのコンテナを複数起動し、Nginxの機能を使ってロードバランサー(負荷分散装置)として振る舞わせることで、システム全体の耐障害性とパフォーマンスを向上させることができます。また、Nginxのキャッシュ機能を有効にして、バックエンドの処理を省略しつつレスポンスを高速化する技術も実用的です。
DockerとNginxの組み合わせは、小規模な個人開発から大規模なエンタープライズシステムまで、あらゆる場面で通用する強力なアーキテクチャです。今回構築した環境をベースに、ぜひ様々な機能を追加・拡張し、自分なりの最強なWeb開発環境を築き上げてみてください。