AWSのAWS Certificate Manager (ACM)をELBに適用し、SSLを強制的に適用させたい場合の対応方法です。
ELBではリスナーの設定で、ポート80(http)でアクセスされた場合もポート443(https)でアクセスされた場合もポート80でEC2に転送される設定になっていることが前提です。(よくある設定ですね)
webサーバはNginxを使用します。
ACMは無料で利用でき、設定も容易なために使いましたが、ELBを完全に理解していない状態で対応を行ったため、以下のことをしてハマりました汗
・Nginxでポート80の場合は常に443(https)にリダイレクトさせ、リダイレクトの無限ループが発生してしまった。
まずACMをELBで適用させた場合のSSLの仕組みですが、ACMを使わずにサーバでSSL化している場合とは別物と考えた方が良いと思います。
サーバでSSL対応した場合、当然サーバ内でポート443による暗号化がされて通信されることになります。
ところがELBでSSL対応(ACM適用)した場合、ELBとEC2間の通信に暗号化が用いられ、サーバ内の通信は80(http)で行われるのです。
よって例えば以下のNginxの設定のようにポート80(http)で来たすべてのリクエストをSSLにリダイレクトしてしまうと、
server { listen 80 default_server; listen [::]:80 default_server; server_name *********; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; # リダイレクト処理 location / { return 301 https://$host$request_uri; } ・ ・ ・ }
ELBには443(https)でリクエストが送られますが、EC2に来る頃には再び80(http)でリクエストが飛んでくるために再度リダイレクト…といった具合に無限ループが発生してしまいます。
そこで使用するのがX-Forwarded-Protoというリクエストヘッダーです。これを使用すれば、クライアントとロードバランサーの通信間で使用されたプロトコル(つまりURLからアクセスされたプロトコル)を取得できるため、このプロトコルがhttpの時のみリダイレクトさせましょう。EC2内での通信は元々80(http)で行うため、X-Forwarded-Proto(URLのプロトコル)がhttpsになってさえいればあとはリダイレクトの必要はありません。
以下のようなif文を入れれば対応できました!
location / { if ($http_x_forwarded_proto != https) { return 301 https://$host$request_uri; } }