親バカエンジニアのナレッジ帳

webのエンジニアをやっており、日頃の開発で詰まったことや書き残しておきたいことを載せています。

ELBでNginxを利用した場合の完全SSL化


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;
    }
}