はじめに

  • この記事は、自分がDocker Composeを用いてNGINXを勉強した結果をまとめたものです。
  • Dockerコンテナで以下の機能を試しながらNGINXを勉強していきます。
    • HTTPサーバを起動する
    • NGINXをリバースプロキシとして動作させる
    • HTTPSを有効化させる
  • 記事の内容は、NGINX初学者の方向けになっており、Dockerについての解説は省略しています。

前提

  • Docker及びDocker Composeがインストールされていること。
  • 検証時に使用した環境は以下の通り。
$ docker -v
Docker version 27.0.3, build 7d4bcd8
$ docker compose version
Docker Compose version v2.28.1

HTTPサーバを起動する

構成

以下のファイル、ディレクトリを用いてNginxコンテナを作成します。

$ tree $(pwd)
/nginx_study
├── compose.yml
├── conf.d
│   └── default.conf
├── html
│   └── index.html
└── nginx.conf
compose.yml
services:
  nginx:
    image: nginx:1.27.0
    ports: 
      - "8080:80"
    volumes: 
      - ./html:/usr/share/nginx/html
      - ./conf.d:/etc/nginx/conf.d
index.html
<!DOCTYPE html>
<html>
<head>
  <title>Welcome to Nginx!</title>
</head>
<body>
  <h1>Success! The Nginx server is working!</h1>
</body>
</html>
nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

}
default.conf
server {
    listen 80;
    server_name localhost;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }

}

NGINXの設定ファイルの基礎知識

  • NGINXのデフォルト設定ファイルは、nginx.confである。
    • nginx.confは/usr/local/nginx/conf、/etc/nginx、または/usr/local/etc/nginxに格納する。
  • 今回用意したdefault.confは、バーチャルサーバの詳細設定ファイルとしてnginx.confとは別で用意した。
  • default.confは、nginx.conf内のincludeディレクティブ(後述)でNGINXの起動時に読み込む設定としている。

参考: Beginner’s Guide https://nginx.org/en/docs/beginners_guide.html

ディレクティブとコンテキストについて

各設定は「ディレクティブ」と「コンテキスト」を用いて記述する。

  • ディレクティブ

    • ディレクティブは「シンプルディレクティブ」と「ブロックディレクティブ」に分かれる
    ディレクティブの種類 記載方法
    シンプルディレクティブ スペースで区切られた名前とパラメータで構成され、セミコロンで終わるように記載する(例:listen 80;)
    ブロックディレクティブ 「{}」で囲う構文で、複数のシンプルディレクティブを記載した一連の処理を記載する(例:events、http、server、location)
  • コンテキスト

    • ブロックディレクティブの{}で囲われた範囲を「コンテキスト」と呼ぶ
    • コンテキスト外({}で囲われていない場所)に配置されたディレクティブは、「mainコンテキスト」内に存在しているとみなされる(例:user nginx;やhttpディレクティブ等)

ディレクティブとコンテキストの例

# mainコンテキストに記載されたuserディレクティブ
user nginx;

# mainコンテキストに記載されたeventsブロックディレクティブ
events {
    # worker_connectionsディレクティブはeventsコンテキストに属する
    worker_connections 1024;
}

参考: Beginner’s Guide Configuration File’s Structure https://nginx.org/en/docs/beginners_guide.html#conf_structure

用意したnginx.confの意味

メインコンテキスト

nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
  • user

    • プロセスを起動するユーザまたはグループを指定する
  • worker_processes

    • ワーカープロセスの起動数を定義する
    • autoが選択されると、使用可能なCPUコア数を自動で設定してくれる
  • error_log

    • NGINXエラー時のログファイルの出力先とログレベルを定義する
  • pid

    • マスタープロセスのプロセスIDを格納するファイルを指定する

eventsコンテキスト

nginx.conf
events {
    worker_connections 1024;
}
  • worker_connections
    • 1つのワーカープロセスによって開くことができる同時接続の最大数

httpコンテキスト

nginx.conf
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

}
  • include
    • 別のファイルまたは指定されたマスクに一致するファイルを読み込ませる
    • 今回の設定では、/etc/nginx/mime.typesと/etc/nginx/conf.d配下のdefault.confがnginxの設定ファイルとして読み込まれる
    • mime.typesは以下のような形で、MIMEタイプと拡張子のマッピングが記載されている
mime.types
types {
    text/html html htm shtml;
    text/css css;
    text/xml xml;
}
  • default_type
    • HTTPレスポンスのデフォルトMIMEタイプを設定
    • 今回の設定では、includeディレクティブで指定した/etc/nginx/mime.typesにマッチしない場合、こちらで指定したapplication/octet-streamが設定される

serverディレクティブ/コンテキスト

default.conf
server {
    listen 80;
    server_name localhost;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }

}
  • server

    • 個々のバーチャルサーバの設定を定義
    • serverディレクティブはhttpコンテキスト内に記載する
    • httpコンテキスト内の「include /etc/nginx/conf.d/*.conf;」によってdefault.confが読み込まれるため、default.confで記載されているserverディレクティブがnginx.confのhttpコンテキスト内に包含されている形となる
  • listen

    • リクエストを受け入れるポートを指定
  • server_name

    • バーチャルサーバの名前を指定
    • リクエストに対してどのserverブロックが使用されるかを決定するもの

locationディレクティブ/コンテキスト

  • location

    • リクエストURIに応じた設定を記載する
  • root

    • リクエストのルートディレクトリを指定
  • index

    • HTTPリクエストが「/」で終わっている場合にクライアントに応答するファイルを指定

コンテナの起動

$ docker compose up -d

動作確認

ブラウザでhttp://localhost:8080でアクセスし、サイトが表示されたらOK。 http_server

ここまでのまとめ

  • NGINXの設定はnginx.confやincludeディレクティブで指定したファイルに記述する
  • nginx.confはディレクティブとコンテキストで構成される

NGINXをリバースプロキシとして動作させる

今度はNGINXをリバースプロキシとして動かしてみます。

バックエンドはFlaskとします。

構成

$ tree $(pwd)
/nginx_study
├── app  #新規作成
│   ├── Dockerfile  #新規作成
│   └── app.py  #新規作成
├── compose.yml  #変更
├── conf.d
│   └── default.conf  #変更
├── html
│   └── index.html
└── nginx.conf
Dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY ./app/app.py /app

RUN pip install flask

CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]
app.py
from flask import Flask

app = Flask(**name**)

@app.route("/")
def hello():
    return "Hello from Flask!"

if __name__ == "__main__":
    app.run(host="0.0.0.0/0", port=5000)
compose.yml
services:
  nginx:
    image: nginx:1.27.0
    ports: 
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
      - ./conf.d:/etc/nginx/conf.d

# 以下を追記

  flask:
    build:
      context: .
      dockerfile: ./app/Dockerfile
    ports:
      - "5000:5000"

Flaskアプリケーションはhttp://localhost:5000でアクセスがあった際に、「Hello from Flask!」と応答する簡単なものを用意しました。

NGINXの設定

default.confを以下の内容へ変更します。

default.conf
# upstreamコンテキストを追加

upstream backend {
    server flask:5000;
}

server {
    listen 80;
    server_name localhost;

    # locationコンテキストを丸々変更
    location / {
        proxy_pass http://backend;
        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;
    }

}

upstreamディレクティブ/コンテキスト

  • upstream

    • NGINXをリバースプロキシとしてバックエンドサーバ等と組み合わせた、ひとまとまりのサーバとして使用したい場合に記載する
    • 「upstream backend」のbackendとは、upstreamの名前
  • server

    • リクエスト送信先となる個々のサーバを定義

上記をまとめると、NGINX(リバースプロキシ)と1台のサーバからなるbackendという名前のupstreamグループを定義している。

locationコンテキスト

  • proxy_pass

    • プロキシ先となるプロトコル、アドレス、URIを指定する
    • 今回の設定では、NGINXへの全てのリクエストをbackend upstreamグループで定義したサーバへ送信する設定となる
  • proxy_set_header

    • プロキシ先のサーバへアクセスする際に付与するリクエストヘッダを定義
  • proxy_set_header Host $host;

    • クライアントのHostヘッダの情報を保持する設定
    • $hostは元のリクエストのホスト名を保持する変数で、リクエスト時にHostヘッダが存在しない場合は、リクエストを処理したserver_nameディレクティブに記載されたサーバ名が選択される
  • proxy_set_header X-Real-IP $remote_addr;

    • クライアントのIPアドレスを保持する設定
    • $remote_addrはクライアントのIPアドレスを保持する変数
  • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    • クライアントのIPアドレスを保持する設定
    • X-Real-IPとの違いは、X-Real-IPがクライアントのIPアドレスのみで、X-Forwarded-ForはプロキシサーバのIPアドレスも保持する
    • $proxy_add_x_forwarded_forは、元のクライアントIPアドレスとプロキシサーバーのアドレスを含む変数
  • proxy_set_header X-Forwarded-Proto $scheme;

    • クライアントのリクエストプロトコル(HTTPかHTTPS)を保持する設定
    • $scheme は、リクエストのプロトコルを保持する変数

コンテナの起動

$ docker-compose down
$ docker-compose up -d --build

動作確認

ブラウザでhttp://localhost:8080でアクセスし、「Hello from Flask!」と表示されたらOK。 reverse_proxy

ここまでのまとめ

  • リバースプロキシを使用する際は、upstreamディレクティブを使用する
  • upstreamブロック内にバックエンドサーバを定義する
  • 転送先のバックエンドサーバは、locationコンテキスト内のproxy_passで指定する
  • プロキシリクエストに特定のHTTPヘッダを設定する場合は、locationコンテキスト内のproxy_set_headerを定義する

HTTPSを有効化させる

続いて、リバースプロキシの機能に加えてHTTPSを有効化してみます。

合わせて、http://localhost:8080でアクセスした際に、https://localhost:8080へリダイレクトする設定も追加します。

構成

/nginx_study
├── app
│   ├── Dockerfile
│   └── app.py
├── compose.yml #変更
├── conf.d
│   └── default.conf #変更
├── html
│   └── index.html
├── nginx.conf
└── ssl #新規作成
    ├── nginx.crt #新規作成
    └── nginx.key #新規作成
compose.yml
services:
  nginx:
    image: nginx:1.27.0
    ports: 
      - "8080:80" 
      - "443:443" #追記
    volumes: 
      - ./html:/usr/share/nginx/html
      - ./conf.d:/etc/nginx/conf.d 
      - ./ssl:/etc/nginx/ssl #追記
  
  flask:
    build:
      context: .
      dockerfile: ./app/Dockerfile
    ports: 
      - "5000:5000"

自己証明書の作成

以下の手順で自己証明書を作成します。

Common Name (CN)にはlocalhostを指定し、それ以外の項目は空欄とします。

$ mkdir /nginx_study/ssl ; cd $_
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nginx.key -out nginx.crt
<省略>
Common Name (e.g. server FQDN or YOUR name) []:localhost ★localhostと入力 他は空欄でOK

NGINXの設定

default.confを以下の内容に変更します。

default.conf
# upstreamコンテキストを追加

upstream backend {
    server flask:5000;
}

# 80番ポートのlocationを以下の通りに変更

server {
    listen 80;
    server_name localhost;

    location / {
        return 301 https://$host$request_uri;
    }

}

# 443番ポートの設定を追加

server {
    listen 443 ssl;
    server_name localhost;

    ssl_certificate /etc/nginx/ssl/nginx.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://backend;
        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;
    }

}

  • return 301 https://$host$request_uri;

    • 指定されたステータスコードと共にレスポンスをクライアントに返す
    • 今回はステータスコード301(Moved Parmanently)で、リクエストされたリソースが恒久的に新しいURLへ移動したことを示す
    • https://$host$request_uriの$hostはクライアントがリクエストしたホスト名、$request_uriはリクエストURIを保持する変数
  • listen 443 ssl;

    • NGINXが443ポートをリッスンする設定
    • sslの部分で、SSL/TLSを使用することを示す
  • ssl_certificate /etc/nginx/ssl/nginx.crt;

    • サーバーのSSL証明書のパスを指定
  • ssl_certificate_key /etc/nginx/ssl/nginx.key;

    • サーバーのSSL秘密鍵のパスを指定
  • ssl_protocols TLSv1.2 TLSv1.3;

    • サポートするSSL/TLSプロトコルを指定
    • NGINXのVersion 1.23.4以降のデフォルト値はTLSv1 TLSv1.1 TLSv1.2 TLSv1.3が有効になっているため、今回はTLSv1とTLSv1.1は除外している
  • ssl_ciphers HIGH:!aNULL:!MD5;

    • サポートする暗号スイートを指定
    • HIGHは鍵の長さが128ビット以上の高度な暗号スイート
    • aNULLは暗号化を行わない暗号スイートで、!マークがつくことで、この暗号スイートは利用しないことを示す
    • MD5はMD5を利用する暗号スイートで、!マークがつくことで、この暗号スイートは利用しないことを示す
    • NGINXのVersion 1.0.5以降のデフォルト値がHIGH:!aNULL:!MD5となっているため、明示的に記載する必要はないが今回は学習のため記載

起動

$ docker compose down
$ docker-compose up -d

動作確認

http://localhost:8080へアクセスし、https://localhostへリダイレクトされた上で、「Hello from Flask!」と表示されたらOK https

ここまでのまとめ

  • HTTPからHTTPSへのリダイレクトはreturnディレクティブを使用する
  • HTTPSを使用する際は、listenディレクティブにSSL/TLSを利用する旨を記載する
  • ssl_certificateディレクティブ、ssl_certificate_keyディレクティブでサーバ証明書と秘密鍵を指定する
  • ssl_protocolsディレクティブ、ssl_ciphersディレクティブで使用するSSL/TLS設定を指定する

参考にさせていただいたサイト

https://nginx.org/en/docs https://qiita.com/Anorlondo448/items/c0234b2a5bf51578c974 https://zenn.dev/na0kia/articles/c68cfd65dcbdda https://docs.openssl.org/1.1.1/man1/ciphers/#cipher-strings