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

# 全てのトラフィックを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://qiita.com/naokit-dev/items/c62bdf1d1656c4dba091)

  



# 現状と目標

`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](https://heartbeats.jp/hbblog/2012/02/nginx03.html)

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

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

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



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

## 結論

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

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

もしくは

```yml
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あり（その逆パターン）

```yml
# 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](https://www.nginx.com/blog/creating-nginx-rewrite-rules/)
>
>### 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](https://www.nginx.com/blog/creating-nginx-rewrite-rules/)
>
> ## 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.



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

```yml
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](https://www.nginx.com/blog/creating-nginx-rewrite-rules/)
>
>### 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.
>
>```yml
>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`についての記述のみ残しました

```yml
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

```yml
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 以下の２つの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)
```
<img width="1440" alt="スクリーンショット 2020-08-12 22.19.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649386/fb611143-7c66-bebe-73f2-f055b07cc97a.png">


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

以下再度検証へ...



# 最終検証・解決

## AWS Route 53

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

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

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

## ELBの設定を確認
<img width="1192" alt="スクリーンショット 2020-08-12 21.07.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649386/50333c27-4214-5c0d-cfa7-c782f449d7e9.png">


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

この状況を図にすると
（これがやりたかった！）

![traffic_flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649386/b4c50c62-9299-1b11-a222-77b6604004f6.png)


**重要なのはHTTPSリクエストに対しても
EC2は80ポートでELBと接続されているということ**
(言葉が不適切でしたら訂正願います)

だから

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

## NGINXの設定（再・OK）

nginx.conf
ここは変わりなし

```yml
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

```yml
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
```