NGINXをNGINX Unitのリバースプロキシとして動かす話

前置き

2024/1/1現在、このブログはAWSのLightsailで運用されておりOSはAmazonLinux2を使用しています。
AmazonLinux2のEoLは本来ならもう既に過ぎてしまっていますが、2025/6/30に延長されています。

そこでその時が来てから慌てないようにマイグレーションの検討を始めたわけですが、そこで下記の記事を読んだのを思い出しました。

やや眉唾物のような気もしますが、本当にPHPの実行環境をPHP-FPMからNGINX Unitに切り替えると約8倍も速くなるというなら検討の価値はあります。
というわけで、まずはdockerでNGINX Unitを動かす環境作りを始めることにしたわけです。

NGINXをWebサーバー兼リバースプロキシとして使う

もちろんNGINX Unit単体でも運用できるようですが、公式ドキュメントを見るとNGINXをリバースプロキシとして使うことも想定されているようです。

WordPressを動かす場合はPHPファイルに対するアクセスはNGINX Unitに転送し、それ以外のファイル(画像、css、jsなど)はNGINX側で処理する形になるかと思います。次のイメージですね。 リバースプロキシのイメージ

早速お試し

と言うわけでdockerで実験用の環境を作成し、サンプルのページを表示してみました。そのときのファイルはGithubにあげてあります。

内容の解説ですが、まず下記のようなファイル構成になっています。
./cert配下に証明書が置いてありますが、実際に自分で使っている証明書を置くわけにもいかないのでダミーとして空ファイルが置いてあります。

.
├── cert
│   ├── bundle.pem (空ファイル)
│   ├── dhparam.pem (空ファイル)
│   ├── hoge.crt (空ファイル)
│   └── hoge.key (空ファイル)
├── nginx
│   ├── common
│   │   └── subdomain_ssl.conf
│   ├── conf.d
│   │   ├── http.conf
│   │   └── https.conf
│   └── html
│       ├── index.php
│       └── newyear2024.jpg
├── unit
│   ├── http.json
│   └── https.json
├── docker-compose-http.yaml
└── docker-compose-https.yaml

docker-compose-http.yamlとdocker-compose-https.yamlという二つのYAMLファイルがありますが、これはNGINXとNGINX Unit間の転送をhttpで行うか、httpsで行うかの違いです。

転送をhttpで行う場合は

$> docker compose -f docker-compose-http.yaml up

また、転送をhttpsで行う場合は

$> docker compose -f docker-compose-https.yaml up

で起動できます。この二つの違いの意味については後述します。

dockerの設定ファイル

まずはdockerのYAMLファイルが2つありますが差分は多くはありません。下記にdiff -uの結果を出します。

--- docker-compose-http.yaml    2024-01-01 14:24:03.796812900 +0900
+++ docker-compose-https.yaml   2024-01-01 14:24:14.861093000 +0900
@@ -1,19 +1,20 @@
 services:
-  nginx-http:
+  nginx-https:
     image: "nginx"
     ports:
       - "80:80"
       - "443:443"
     volumes:
-      - ./nginx/conf.d/http.conf:/etc/nginx/conf.d/http.conf
+      - ./nginx/conf.d/https.conf:/etc/nginx/conf.d/https.conf
       - ./nginx/common:/etc/nginx/common
       - ./nginx/html:/usr/share/nginx
       - ./cert/hoge.crt:/etc/certificates/hoge.crt
       - ./cert/hoge.key:/etc/certificates/hoge.key
       - ./cert/dhparam.pem:/etc/certificates/dhparam.pem
-  unit-http:
+  unit-https:
     image: "unit:php"
     volumes:
-      - ./unit/http.json:/docker-entrypoint.d/config.json
+      - ./unit/https.json:/docker-entrypoint.d/config.json
+      - ./cert/bundle.pem:/docker-entrypoint.d/bundle.pem
       - ./nginx/html:/usr/share/nginx

上記をみてわかる通り差分はサービス名をhttpとhttpsで変えていることと、NGINXのconfファイルとNGINX Unitのjsonファイルはそれぞれhttp用とhttps用の2ファイルを用意していてどちらかが使われるようになっているという違いがあります。
またhttps側だけbundle.pemというファイルが増えていますが、これについてはNGINX Unitのところで説明します。

それほど変わったことはしていないですが、一点ポイントなのはportsで外部に公開設定しているのはNGINXのHTTP(80)とHTTPS(443)だけで、NGINX Unitは外部に公開設定していません。
NGINX UnitはNGINXからの転送先として存在しているので内部からアクセスできていれば良く、外部に公開する必要性がないというかむしろ余計なポートは開かないようにしておくべきという考えからです。

NGINXの設定ファイル

次はNGINX設定ファイルの説明です。http.confとhttps.confは内容はほぼほぼ一緒のため、http.confだけ説明をします。

  • ./nginx/conf.d/http.conf
upstream unit_backend {
    server unit-http:8080;
}

server {
  listen 80;
  server_name hoge;

  return 301 https://hoge$request_uri; 
}

server {
  listen 443 ssl;
  http2 on;
  server_name hoge;

  root /usr/share/nginx/;
  index index.php;

  include common/subdomain_ssl.conf;

  location ~ [^/]\.php(/|$) {
    # Proxy headers
    proxy_set_header Upgrade           $http_upgrade;
    proxy_set_header Connection        "upgrade";
    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_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;

    # Proxy timeouts
    proxy_connect_timeout              60s;
    proxy_send_timeout                 60s;
    proxy_read_timeout                 60s;

    rewrite [^/]\.php(/|$)(.*)$ /$1    break;
    proxy_pass http://unit_backend;
    proxy_redirect  default;
    proxy_cache_bypass                 $http_upgrade;
  }

  access_log /var/log/nginx/hoge.access.log;
  error_log /var/log/nginx/hoge.error.log;
}

1~3行目でupstreamの設定と39行目で転送先の設定をしています。このときのサーバー名の設定はNGINX Unitのサービス名 unit-httpになります。これはdocker composeのネットワーク機能によってサービス名で該当のコンテナにアクセスできるようにしてくれているからです。
7、9、15行目にホスト名が出てくる箇所がありますが、ダミーで hoge にしてあります、適宜差し替えてください。

なお、http.confとhttps.confの差分は39行目の転送をhttpでやるかhttpsでやるかの違いだけです。

また、20行目で別ファイルをincludeしているのでそれについても触れます。

  • ./nginx/common/subdomain_ssl.conf
ssl_certificate     /etc/certificates/hoge.crt;
ssl_certificate_key /etc/certificates/hoge.key;

ssl_dhparam /etc/certificates/dhparam.pem;
ssl_ciphers ECDHE+AESGCM:DHE+AESGCM:HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2;

add_header Strict-Transport-Security "max-age=15768000; includeSubdomains";

内容はSSLの設定ですが、元々が複数のバーチャルホストで運用することを想定しているため証明書の設定などは別ファイルに切り出しているだけです。設定自体はそれなりにセキュリティ面に気を遣っているとは思いますが、特に変わった設定はないと思います。
また、先にも触れていますが証明書はダミーの空ファイルになっているのでご自分でお試しになる場合は適宜自分で持っている証明書に置き換えてください。

 

NGINX Unitの設定ファイル

一番困ったのがNGINX Unitの設定ファイルです。そもそもNGINX Unitは起動時に設定ファイルなどの読み込みは行わず空っぽの状態で起動し、設定はAPIを叩くことで投入するという変わった仕組みだからです。
これは設定ファイルの編集をミスってしまってその後サービスが起動しなくなってしまうのを避ける(APIなら叩かれた時点で内容チェックして不正なら却下できる)とかいくつか理由があるそうですが、dockerで運用する場合、起動時に毎回毎回APIを叩くというのはなかなか難儀します。
そこでdockerコンテナの場合は /docker-entrypoint.d にjsonファイルを置いておくと起動時にAPIを呼び出して自動で投入してくれるようになっているそうです。

なのでdockerのYAMLでhttp.json、またはhttps.jsonを /docker-entrypoint.dに配置されるようにして自動でAPIに投入してもらいます。

  • ./unit/http.json
{
  "listeners": {
      "*:8080": {
          "pass": "applications/php"
      }
  },
  "applications": {
      "php": {
          "type": "php",
          "root": "/usr/share/nginx/"
      }
  }
}

NGINX Unitの設定はいたってシンプルです。
2~6行目がリスナーの定義でポート8080番にアクセスされた場合はphpのアプリケーションとして扱うとしています。もちろん先のNGINXでのupstreamとして指定しているポート番号はここと一致する必要があります。
7~12行目がアプリケーションの設定ですが、何のアプリケーションが動くかというtypeの指定が必須です。ここではもちろんPHPを指定しています。
そしてPHPの場合はrootの指定も必須になります。これはNGINXと同じでドキュメントルートを指定します。通常NGINXの設定と一致することになるでしょう。(このサンプルでもhttp.confの17行目と一致していますね)
なんと最低限であればこれだけで動いてしまいます。

次にhttpsのconfigファイルを見てみましょう。

  • ./unit/https.json
{
  "listeners": {
      "*:8080": {
          "pass": "applications/php",
          "tls": {
              "certificate": "bundle"
          }
      }
  },
  "applications": {
      "php": {
          "type": "php",
          "root": "/usr/share/nginx/"
      }
  }
}

http側と比べて5~7行目tlsの設定が増えています。NGINXだけでなくNGINX Unitもhttpsとして接続を受け付けるのであれば、当然NGINX Unit側にも証明書が必要になってくるわけです。
これがまたちょっと癖があって証明書(CRT)と秘密鍵(KEY)だけでなく、証明書署名要求(CSR)も必要で、さらにそれを一つのファイルにまとめる必要があります。

公式でも説明されていますが、まとめかたはそれぞれのpemファイルをcatで一つに結合してしまえば良いです。

$> cat cert.pem csr.pem key.pem > bundle.pem

この結合したファイルはjsonファイルと同様に /docker-entrypoint.d にpemファイルを置いておくとこれも起動時にAPIを呼び出して自動で投入してくれます。
jsonの6行目でcertificateの値として使用する証明書を設定しますが、この用にして作成したpemファイルの名前を指定します。拡張子は不要です。

httpとhttps

で、なんで転送をhttpとhttpsでやる二つの例を用意したのか?というポイントについては力尽きたのでまた今度にします。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

This site uses Akismet to reduce spam. Learn how your comment data is processed.