🐥

Nginxにて単一ポート番号上でSSLと非SSLプロトコル両方を受け付ける

2023/04/18に公開

やりたいこと

Nginxにて単一のポート番号上でSSLと非SSLのどちらでもリクエストを受け付けるようにしたい。

通常のhttpモジュールのみの設定では同じポート番号でSSLと非SSLの両方を受け付けることはできない。

上記が必要なシチュエーションとして、現在SSL終端を担っているNginxよりも上位にSSL終端を実施するロードバランサを配置したい際に、ゼロダウンタイムで構築したいケースがあった。

実現方法

Nginx 1.15.2 から導入されたstream_ssl_prereadモジュール内の$ssl_preread_protocol変数を利用することでSSL通信時のinitial ClientHelloを見てHTTPかHTTPSかを事前に判定する。それにより、表側のstream(TCP通信)にて単一ポート番号で受け付けても裏側(Upstream)への転送をSSLか非SSLかに分けることができる。

この内容の詳細は以下の公式ブログを参照。

https://www.nginx.com/blog/running-non-ssl-protocols-over-ssl-port-nginx-1-15-2/

実装内容

以下の内容にてポート番号が4つも登場し、ややこしいので以下にまとめます。

  • :8080 → Nginxサーバの表側であるstream(TCP通信)が受け付けるポート番号
  • :8081 → upstream側から更に最終的に受け渡したいアプリケーションサーバが受け付けているポート番号
  • :8082 → streamが受け渡すupstream側のHTTP通信のポート番号
  • :8083 → streamが受け渡すupstream側のHTTPS通信(SSL)のポート番号

stream(TCP)側の設定が以下。

/etc/nginx/nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}

stream {
    upstream http {
        server localhost:8082; # HTTP通信の場合は8082ポートへ転送
    }

    upstream https {
        server localhost:8083; # HTTPS通信の場合は8083ポートへ転送
    }

    map $ssl_preread_protocol $upstream { # SSL通信時のinitial ClientHelloの内容が$ssl_preread_protocolに入り$upstream変数に値が入る。
        default https;                    # SSL通信を検知したら $upstream = https
        "" http;                          # SSL通信で無い場合は $upstream = http
    }

    server {                  # stream(TCP通信)のサーバ設定
        listen 8080;
        proxy_pass $upstream; # 上記のmap設定によって$upstreamはhttpsかhttpのどちらかになる。これにより単一ポート番号(8080)でHTTP、HTTPS両方をハンドリングしている。
        ssl_preread on;       # SSL通信時のinitial ClientHelloを見るかどうかを設定。これをonにしないと$ssl_preread_protocolが動作しない。
    }
}

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

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;

    keepalive_timeout  65;

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

上記のstreamから流れてくる通信を受け付けるupstream側の設定が以下。

/etc/nginx/conf.d/default.conf

upstream app {
    server localhost:8081 max_fails=1 fail_timeout=60
}

server { # upstream側でHTTPとHTTPSをそれぞれ受け付けるポートを別で持つ。
    listen       8083 ssl;
    listen       8082;

    ssl_certificate     path-to-cert-file;
    ssl_certificate_key path-to-key-file;

    server_name  example.com;

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

    location / {
        proxy_pass http://app; # 最終的にアプリケーションサーバへ転送する
    }
}

設定内容の意味はコメントアウトとして記載しています。

参考

GitHubで編集を提案

Discussion