はじめに
nginx をリバースプロキシにして、バックエンドのサーバーと WebSocket で通信しようとすると、接続が確立できずエラーになることがあります。
私は、最近 nginx をリバースプロキシに Vite の開発環境を構築していた際にこの現象に遭遇しました。
Vite は HMR するために WebSocket を用いているので、nginx の設定が不十分だと、コードを変更して保存してもブラウザ側にリアルタイムで反映されない状態になります。
おそらく、コンソール画面に以下のようなエラーが出力されるはずです。
調べてみた感じ、nginx で WebSocket 通信をしようとすると、追加で設定が必要だったので、それについてまとめます。
エラー内容
以下のように、ws を使って nginx をリバースプロキシに、WebSocket 通信を待ち受けるサーバーを構築してみます。
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 3000 });
wss.on('connection', (ws) => {
ws.on('error', console.error);
ws.on('message', (data) => {
console.log('received: %s', data);
});
ws.send('example');
})
server {
listen 80;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
websocat を WebSocket client として利用し、WebSocket 通信をしようとすると、以下のようなエラーが出力されます。
$ websocat ws://localhost:80
websocat: WebSocketError: WebSocketError: Received unexpected status code (426 Upgrade Required)
websocat: error running
426 Upgrade Required
The HTTP 426 Upgrade Required client error response status code indicates that the server refused to perform the request using the current protocol but might be willing to do so after the client upgrades to a different protocol.
現在のプロトコルではリクエストを実行できないが、プロトコルをアップグレードすれば受け付ける可能性があることを示すステータスコードです。
426 Upgrade Required が出力される理由
WebSocket 通信は、HTTP1.1 のリクエストの送信後に、プロトコルを切り替えることによって実現されています。
このプロトコルの切り替えは、クライアントからのリクエストヘッダーに含まれる Upgrade と Connection を確認することによって行われます。( https://datatracker.ietf.org/doc/html/rfc6455#section-4.1 )
nginx をリバースプロキシとして動かす場合、デフォルトではこれらのヘッダーがバックエンドのサーバーへ転送されないようになっています。
これは Hop-by-hop という、HTTPプロキシが転送先へ渡してはいけない HTTP ヘッダーが定められていて、nginx がその仕様に則っているためです。
ということで、nginx をリバースプロキシにして WebSocket 通信を行おうとすると、本来必要なヘッダーが削除されてしまうため、バックエンドのサーバーに正しくリクエストが転送されず WebSocket 通信を開始できないため 426 が返されるということになります。
対応方法
Upgrade と Connection を正しく転送できるようにすればいいので、以下のように設定ファイルを少し変更するだけで、対応することができます。
server {
listen 80;
location / {
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_http_version 1.1;
proxy_pass http://127.0.0.1:3000;
}
}
参考
