LoginSignup
5
9

More than 3 years have passed since last update.

wwwなしの、SSL(https)にリダイレクトしたいときのNGINXの設定 -Route 53 + ELB (ACM) + EC2構成-

Last updated at Posted at 2020-08-12

全てのトラフィックをwwwなしの、SSL(https)にリダイレクトしたい

結論

長いので先に結論だけ書きます

  • SSL証明にACMを使い、ELBに証明書が適応されている状態を想定

  • ELB <-> EC2の接続がHTTP: 80の場合、NGINXはport 80でlistenする (ここの気付きが重要でした)

  • NGINXによるリダイレクトはwwwあり->wwwなしのみでよい

  • httpsリクエストをNGINXの80で受けるという設定に戸惑ったが、動作上これで良さそう

ELBでhttp -> httpsリダイレクトを行う方法も書いてみました
HTTP -> HTTPSのリダイレクトはELBで行う方がスマートかもしれない (Nginxでリダイレクトを行う方法あり) - Qiita

現状と目標

httpsかつ、wwwなしのドメインをリクエストしないとSSL接続されない

理想は以下の全ての条件でSSL接続にリダイレクトしたいが現状は

# wwwあり
http://www.mdclip.xyz -> http://mdclip.xyz  (no SSL)
https://www.mdclip.xyz ->  http://mdclip.xyz (no SSL)

# wwwなし
http://mdclip.xyz -> http://mdclip.xyz (no SSL)
https://mdclip.xyz -> https://mdclip.xyz (SSL)

環境

  • DNS (Route 53) -> ELB -> EC2 (NGINX -> Puma)
  • SSL証明: ACM
  • Rails 6 (config/environments/production.rb: config.force_ssl = true)

NGINXの設定はnginx.confに書く?conf.d/*.confに書く

NGINXの設定はnginx.confに記述されているが
その中で/etc/nginx/conf.d/*.confがincludeされており
conf.d以下の内容が呼び出されるようになっている

記事によってこの辺りが曖昧でどのように使い分けるか把握しにくかったが
以下の記事をもとにイメージを掴むことができた

参考: nginx連載3回目: nginxの設定、その1 - インフラエンジニアway - Powered by HEARTBEATS

NGINXではserverディレクティブ(サーバー個別の設定、server {...}で記述される)で定義された
仮想サーバーを複数稼働させることができる
そして、このバーチャルサーバー毎の設定を置く場所がconf.d以下とのこと

なのでconf.d以下のfoo.confには単一のサーバーについての記述に留め
複数のserverディレクティブが記述されているような運用は良くないのかもしれない

ただし、今回conf.d以下の設定ファイルにドメイン間のリダイレクトを担う
serverディレクティブを複数配置してみたが、動作上は問題なさそうでした

全てのトラフィックをwwwなしの、SSL(https://)にリダイレクトしたい場合のNGINXの設定

結論

後述の内容から、以下のように書けば良いと理解した

server {
    listen 80; 
    server_name example.com;
    return 301 https://example.com$request_uri;
}
server {
    listen 80;
    listen 443 ssl; 
    server_name www.example.com; 
    return 301 https://example.com$request_uri; 
}

もしくは

server {
    listen 80; 
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}
server {
    listen 443 ssl; 
    server_name www.example.com; 
    return 301 https://example.com$request_uri; 
}

wwwなし -> wwwあり(その逆パターン)

# add 'www'
server {
    listen 80; #非SSL
    listen 443 ssl; #SSL こうやって2つ指定できる
    server_name example.com; #wwwなしを
    return 301 $scheme://www.example.com$request_uri; #wwwありへ
}

# 上記と同意
server {
    listen 80; #非SSL
    server_name example.com;
    return 301 $scheme://www.example.com$request_uri;
}

server {
    listen 443 ssl; #SSL 
    server_name example.com;
    return 301 $scheme://www.example.com$request_uri;
}


# remove 'www'
server {
    listen 80; #非SSL
    listen 443 ssl; #SSL 
    server_name www.example.com; #wwwありを
    return 301 $scheme://example.com$request_uri; #wwwなしへ
}

参考: How to Create NGINX Rewrite Rules | NGINX

Adding and Removing the www Prefix

These examples add and remove the www prefix:

# add 'www'
server {
listen 80;
listen 443 ssl;
server_name domain.com;
return 301 $scheme://www.domain.com$request_uri;
}

# remove 'www'
server {
listen 80;
listen 443 ssl;
server_name www.domain.com;
return 301 $scheme://domain.com$request_uri;
}

Again, return is preferable to the equivalent rewrite, which follows. The rewrite requires interpreting a regular expression – ^(.*)$ – and creating a custom variable ($1) that in fact is equivalent to the built‑in $request_uri variable.

# NOT RECOMMENDED
rewrite ^(.*)$ $scheme://www.domain.com$1 permanent;

http(非SSL) -> https(SSL)にリダイレクト

# http(非SSL) -> https(SSL)に転送
server {
    listen 80; 
    server_name www.example.com;
    return 301 https://www.example.com$request_uri;
}

古いドメインから新しいドメインに移行する場合などは
敢えて$request_uriを省略することでホームページへ都度リダイレクトすることも可能

参考: How to Create NGINX Rewrite Rules | NGINX

Example – Forcing all Requests to Use SSL/TLS

This server block forces all visitors to use a secured (SSL/TLS) connection to your site.

server {
 listen 80;
 server_name www.domain.com;
 return 301 https://www.domain.com$request_uri;
}

Some other blogs about NGINX rewrite rules use an if test and the rewrite directive for this use case, like this:

# NOT RECOMMENDED
if ($scheme != "https") {
 rewrite ^ https://www.mydomain.com$uri permanent;
}

But this method takes extra processing because NGINX must both evaluate the if condition and process the regular expression in the rewrite directive.

全てのトラフィックを特定のドメインにリダイレクト

server {
    listen 80 default_server; # http://
    listen 443 ssl default_server; # https://
    server_name _; # "_"全一致
    return 301 $scheme://www.example.com;
}

参考: How to Create NGINX Rewrite Rules | NGINX

Redirecting All Traffic to the Correct Domain Name

Here’s a special case that redirects incoming traffic to the website’s home page when the request URL doesn’t match any server and location blocks, perhaps because the domain name is misspelled. It works by combining the default_server parameter to the listen directive and the underscore as the parameter to the server_name directive.

server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
return 301 $scheme://www.domain.com;
}

We use the underscore as the parameter to server_name to avoid inadvertently matching a real domain name – it’s safe to assume that no site will ever have the underscore as its domain name. Requests that don’t match any other server blocks in the configuration end up here, though, and the default_server parameter to listen tells NGINX to use this block for them. By omitting the $request_uri variable from the rewritten URL, we redirect all requests to the home page, a good idea because requests with the wrong domain name are particularly likely to use URIs that don’t exist on the website.

rewriteよりもreturnを用いることが望ましい理由

  • if...処理を都度行うことは効率的でない.
  • rewriteを用いるとNGINXが正規表現を処理する必要があり効率的でない.
  • ヒトにとってもNGINXが301コードを返すことが明示的で解釈しやすい.

以上を踏まえたNGINXの設定(NG)

nginx.conf
いずれのバーチャルサーバーにも該当しない場合のトラフィックを受ける
default_serverについての記述のみ残しました

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    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;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

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

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

    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 / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

conf.d/app.conf

error_log  /var/www/myapp/shared/log/nginx.error.log;
access_log /var/www/myapp/shared/log/nginx.access.log;

upstream myapp {
    server unix://var/www/myapp/shared/tmp/sockets/puma.sock;
}

## #1 以下の2つのserverディレクティブで, wwwなしのhttpsにリダイレクト
# http > https
server {
    listen 80;
    server_name example.com;
    return 301 https://example.com$request_uri;
}
# www > no_www
server {
    listen 80;
    listen 443 ssl;
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}


server {
    listen 443 ssl; # #2 httpsで受ける
    client_max_body_size 4G;
    server_name example.com;

    keepalive_timeout 5;

    # Location of our static files
    root /var/www/myapp/current/public;

    location ~ ^/assets/ {
    root /var/www/myapp/current/public;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        if (!-f $request_filename) {
            proxy_pass http://myapp;
            break;
        }
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/myapp/current/public;
    }
}

ところがこの時点での結果は、

# wwwあり
http://www.mdclip.xyz -> http://www.mdclip.xyz  (no SSL)
https://www.mdclip.xyz ->  https://www.mdclip.xyz (SSL)

# wwwなし
http://mdclip.xyz -> http://mdclip.xyz (no SSL)
https://mdclip.xyz -> https://mdclip.xyz (SSL)

スクリーンショット 2020-08-12 22.19.34.png

しかも全てのトラフィックがPumaではなくdefault_serverに到達してしまった

以下再度検証へ...

最終検証・解決

AWS Route 53

example.comだけでなく、www.example.comのAレコードについても
ルーティング先にELBのエイリアスを指定する必要がありました

これがないとWWWありのリクエストがNGINXまで到達せず

”サーバーが見つからない”エラーが出ます

ELBの設定を確認

スクリーンショット 2020-08-12 21.07.01.png

Target groupの設定はEC2インスタンスにHTTP: 80で接続するようになっていました
(ここもHTTPS: 443に変更する方法もあるようです)

この状況を図にすると
(これがやりたかった!)

traffic_flow.png

重要なのはHTTPSリクエストに対しても
EC2は80ポートでELBと接続されているということ

(言葉が不適切でしたら訂正願います)

だから

  • NGINX側は443ではなく80でlistenする必要がある
  • 443 > 80へのリダイレクトはNGINXで設定不要(上図の緑の部分のみでいい)

NGINXの設定(再・OK)

nginx.conf
ここは変わりなし

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    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;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

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

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

    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 / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

conf.d/app.conf

error_log  /var/www/myapp/shared/log/nginx.error.log;
access_log /var/www/myapp/shared/log/nginx.access.log;

upstream myapp {
    server unix://var/www/myapp/shared/tmp/sockets/puma.sock;
}

# www > no_www
server {
    listen 80; #443は不要
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 80; #80でlisten
    client_max_body_size 4G;
    server_name example.com;

    keepalive_timeout 5;

    # Location of our static files
    root /var/www/myapp/current/public;

    location ~ ^/assets/ {
    root /var/www/myapp/current/public;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        if (!-f $request_filename) {
            proxy_pass http://myapp;
            break;
        }
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/myapp/current/public;
    }
}

これで以下のように目標が達成されました

# wwwあり
http://www.mdclip.xyz -> https://mdclip.xyz  (SSL)
https://www.mdclip.xyz ->  https://mdclip.xyz (SSL)

# wwwなし
http://mdclip.xyz -> https://mdclip.xyz (SSL)
https://mdclip.xyz -> https://mdclip.xyz (SSL)

おまけ

AmazonLinux環境でのNGINX関連コマンド

設定をリロード

sudo systemctl reload nginx

再起動

sudo systemctl restart nginx

起動

sudo systemctl start nginx

終了

sudo systemctl stop nginx

NGINXのプロセスを確認 (残ったプロセスをkillするときなど)

sudo lsof -i | grep nginx
5
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
9