Nginx&オレオレ証明証でNextJS&FastAPIをSSL化&HTTPS通信を行う

Docker

はじめに

自分でNext.jsでアプリを作っているときにスマホからPCのIPにアクセスして
デザインを確認したい場面があった。
しかし、位置情報を使うアプリだったためスマホからはHTTP通信で位置情報はHTTPS通信を使うため
実行エラーになる時があった。
スマホで手っ取り早くHTTPSアクセスするためになにか方法がないか調べたがどうやらNginxが良さげだったので使ってた。
今回のサンプルは以下githubにあります。

nginx_sample/nextjs_nginx at main · sonitan0803/nginx_sample
Nginxのサンプル. Contribute to sonitan0803/nginx_sample development by creating an account on GitHub.

なるべく公式ドキュメントも見るようにしているが、英語の翻訳があっているか怪しいので
参考に載せておく

実行結果

実装

今回はNext.js,FastAPI,NginxをすべてDocker上に構築している。
Dockerの構築は以下のようにしている

.
|_fastapi_sample
| |_DockerFile
|_next_sample
| |_DockerFile
|_nginx
| |_Dockerfile
|_docker-compose.yml

Docker

Dockerの設定は以下のようにしている

docker-compose.yml

version: "3.0"

services:
    nextjs:
        image: nextjs_image
        container_name: nextjs_containe
        build:
            context: .
            dockerfile: ./next_sample/Dockerfile
        networks:
            - my_network

    fastapi:
        image: fastapi_image
        container_name: fastapi_container
        build: ./fastapi_sample
        networks:
            - my_network

    nginx:
        image: nginx_image
        container_name: nginx_container
        build:
            context: .
            dockerfile: ./nginx/Dockerfile
        ports:
            - "80:80"
            - "81:81"
            - "443:443"
        depends_on:
            - nextjs
            - fastapi
        networks:
            - my_network

networks:
    my_network:
        driver: bridge

今回はNginxからのアクセスを想定している。
そのため、直接 Next.jsとFastAPIのアプリにはアクセスしないようにしたいため
Next.js と FastAPI のコンテナには直接アクセスできないようにポートはマッピングしていない。
そのため、my_networkにNginx,Next.js,FastAPIを入れることで
ポートをマッピングしているNginxからのアクセスを可能にしている。
ちなみにだが、別に同一アプリの場合実際は設定しなくても、接続可能らしいが…..

デフォルトで Compose はアプリに対して1つの ネットワーク を作成します。サービス用の各コンテナはデフォルトのネットワークに接続し、そのネットワーク上で他のコンテナと相互に「 接続可能reachable 」になります。
https://docs.docker.jp/compose/networking.html

fastapi_sample/Dockerfile

FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

COPY ./app/ .

# ポート開放
EXPOSE 8080

CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8080"]

requirements.txt は以下のようにしている


fastapi
uvicorn

next_sample/DockerFile


FROM node:18-alpine

# 作業ディレクトリ
WORKDIR /app

# pnpm install
RUN npm install -g pnpm

# コード全体を /app にコピー
COPY ./next_sample /app

# 依存関係をインストール
RUN pnpm install

# ビルド
RUN pnpm run build

# ポート開放
EXPOSE 3000

# 起動
CMD [ "pnpm", "start" ]

今回は実装を丸々コピーしてそれをコンテナ内に入れて
ビルド->起動をしている。
node_modulesファイルをコピーすると無駄に時間がかかるので.gitignoreファイルを作って
コピーはしないようにしている。


node_modules
README.md
build

nginx/Dockerfile

FROM nginx:alpine

#設定ファイルコピー
COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf
#証明書コピー
COPY ./nginx/ssl/crt.pem /etc/nginx/ssl/fullchain.pem
COPY ./nginx/ssl/privkey.pem /etc/nginx/ssl/privkey.pem
COPY ./nginx/ssl/passwd /etc/nginx/ssl/passwd

今回はSSL化するためCOPYで証明書やパスワードなどをまるっとコピーしている
nginx.conf をコピーして、デフォルト設定を書き換えている
SSLのディレクトリは今回は、用意していないので自分で証明書を作る必要がある。
自分は以下を参考に、nginx/ssl ディレクトリに証明証を作成した。

Nginxに自己署名証明書を設定してHTTPS接続してみる - Qiita
はじめにSSL/TLS の勉強として、自己署名証明書を用意して SSL 通信をやってみたのでまとめる。やること今回やることは以下。自己署名証明書の用意秘密鍵の作成CSR の作成証明書に…

Nginx

ここからは今回のキモ(多分)であるNginxの設定を見ていく。
全体の構成は以下になっている


server {
    listen 80;
    server_name localhost;

    # HTTPからHTTPSへリダイレクト
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_password_file /etc/nginx/ssl/passwd;

   location / {
        proxy_pass http://nextjs:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /_next/ {
        proxy_pass http://nextjs:3000/_next/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /static/ {
        proxy_pass http://nextjs:3000/static/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # FastAPI コンテナへのリバースプロキシ設定
    location /api {  # ここでFastAPIのエンドポイントを"/api/"以下に設定
        proxy_pass http://fastapi:8080;  # FastAPIコンテナのホスト名とポート
        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;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

ここからは詳細を紐解いていきたい

SSLの基本設定

server {
    listen 80;
    server_name localhost;

    # HTTPからHTTPSへリダイレクト
    return 301 https://$host$request_uri;
}

ここはHTTP通信できたときにHTTPS通信にリダイレクトしている
ListenにHTTPポート(80)を指定している。
今回はlocalhostで開発を前提にしているので、localhostをserver_nameに指定している

公式ドキュメントを見るとserverブロックにはlistenするポートとserver_nameを入れるらしい。

Generally, the configuration file may include several server blocks distinguished by ports on which they listen to and by server names.
https://nginx.org/en/docs/beginners_guide.html

returnは元々公式を見ると、処理を中断してステータスを返すために使われていたようにみえる
バージョン0.8.42からリダイレクトとしても機能するようにしたぜ!って書いている。
301ステータス自体は永続的にリダイレクトするという意味らいい

Stops processing and returns the specified code to a client.
The non-standard code 444 closes a connection without sending a response header.
Starting from version 0.8.42, it is possible to specify either a redirect URL (for codes 301, 302, 303, 307, and 308) or the response body text (for other codes). A response body text and redirect URL can contain variables.
As a special case, a redirect URL can be specified as a URI local to this server, in which case the full redirect URL is formed according to the request scheme ($scheme) and the server_name_in_redirect and port_in_redirect directives.https://nginx.org/en/docs/http/ngx_http_rewrite_module.html

returns a permanent redirect with the 301 code.https://nginx.org/en/docs/http/ngx_http_rewrite_module.html

公式分だけだと??だったが以下を参考にすると、なんとなくhttp通信は永続的にhttps通信に変えたいというふうに思えば
良いのかなと理解した。

Nginx returnディレクティブ - Qiita
returnディレクティブ処理を停止し、指定されたcodeをクライアントに返します。非標準のコード 444 は応答ヘッダを送信せずに接続を閉じます。バージョン 0.8.42から、リダイレクトUR…
server {
    listen 443 ssl;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_password_file /etc/nginx/ssl/passwd;

NginxでHTTPS通信をするためにはserver blockのlistenソケットにsslをつけろとのことだ
また、サーバ証明書と秘密鍵の場所も定義してねとのこと

To configure an HTTPS server, the ssl parameter must be enabled on listening sockets in the server block, and the locations of the server certificate and private key files should be specified:https://nginx.org/en/docs/http/configuring_https_servers.html

ssl_certificate,ssl_certificate_keyはpem形式のファイルを指定してと、公式ドキュメントにある。
証明書の仕組みは今回は、理解していないので別途理解を深めたいところではある。

Specifies a file with the certificate in the PEM format for the given virtual server. If intermediate certificates should be specified in addition to a primary certificate, they should be specified in the same file in the following order: the primary certificate comes first, then the intermediate certificates. A secret key in the PEM format may be placed in the same file.

Since version 1.11.0, this directive can be specified multiple times to load certificates of different types, for example, RSA and ECDSA:https://nginx.org/en/docs/http/ngx_http_ssl_module.html

ssl_password_file は読んで字のごとく秘密鍵のパスワードを書くとのこと

Specifies a file with passphrases for secret keys where each passphrase is specified on a separate line. Passphrases are tried in turn when loading the key.https://nginx.org/en/docs/http/ngx_http_ssl_module.html

まずはここまでの実装でHTTPS通信をするための、基本設定ができたと言えよう。

HTTPS通信でNext.jsのアプリへアクセス


 location / {
        proxy_pass http://nextjs:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

以下のようにURIを指定すると、そのURIにアクセスした場合の処理をかけるらしい

location URI {
	context
}

proxy_pass は受け取ったリクエストを http://nextjs:3000 に転送する設定のようだ。
今回 nextjs はDockerのコンテナ名を指定している。

proxy_http_version は HTTP/1.1 で通信するように設定している。
WebSocket とかを使うときに HTTP/1.0 だと非対応だから指定する必要があるらしい。
ただ、今回は必要ないのでいらないかも….(参考元のコピペだから今後調査が必要)
公式ドキュメントだと NTLM認証とkeepalive接続とのときに使うと良いよとのこと。(2つとも私はまだしらない)

Version 1.1 is recommended for use with keepalive connections and NTLM authentication.
https://nginx.org/en/docs/http/ngx_http_proxy_module.html

以下の4点は自分の中で調べてみたが、必要性がわかっていないので別途調査をしたい

  • proxy_set_header Upgrade $http_upgrade;
  • proxy_set_header Connection “upgrade”;
  • proxy_set_header Host $host;
  • proxy_cache_bypass $http_upgrade;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

公式を参考にすると、指定したエラーに対して表示するURIを表示するらしい

Defines the URI that will be shown for the specified errors. A uri value can contain variables.

https://nginx.org/en/docs/http/ngx_http_core_module.html

Next.js

Next.jsのDocker化については別記事で書く予定

FastAPI

FastAPIのDocker化については以下の記事で紹介しているので、参考にしてください

docker-composeでFastAPIを簡単構築
はじめに 以前Nginxの記事でFastAPIを使ったが、環境構築方法は残していなかったのでメモ。 今回のリポジトリは以下で公開している 実装 構成 今回はdocker-coposeで環境構築を行うため、以下のような構築 FastAPIでの...

さいごに

なるべく公式のドキュメントを参考にしたけど
読みにくかった…..
英語の勉強しないとな

この記事を書いた人
あかちゃん

現職のフロントエンジニア&組み込みエンジニア(現在2社目)
仕事では、C,C++,C#やTypescript,JavaScript,Html,CSS等の技術をメインに仕事しています。
組み込みだけをメインに7年ほどやり、ここ数年でweb関係の仕事をするようになってきました。
現在Web技術は副業をするため絶賛修行中

記事にはなるべくQitaとか個人ブログじゃなくて、公式などの一次ソースをなるべく載せるようにしたい。

あかちゃんをフォローする
DockerFastAPINext.jsNginxバックエンドフロントエンド環境
スポンサーリンク
あかちゃんをフォローする

コメント

タイトルとURLをコピーしました