NginxのリバースプロキシでWebソケットを通す際の設定

Webソケットを利用するサービスをNginx経由で動作させるための最低限の設定と、設定項目の意味を整理しました。

# 前提

本記事はnginxの設定方法をある程度知っていることを前提としています。
ディレクティブの説明などは別の記事をご参照ください。

一般公開しない、開発・分析用途を想定しています。
セキュリティやパフォーマンスについては考慮していないのでご注意ください。

具体例として、データ分析で人気のJupyterLabをNginx経由で利用するためのコードをベースに記事にしました。

# Webソケットを使わない設定

まずは、シンプルな転送設定を確認します。
サブドメイン経由のアクセスをJupyterLabのサーバーへ転送しているだけです。

```nginx
server {
    listen 80;
    server_name jupyter.sample.domain;
    
    location / {
        proxy_pass   http://jupyterlab:8888;
    }
}
```

この場合、ログインはできるのですがWebソケットによる通信を開始できず、サービス内のシェル機能や肝心の分析機能が動きません。

## Webソケットが使える転送設定

次のように変更するとWebソケットが利用可能になります。

```nginx
map $http_upgrade $connection_upgrade { 
    default upgrade;
    ''      close;
} 

server {
    listen 80;
    server_name jupyter.sample.domain;

    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade; 
    proxy_set_header Connection $connection_upgrade;

    location / {
        proxy_pass   http://jupyterlab:8888;
    }
}
```

詳しい説明は公式の[WebSocket proxying](http://nginx.org/en/docs/http/websocket.html)にも記載があります。
日本語では[こちらの記事](http://uorat.hatenablog.com/entry/2016/09/19/203939)が詳しいです。

# 追加項目の解説

以降、順を追って追加した項目を説明します。

## map

ある変数の内容に応じて、別の変数の値を決めたいときに使用する機能です。
if文で置き換える事も可能です。

```nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
```

＄http_upgradeの値が空('')の場合は、
＄connection_upgrade = close

それ以外の場合は、
＄connection_upgrade = upgrade

という処理が行われています。

参考：[nginxにおけるmapとその応用](https://qiita.com/cubicdaiya/items/d938f3354f424830630b#ngx_http_split_clients_module)

## proxy_http_version 1.1;

HTTPプロトコルバージョンを指定します。

webソケットに必要なkeep-aliveはバージョン1.1以降で利用可能ですが、デフォルトでは1.0なので明示的に指定する必要があります。下記の「proxy_set_header Upgrade」や「proxy_set_header Connection」も1.1以降でのみ利用可能なヘッダ設定です。

## proxy_set_header Host $host;

ホスト情報をプロキシサーバのものではなく、接続元のものに書き換えます。

これはWebソケットの利用に必ず必要な設定ではありませんが（先に挙げた[公式の記事](http://nginx.org/en/docs/http/websocket.html)の設定例には入っていません）、JupyterLabでは無いとwebソケットの接続に失敗したので追加しました。

## proxy_set_header Upgrade $http_upgrade;

プロトコルスイッチという仕組みで通信プロトコルをHTTP/1.1からwebsocketに変更します。

プロコルスイッチはHTTP/1.1から利用可能になった仕組みです。
先ほどの[公式の記事](http://nginx.org/en/docs/http/websocket.html)に記載があります。

ここで紹介しているUpgradeと次のConnectionは、Hop-by-hopヘッダと呼ばれる特殊なヘッダ情報です。通常のEnd-to-endヘッダは情報を最終的な宛先に届けるために使用するのに対し、Hop-by-hopヘッダは（存在する場合は）プロキシに対して値を渡すために使用されます。

参考：[HTTPプロキシでのヘッダ制御](https://qiita.com/unhurried/items/93a0c2c14aeef234b4b0)

## proxy_set_header Connection $connection_upgrade;

送信先にコネクションを維持するか否かを伝えるためのヘッダーです。

ここでは、$http_upgradeの値に応じて設定済みの、「upgrade」あるいは「close」を設定しています。

# おまけ

追加項目の説明は以上ですが、少し重めな設定を記載します。
筆者自身、下記の設定をそのまま使っているわけではありませんが、うまくいかない時のトライ＆エラー用も兼ねて載せておきます。

```nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name jupyter.sample.domain;
    
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_read_timeout 600s;
    # proxy_set_header Connection "";
    
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
    proxy_set_header X-Frame-Options SAMEORIGIN;
    proxy_buffering on;
    proxy_buffers 256 16k;
    proxy_buffer_size 16k;
    # proxy_set_header X-Forwarded-For $remote_addr;

    # GCPのLBへ転送する際に発生したセキュリティ対応
    # proxy_set_header X-Content-Type-Options nosniff;
    proxy_hide_header X-Content-Type-Options;

    # セキュリティを強化するため、元々なかったヘッダを追加する場合に利用
    # add_header X-Frame-Options SAMEORIGIN;
    # add_header X-XSS-Protection "1; mode=block";
    # add_header X-Content-Type-Options nosniff;
    # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' https://gravatar.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'";
    
    client_max_body_size 50M;
    
    location / {
        proxy_pass   http://jupyterlab:8888;

        access_log /var/log/nginx/jupyterlab_access.log flush=5s;
        error_log /var/log/nginx/jupyterlab_error.log flush=5s;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    
    # include /etc/nginx/proxy.d/*.conf;
} 

```

# 参考資料

## Nginxのリバースプロキシ設定関連

[Nginxを用いたWebSocketサーバのReverseProxy構成及びSSL/TLS接続](http://uorat.hatenablog.com/entry/2016/09/19/203939)

[Nginxによるリバースプロキシの設定方法](https://qiita.com/schwarz471/items/9b44adfbec006eab60b0)

[proxy関連の設定リファレンス(公式)](http://nginx.org/en/docs/http/ngx_http_proxy_module.html)

[Webソケットをリバプロする(公式)](http://nginx.org/en/docs/http/websocket.html)

## HTTPヘッダーについて

[IANAの資料](https://www.iana.org/assignments/message-headers/message-headers.xhtml)

[X-Forwarded-For](https://ja.wikipedia.org/wiki/X-Forwarded-For)

[nginxでX-Forwarded-Forの値に$proxy_add_x_forwarded_forを安易に使わない](https://www.serotoninpower.club/archives/790/#%E5%85%AC%E5%BC%8F%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AE-proxy-add-x-forwarded-for-%E3%81%AE%E8%AA%AC%E6%98%8E)

[HTTP ヘッダー(MDN)](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers)

[HTTP(wikipedia)](https://ja.wikipedia.org/wiki/Hypertext_Transfer_Protocol#HTTP/2)

[HTTPプロキシでのヘッダ制御](https://qiita.com/unhurried/items/93a0c2c14aeef234b4b0)

## Webソケットについて

[WebSocketについて調べてみた。](https://qiita.com/south37/items/6f92d4268fe676347160)

[Websocket(wikipedia)](https://ja.wikipedia.org/wiki/WebSocket)