🐥
Nginxにて単一ポート番号上でSSLと非SSLプロトコル両方を受け付ける
やりたいこと
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かに分けることができる。
この内容の詳細は以下の公式ブログを参照。
実装内容
以下の内容にてポート番号が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; # 最終的にアプリケーションサーバへ転送する
}
}
設定内容の意味はコメントアウトとして記載しています。
Discussion