WebSocket を NGINX/HAProxy で終端する設定例:アップグレードヘッダーとタイムアウト完全ガイド

WebSocket は、クライアントとサーバー間でリアルタイム通信を実現する強力なプロトコルです。チャットアプリケーション、リアルタイムダッシュボード、オンラインゲームなど、双方向通信が必要な場面で広く使われています。しかし、本番環境で WebSocket を運用する際には、NGINX や HAProxy といったプロキシサーバーを介して通信を終端するケースが一般的です。
プロキシサーバーを介した WebSocket 通信では、「アップグレードヘッダー」の適切な設定と「タイムアウト」の調整が重要になります。これらの設定を誤ると、接続が確立できなかったり、予期せず切断されたりといった問題が発生してしまうでしょう。
本記事では、NGINX と HAProxy における WebSocket 終端の設定方法を基礎から実践まで丁寧に解説します。アップグレードヘッダーの役割、タイムアウトパラメータの意味、そして実際に使える設定例を通じて、安定した WebSocket 通信を実現する方法をお伝えしますね。
WebSocket とプロキシの基礎知識
WebSocket プロトコルの特徴
WebSocket は HTTP/1.1 をベースとして設計された通信プロトコルで、クライアントとサーバー間で永続的な双方向通信を可能にします。通常の HTTP 通信とは異なり、一度接続が確立されれば、サーバー側から能動的にデータを送信できる点が大きな特徴です。
WebSocket 通信は以下のような流れで確立されます。
mermaidsequenceDiagram
participant Client as クライアント
participant Proxy as プロキシ<br/>サーバー
participant Backend as バックエンド<br/>サーバー
Client->>Proxy: HTTP リクエスト<br/>Upgrade: websocket
Proxy->>Backend: HTTP リクエスト転送<br/>Upgrade: websocket
Backend->>Proxy: HTTP 101 Switching Protocols
Proxy->>Client: HTTP 101 Switching Protocols
Note over Client,Backend: WebSocket 接続確立
Client->>Backend: WebSocket フレーム送信
Backend->>Client: WebSocket フレーム送信
この図が示すように、WebSocket 通信は最初に通常の HTTP リクエストから始まります。クライアントは Upgrade: websocket
ヘッダーを含むリクエストを送信し、サーバーが 101 Switching Protocols
レスポンスを返すことで、プロトコルが HTTP から WebSocket へ切り替わるのです。
WebSocket プロトコルの主な特徴をまとめると以下のようになります。
# | 特徴 | 説明 |
---|---|---|
1 | 双方向通信 | クライアント・サーバー双方から自由にデータ送信が可能 |
2 | 低レイテンシ | 接続を維持するため、リクエストごとのオーバーヘッドがない |
3 | プロトコルアップグレード | HTTP から WebSocket へのプロトコル切り替えが必要 |
4 | フレームベース通信 | 小さなフレーム単位でデータをやり取り |
プロキシ終端が必要な理由
本番環境で WebSocket アプリケーションを運用する際、バックエンドサーバーを直接インターネットに公開することは推奨されません。代わりに、NGINX や HAProxy といったプロキシサーバーを前段に配置し、以下のような役割を担わせます。
セキュリティ層としての役割
プロキシサーバーは、外部からの直接アクセスを遮断し、TLS/SSL 終端を担当することで通信を暗号化します。DDoS 攻撃や不正なリクエストをフィルタリングする役割も果たすでしょう。
負荷分散とスケーラビリティ
複数のバックエンドサーバーへトラフィックを分散させることで、システム全体の可用性とパフォーマンスを向上させられます。特定のサーバーに負荷が集中するのを防ぎ、スムーズなスケールアウトを実現できますね。
以下の図は、プロキシサーバーを介した WebSocket 通信のアーキテクチャを示しています。
mermaidflowchart LR
clients["クライアント群"] -->|WebSocket<br/>リクエスト| proxy["プロキシサーバー<br/>(NGINX/HAProxy)"]
proxy -->|負荷分散| backend1["バックエンド<br/>サーバー 1"]
proxy -->|負荷分散| backend2["バックエンド<br/>サーバー 2"]
proxy -->|負荷分散| backend3["バックエンド<br/>サーバー 3"]
subgraph security["セキュリティ層"]
proxy
end
subgraph backend_pool["バックエンドプール"]
backend1
backend2
backend3
end
このアーキテクチャでは、プロキシサーバーが単一のエントリーポイントとなり、複数のバックエンドサーバーへ接続を振り分けます。クライアントはプロキシサーバーのアドレスのみを知っていればよく、バックエンドの構成変更にも柔軟に対応できるのです。
運用管理の一元化
アクセスログの集約、監視、トラフィック制御などを一箇所で管理できます。バックエンドサーバーの追加・削除もプロキシ設定の変更だけで対応可能です。
アップグレードヘッダーの役割
WebSocket 通信を確立するには、HTTP から WebSocket へのプロトコル切り替え、つまり「アップグレード」が必要になります。このプロトコル切り替えを実現するために、特定の HTTP ヘッダーが使用されるのです。
クライアントが WebSocket 接続を開始する際、以下のようなヘッダーを含む HTTP リクエストを送信します。
httpGET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
このリクエストの中で特に重要なのが Upgrade
と Connection
ヘッダーです。
# | ヘッダー名 | 役割 |
---|---|---|
1 | Upgrade | プロトコルを websocket へ切り替えることを要求 |
2 | Connection | Upgrade ヘッダーを適用することを指示 |
3 | Sec-WebSocket-Key | クライアントが生成するランダムな値(セキュリティ用) |
4 | Sec-WebSocket-Version | 使用する WebSocket プロトコルのバージョン |
サーバーがこのリクエストを受け入れる場合、以下のようなレスポンスを返します。
httpHTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
101 Switching Protocols
というステータスコードが、プロトコル切り替えの成功を示しています。この時点で、HTTP 接続は WebSocket 接続へと変換され、以降は WebSocket フレームを使った双方向通信が行われるのです。
プロキシサーバーでの問題
ここで注意が必要なのは、多くのプロキシサーバーはデフォルトで通常の HTTP 通信を想定しており、Upgrade
ヘッダーを適切に処理しないという点です。プロキシが Upgrade
ヘッダーをバックエンドへ転送しなかったり、Connection
ヘッダーを削除したりすると、プロトコル切り替えが失敗してしまいます。
したがって、NGINX や HAProxy で WebSocket を扱う際には、これらのヘッダーを正しく処理するための明示的な設定が必要になるのです。次のセクションでは、具体的な設定方法を見ていきましょう。
NGINX での WebSocket 終端設定
NGINX は高性能な Web サーバー兼リバースプロキシとして広く利用されており、WebSocket のプロキシ終端にも対応しています。ここでは、NGINX で WebSocket 接続を適切に処理するための設定方法を段階的に解説します。
基本設定:アップグレードヘッダーの構成
NGINX で WebSocket 通信を有効にするには、Upgrade
と Connection
ヘッダーをバックエンドサーバーへ適切に転送する必要があります。まずは基本的なヘッダー設定から見ていきましょう。
map ディレクティブによる Connection ヘッダーの動的設定
NGINX の設定ファイルの http
ブロック内に、以下の map
ディレクティブを追加します。
nginx# http ブロック内に配置
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
この設定は、クライアントから送られてくる Upgrade
ヘッダーの値に応じて、$connection_upgrade
という変数の値を動的に変更します。Upgrade
ヘッダーが存在する場合は upgrade
を、存在しない場合は close
をセットするのです。
この仕組みにより、WebSocket 接続とそうでない通常の HTTP 接続を同じプロキシ設定で扱えるようになります。
location ブロックでのヘッダー転送
次に、server
ブロック内の location
ディレクティブで、実際にヘッダーをバックエンドへ転送する設定を行います。
nginxlocation /ws {
# バックエンドサーバーのアドレス
proxy_pass http://backend_server:8080;
# HTTP バージョンを 1.1 に設定(WebSocket に必要)
proxy_http_version 1.1;
# Upgrade ヘッダーをバックエンドへ転送
proxy_set_header Upgrade $http_upgrade;
# Connection ヘッダーを動的に設定
proxy_set_header Connection $connection_upgrade;
}
この設定の各ディレクティブの役割を整理します。
# | ディレクティブ | 役割 |
---|---|---|
1 | proxy_pass | リクエストを転送するバックエンドサーバーのアドレス |
2 | proxy_http_version 1.1 | HTTP/1.1 を使用(WebSocket は HTTP/1.1 が必須) |
3 | proxy_set_header Upgrade | クライアントの Upgrade ヘッダーをそのまま転送 |
4 | proxy_set_header Connection | map で定義した変数を使って Connection ヘッダーを設定 |
proxy_http_version 1.1
は特に重要です。NGINX はデフォルトで HTTP/1.0 を使用しますが、WebSocket は HTTP/1.1 以降でしか動作しないため、この設定が必須となります。
追加の推奨ヘッダー設定
WebSocket 通信を安定させるために、以下のヘッダーも併せて設定することをお勧めします。
nginxlocation /ws {
proxy_pass http://backend_server:8080;
proxy_http_version 1.1;
# WebSocket 用ヘッダー
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# クライアント情報をバックエンドへ伝達
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
これらの追加ヘッダーにより、バックエンドサーバーはクライアントの実際の IP アドレスやホスト名を把握できます。アクセスログの記録やセキュリティ判定に役立つでしょう。
タイムアウト設定の詳細
WebSocket は長時間接続を維持する性質があるため、NGINX のデフォルトのタイムアウト設定では接続が途中で切断されてしまう可能性があります。適切なタイムアウト設定を行うことで、安定した通信を実現しましょう。
主要なタイムアウトパラメータ
NGINX で WebSocket に関連する主なタイムアウトパラメータは以下の 3 つです。
nginxlocation /ws {
proxy_pass http://backend_server:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# バックエンドからの応答待ち時間
proxy_read_timeout 3600s;
# バックエンドへのリクエスト送信タイムアウト
proxy_send_timeout 3600s;
# バックエンドへの接続確立タイムアウト
proxy_connect_timeout 60s;
}
各パラメータの意味と推奨値を詳しく見ていきます。
proxy_read_timeout(最重要)
このパラメータは、バックエンドサーバーから次のデータを受信するまでの最大待ち時間を指定します。WebSocket では、データが送信されない期間が長く続くことがあるため、この値を十分に大きく設定する必要があるのです。
nginx# デフォルト値: 60s
# WebSocket 推奨値: 3600s(1時間)以上
proxy_read_timeout 3600s;
この設定を行わないと、60 秒間データの送受信がない場合に NGINX が接続を切断してしまいます。チャットアプリのように、ユーザーが長時間メッセージを送らない可能性がある場合は、さらに長い値(例: 86400s = 24 時間)を設定することもあるでしょう。
proxy_send_timeout
バックエンドサーバーへのデータ送信が完了するまでのタイムアウトを設定します。proxy_read_timeout
と同様に、長時間の接続を維持するために大きな値を設定します。
nginx# デフォルト値: 60s
# WebSocket 推奨値: proxy_read_timeout と同じ値
proxy_send_timeout 3600s;
通常、proxy_read_timeout
と同じ値を設定することで、送信・受信の両方向で一貫したタイムアウト動作が実現できます。
proxy_connect_timeout
バックエンドサーバーへの初期接続を確立するまでのタイムアウトです。WebSocket の接続確立自体は通常数秒以内に完了するため、デフォルト値(60s)のままでも問題ないケースが多いでしょう。
nginx# デフォルト値: 60s
# WebSocket 推奨値: 60s(デフォルトのまま)
proxy_connect_timeout 60s;
ただし、バックエンドサーバーの応答が遅い場合や、ネットワークレイテンシが高い環境では、この値を調整する必要があるかもしれません。
タイムアウトの関係性
以下の図は、各タイムアウトパラメータがどのタイミングで適用されるかを示しています。
mermaidsequenceDiagram
participant N as NGINX
participant B as バックエンド
Note over N,B: proxy_connect_timeout
N->>B: 接続要求
B->>N: 接続確立
Note over N,B: proxy_send_timeout
N->>B: リクエスト送信
Note over N,B: proxy_read_timeout
B->>N: レスポンス受信
Note over N,B: WebSocket 通信開始<br/>proxy_read_timeout が継続的に適用
N->>B: WebSocket フレーム
B->>N: WebSocket フレーム
N->>B: WebSocket フレーム
B->>N: WebSocket フレーム
WebSocket 接続が確立された後は、proxy_read_timeout
が最も重要なパラメータとなります。データの送受信がない期間でも接続を維持するために、十分に長い値を設定しましょう。
実践的な設定例
これまでに解説した内容を統合して、実際の運用で使える完全な NGINX 設定例を示します。ここでは、TLS/SSL 終端、複数バックエンドへの負荷分散、そして適切なタイムアウト設定を含む構成を紹介しますね。
アップストリーム定義
まず、バックエンドサーバーのプールを定義します。
nginx# http ブロック内に配置
upstream websocket_backend {
# ラウンドロビンで負荷分散(デフォルト)
server 192.168.1.101:8080;
server 192.168.1.102:8080;
server 192.168.1.103:8080;
# 接続の維持設定
keepalive 32;
}
keepalive
ディレクティブは、NGINX からバックエンドサーバーへの接続をキープアライブ状態で維持する数を指定します。これにより、接続の再利用が可能になり、パフォーマンスが向上するのです。
map ディレクティブの定義
nginx# http ブロック内に配置
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
完全な server ブロック設定
nginxserver {
# HTTPS リスニング設定
listen 443 ssl http2;
server_name example.com;
# SSL/TLS 証明書設定
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# WebSocket エンドポイント
location /ws {
# アップストリームへプロキシ
proxy_pass http://websocket_backend;
# HTTP/1.1 を使用
proxy_http_version 1.1;
# WebSocket 用ヘッダー設定
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# クライアント情報をバックエンドへ伝達
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# タイムアウト設定(1時間)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 60s;
}
# 通常の HTTP エンドポイント(参考)
location / {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
# 通常の HTTP 用ヘッダー
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
この設定では、/ws
パスへのアクセスが WebSocket として処理され、それ以外のパスは通常の HTTP として処理されます。
HTTP から HTTPS へのリダイレクト設定
セキュリティのため、HTTP アクセスを HTTPS へリダイレクトする設定も追加しましょう。
nginxserver {
listen 80;
server_name example.com;
# すべてのリクエストを HTTPS へリダイレクト
return 301 https://$server_name$request_uri;
}
設定ファイルの構成例
実際の運用では、設定を複数のファイルに分割すると管理しやすくなります。
nginx# /etc/nginx/nginx.conf(メイン設定ファイル)
http {
# WebSocket 用の map 定義
include /etc/nginx/conf.d/websocket-map.conf;
# アップストリーム定義
include /etc/nginx/conf.d/upstream.conf;
# サーバー設定
include /etc/nginx/sites-enabled/*.conf;
}
nginx# /etc/nginx/conf.d/websocket-map.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
nginx# /etc/nginx/conf.d/upstream.conf
upstream websocket_backend {
server 192.168.1.101:8080;
server 192.168.1.102:8080;
server 192.168.1.103:8080;
keepalive 32;
}
nginx# /etc/nginx/sites-enabled/websocket.conf
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
location /ws {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
このように設定を分割することで、変更が必要な箇所を素早く特定でき、チームでの管理も容易になります。
設定の反映
設定ファイルを編集した後は、構文チェックを行ってから NGINX を再起動します。
bash# 設定ファイルの構文チェック
sudo nginx -t
# 構文に問題がなければ再起動
sudo systemctl reload nginx
nginx -t
コマンドで構文エラーを事前に検出することで、サービスの停止を防げます。問題がなければ reload
でグレースフルに設定を反映させましょう。
HAProxy での WebSocket 終端設定
HAProxy は高性能なロードバランサーとして知られ、TCP/HTTP レベルでの柔軟な負荷分散が可能です。WebSocket のプロキシ終端にも対応しており、NGINX とは異なるアプローチで設定を行います。ここでは HAProxy を使った WebSocket 終端の設定方法を解説しますね。
基本設定:アップグレードヘッダーの構成
HAProxy で WebSocket を扱う際の基本的な考え方は、HTTP リクエストを解析して WebSocket 接続かどうかを判断し、適切なバックエンドへルーティングすることです。
frontend セクションの設定
まず、クライアントからの接続を受け付ける frontend
セクションを定義します。
haproxy# HAProxy 設定ファイル(/etc/haproxy/haproxy.cfg)
frontend http_front
# HTTP/HTTPS リスニング設定
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
# デフォルトバックエンド
default_backend http_back
# WebSocket 接続の判定
acl is_websocket hdr(Upgrade) -i websocket
acl is_websocket_path path_beg /ws
# WebSocket 接続を専用バックエンドへルーティング
use_backend websocket_back if is_websocket is_websocket_path
この設定では、acl
(Access Control List)を使って WebSocket 接続を識別しています。
# | ACL 名 | 条件 | 説明 |
---|---|---|---|
1 | is_websocket | hdr(Upgrade) -i websocket | Upgrade ヘッダーに websocket が含まれる |
2 | is_websocket_path | path_beg /ws | パスが /ws で始まる |
両方の条件を満たす場合に、websocket_back
バックエンドへリクエストをルーティングします。この二重チェックにより、誤って通常の HTTP リクエストを WebSocket バックエンドへ送ることを防げるのです。
backend セクションの設定
次に、WebSocket リクエストを処理する backend
セクションを定義します。
haproxybackend websocket_back
# サーバー負荷分散設定
balance roundrobin
# WebSocket 用のオプション設定
option http-server-close
option forwardfor
# バックエンドサーバーの定義
server ws1 192.168.1.101:8080 check
server ws2 192.168.1.102:8080 check
server ws3 192.168.1.103:8080 check
HAProxy では、WebSocket 通信のために特別なヘッダー設定を明示的に行う必要はありません。Upgrade
と Connection
ヘッダーはデフォルトでバックエンドへ転送されるためです。
option http-server-close の意味
option http-server-close
は、サーバー側の接続をリクエストごとにクローズする設定ですが、WebSocket 接続の場合は自動的に無効化されます。つまり、WebSocket のアップグレードが成功した後は、接続が維持されるのです。
通常の HTTP バックエンド
WebSocket 以外の通常の HTTP リクエスト用のバックエンドも定義しておきましょう。
haproxybackend http_back
balance roundrobin
option http-server-close
option forwardfor
# クライアント情報をヘッダーに追加
http-request set-header X-Forwarded-Proto https if { ssl_fc }
server web1 192.168.1.101:8080 check
server web2 192.168.1.102:8080 check
server web3 192.168.1.103:8080 check
より詳細な WebSocket 判定
より厳密に WebSocket 接続を判定したい場合は、以下のような ACL を追加できます。
haproxyfrontend http_front
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
# WebSocket 判定のための複数条件
acl is_upgrade hdr(Upgrade) -i websocket
acl is_connection hdr(Connection) -i upgrade
acl is_ws_path path_beg /ws
# すべての条件を満たす場合のみ WebSocket として扱う
use_backend websocket_back if is_upgrade is_connection is_ws_path
default_backend http_back
この設定では、Upgrade: websocket
と Connection: Upgrade
の両方のヘッダーが存在することを確認しています。より安全な判定が可能になるでしょう。
タイムアウト設定の詳細
HAProxy でも、WebSocket の長時間接続を維持するために適切なタイムアウト設定が必要です。HAProxy のタイムアウトは NGINX とは異なる概念を持っているため、それぞれの意味を理解しましょう。
グローバルセクションとデフォルトセクション
タイムアウト設定は通常 defaults
セクションに記述します。
haproxydefaults
mode http
# ログ設定
option httplog
# 基本的なタイムアウト設定
timeout connect 10s
timeout client 30s
timeout server 30s
しかし、WebSocket 用には別のタイムアウト値が必要になるため、backend セクションで個別に設定します。
WebSocket バックエンドのタイムアウト設定
haproxybackend websocket_back
balance roundrobin
option http-server-close
option forwardfor
# WebSocket 用タイムアウト設定
timeout tunnel 3600s
timeout server 3600s
timeout client 3600s
server ws1 192.168.1.101:8080 check
server ws2 192.168.1.102:8080 check
server ws3 192.168.1.103:8080 check
HAProxy における主要なタイムアウトパラメータを詳しく見ていきます。
timeout tunnel(最重要)
timeout tunnel
は、WebSocket や CONNECT メソッドなど、双方向通信のトンネル接続に適用される特別なタイムアウトです。WebSocket 接続が確立された後は、このタイムアウトが他のタイムアウト設定を上書きします。
haproxy# デフォルト値: なし(設定されていない場合は timeout client/server が適用)
# WebSocket 推奨値: 3600s(1時間)以上
timeout tunnel 3600s
この設定を省略すると、timeout client
と timeout server
の値が使われますが、明示的に timeout tunnel
を設定することで、WebSocket 専用のタイムアウトを指定できるのです。
timeout client
クライアントが非アクティブな状態(データを送受信しない状態)を許容する最大時間です。WebSocket 接続では、この値も十分に長く設定する必要があります。
haproxy# デフォルト値: 設定必須
# WebSocket 推奨値: timeout tunnel と同じ値
timeout client 3600s
timeout server
バックエンドサーバーが非アクティブな状態を許容する最大時間です。timeout client
と同様に、長時間の接続を維持するために大きな値を設定します。
haproxy# デフォルト値: 設定必須
# WebSocket 推奨値: timeout tunnel と同じ値
timeout server 3600s
timeout connect
バックエンドサーバーへの接続確立を待つ最大時間です。WebSocket の接続確立は通常短時間で完了するため、デフォルト値のままで問題ないでしょう。
haproxy# デフォルト値: 設定必須
# WebSocket 推奨値: 10s(通常の HTTP と同じ)
timeout connect 10s
タイムアウトの優先順位
HAProxy では、WebSocket 接続が確立された後、以下の優先順位でタイムアウトが適用されます。
mermaidflowchart TD
start["WebSocket 接続確立"] --> check_tunnel{"timeout tunnel<br/>設定あり?"}
check_tunnel -->|はい| use_tunnel["timeout tunnel<br/>を適用"]
check_tunnel -->|いいえ| use_cs["timeout client と<br/>timeout server を適用"]
use_tunnel --> end_state["タイムアウト監視"]
use_cs --> end_state
timeout tunnel
を設定している場合、WebSocket 通信中はこの値が優先的に使われます。設定していない場合は、timeout client
と timeout server
の小さい方の値が実質的なタイムアウトとなるのです。
タイムアウトの実践的な設定値
用途に応じた推奨タイムアウト値を以下の表にまとめます。
# | 用途 | timeout tunnel | timeout client | timeout server | timeout connect |
---|---|---|---|---|---|
1 | チャットアプリ | 7200s(2 時間) | 7200s | 7200s | 10s |
2 | リアルタイムダッシュボード | 3600s(1 時間) | 3600s | 3600s | 10s |
3 | オンラインゲーム | 1800s(30 分) | 1800s | 1800s | 10s |
4 | 通知システム | 86400s(24 時間) | 86400s | 86400s | 10s |
チャットアプリやダッシュボードのように、ユーザーが長時間ページを開いたままにする可能性がある場合は、2 時間以上のタイムアウトを設定すると良いでしょう。
実践的な設定例
これまでの内容を統合して、本番環境で使える完全な HAProxy 設定例を示します。SSL/TLS 終端、ヘルスチェック、ログ設定を含む実践的な構成を見ていきましょう。
完全な HAProxy 設定ファイル
haproxy# /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# グローバル設定
#---------------------------------------------------------------------
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
# SSL/TLS 設定
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2
#---------------------------------------------------------------------
# デフォルト設定
#---------------------------------------------------------------------
defaults
log global
mode http
option httplog
option dontlognull
# 基本タイムアウト(WebSocket 以外)
timeout connect 10s
timeout client 30s
timeout server 30s
# エラー時の再試行
retries 3
option redispatch
#---------------------------------------------------------------------
# フロントエンド設定
#---------------------------------------------------------------------
frontend http_front
# HTTP リスニング(HTTPS へリダイレクト)
bind *:80
redirect scheme https code 301 if !{ ssl_fc }
# HTTPS リスニング
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
# WebSocket 接続の判定
acl is_upgrade hdr(Upgrade) -i websocket
acl is_connection hdr(Connection) -i upgrade
acl is_ws_path path_beg /ws
# WebSocket 接続をルーティング
use_backend websocket_back if is_upgrade is_connection is_ws_path
# 通常の HTTP 接続
default_backend http_back
#---------------------------------------------------------------------
# バックエンド設定:WebSocket
#---------------------------------------------------------------------
backend websocket_back
# 負荷分散アルゴリズム
balance roundrobin
# オプション設定
option http-server-close
option forwardfor
# WebSocket 用タイムアウト(1時間)
timeout tunnel 3600s
timeout server 3600s
timeout client 3600s
# クライアント情報をヘッダーに追加
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Proto https if { ssl_fc }
# バックエンドサーバー定義
# check: ヘルスチェック有効
# maxconn 100: 最大100接続まで
server ws1 192.168.1.101:8080 check maxconn 100
server ws2 192.168.1.102:8080 check maxconn 100
server ws3 192.168.1.103:8080 check maxconn 100
#---------------------------------------------------------------------
# バックエンド設定:通常の HTTP
#---------------------------------------------------------------------
backend http_back
balance roundrobin
option http-server-close
option forwardfor
# 通常の HTTP タイムアウト
timeout server 30s
timeout client 30s
# ヘッダー設定
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Proto https if { ssl_fc }
# バックエンドサーバー定義
server web1 192.168.1.101:8080 check
server web2 192.168.1.102:8080 check
server web3 192.168.1.103:8080 check
#---------------------------------------------------------------------
# 統計情報ページ(オプション)
#---------------------------------------------------------------------
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats admin if TRUE
ヘルスチェックの詳細設定
バックエンドサーバーの健全性を監視するために、ヘルスチェックを細かく設定できます。
haproxybackend websocket_back
balance roundrobin
# ヘルスチェックの詳細設定
option httpchk GET /health HTTP/1.1
http-check send hdr Host example.com
# タイムアウト設定
timeout tunnel 3600s
timeout server 3600s
timeout client 3600s
# サーバー定義(詳細なヘルスチェック設定)
# inter 5s: 5秒ごとにチェック
# rise 2: 2回成功で復帰
# fall 3: 3回失敗でダウン判定
server ws1 192.168.1.101:8080 check inter 5s rise 2 fall 3
server ws2 192.168.1.102:8080 check inter 5s rise 2 fall 3
server ws3 192.168.1.103:8080 check inter 5s rise 2 fall 3
この設定では、各バックエンドサーバーの /health
エンドポイントに対して 5 秒ごとにヘルスチェックを実行します。3 回連続で失敗したサーバーはダウンと判定され、2 回連続で成功すれば復帰するのです。
スティッキーセッションの設定(必要に応じて)
一部のアプリケーションでは、同じクライアントからのリクエストを常に同じバックエンドサーバーへ送る必要があります。
haproxybackend websocket_back
balance roundrobin
# Cookie ベースのスティッキーセッション
cookie SERVERID insert indirect nocache
timeout tunnel 3600s
timeout server 3600s
timeout client 3600s
# サーバー定義(cookie オプション追加)
server ws1 192.168.1.101:8080 check cookie ws1
server ws2 192.168.1.102:8080 check cookie ws2
server ws3 192.168.1.103:8080 check cookie ws3
この設定により、HAProxy は各サーバーに対して一意の Cookie を設定し、同じ Cookie を持つクライアントは同じサーバーへルーティングされます。
設定の検証と適用
設定ファイルを編集した後は、構文チェックを行ってから HAProxy を再起動しましょう。
bash# 設定ファイルの構文チェック
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# 構文に問題がなければリロード
sudo systemctl reload haproxy
# ステータス確認
sudo systemctl status haproxy
-c
オプションで構文チェックを実行し、エラーがないことを確認してから reload
でグレースフルに設定を反映させます。これにより、既存の接続を切断することなく新しい設定を適用できるのです。
タイムアウト設定の最適化
NGINX と HAProxy の基本設定を理解したところで、タイムアウト設定をさらに深く掘り下げます。アプリケーションの特性や運用環境に応じて、最適なタイムアウト値を選択することが、安定した WebSocket 通信を実現する鍵となるでしょう。
各種タイムアウトパラメータの解説
WebSocket 通信におけるタイムアウトは、単に「長ければ良い」というものではありません。タイムアウト値が長すぎると、障害発生時の検知が遅れたり、リソースが無駄に保持されたりする問題が生じます。逆に短すぎると、正常な接続が切断されてしまうでしょう。
タイムアウトの種類と役割
NGINX と HAProxy では異なるタイムアウトパラメータが使われますが、それぞれの役割を対応付けて理解しましょう。
# | 役割 | NGINX | HAProxy | 説明 |
---|---|---|---|---|
1 | 接続確立 | proxy_connect_timeout | timeout connect | バックエンドへの接続確立を待つ時間 |
2 | データ受信 | proxy_read_timeout | timeout server | バックエンドからデータを受信するまでの時間 |
3 | データ送信 | proxy_send_timeout | timeout client | バックエンドへデータを送信するまでの時間 |
4 | トンネル通信 | (なし) | timeout tunnel | WebSocket などの双方向通信のタイムアウト |
タイムアウトの相互作用
以下の図は、WebSocket 通信におけるタイムアウトの適用タイミングを示しています。
mermaidstateDiagram-v2
[*] --> Connecting: 接続開始
Connecting --> Connected: 接続確立成功
Connecting --> Failed: タイムアウト<br/>(connect_timeout)
Connected --> Upgrading: プロトコル<br/>アップグレード
Upgrading --> WebSocket: 101 Switching<br/>Protocols
Upgrading --> Failed: タイムアウト<br/>(read_timeout)
WebSocket --> Receiving: データ受信待ち
Receiving --> Sending: データ受信
Sending --> Receiving: データ送信
Receiving --> Failed: タイムアウト<br/>(read_timeout/<br/>tunnel)
Sending --> Failed: タイムアウト<br/>(send_timeout/<br/>tunnel)
Failed --> [*]
接続確立後は、データの送受信がない期間でも read_timeout
(NGINX)または timeout tunnel
(HAProxy)がタイムアウトを監視し続けます。この値が最も重要なパラメータとなるのです。
キープアライブとの関係
多くの WebSocket クライアントライブラリは、接続を維持するために定期的に「Ping/Pong」フレームを送信します。この仕組みを理解することで、適切なタイムアウト値を設定できるでしょう。
mermaidsequenceDiagram
participant C as クライアント
participant P as プロキシ
participant B as バックエンド
Note over C,B: WebSocket 接続確立済み
C->>P: データ送信
P->>B: データ転送
Note over C,B: 30秒間通信なし
C->>P: Ping フレーム
P->>B: Ping フレーム
B->>P: Pong フレーム
P->>C: Pong フレーム
Note over C,B: Ping/Pong により<br/>タイムアウトリセット
Note over C,B: さらに30秒間通信なし
C->>P: Ping フレーム
P->>B: Ping フレーム
Ping/Pong フレームは、実際のアプリケーションデータがなくても接続が生きていることを証明します。プロキシサーバーのタイムアウトは、この Ping/Pong の間隔よりも長く設定する必要があるのです。
例えば、クライアントが 30 秒ごとに Ping を送信する場合、プロキシのタイムアウトは少なくとも 60 秒以上(できれば 90 秒以上)に設定すべきでしょう。これにより、ネットワーク遅延などで Ping が遅れた場合でも接続が切断されません。
タイムアウトとリソース管理
タイムアウト値を長く設定すると、サーバーリソース(メモリ、ファイルディスクリプタ)を長期間保持することになります。同時接続数が多い環境では、これが問題になる可能性があるでしょう。
mermaidflowchart LR
timeout["タイムアウト設定"] --> resource["リソース保持時間"]
resource --> memory["メモリ使用量"]
resource --> fd["ファイル<br/>ディスクリプタ"]
connections["同時接続数"] --> memory
connections --> fd
memory --> capacity["サーバー容量"]
fd --> capacity
同時接続数とタイムアウト値のバランスを考慮して、サーバーのリソース上限を超えないように設計する必要があります。
用途別の推奨値
アプリケーションの特性に応じた、実践的なタイムアウト設定の推奨値を紹介します。これらの値は出発点として使用し、実際のトラフィックパターンやサーバーリソースに応じて調整してください。
チャットアプリケーション
ユーザーが長時間ページを開いたままにする可能性があり、メッセージの送受信頻度は低い場合があります。
nginx# NGINX の場合
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 2時間のタイムアウト
proxy_read_timeout 7200s;
proxy_send_timeout 7200s;
proxy_connect_timeout 10s;
}
haproxy# HAProxy の場合
backend websocket_back
timeout tunnel 7200s
timeout server 7200s
timeout client 7200s
timeout connect 10s
# | パラメータ | 推奨値 | 理由 |
---|---|---|---|
1 | 読み取りタイムアウト | 7200s(2 時間) | ユーザーが長時間メッセージを送らない可能性 |
2 | 送信タイムアウト | 7200s(2 時間) | 読み取りタイムアウトと同じ値で統一 |
3 | 接続タイムアウト | 10s | 通常の接続確立は数秒で完了 |
リアルタイムダッシュボード
データの更新頻度が比較的高く、定期的にサーバーからプッシュ通知が送られます。
nginx# NGINX の場合
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 1時間のタイムアウト
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 10s;
}
haproxy# HAProxy の場合
backend websocket_back
timeout tunnel 3600s
timeout server 3600s
timeout client 3600s
timeout connect 10s
# | パラメータ | 推奨値 | 理由 |
---|---|---|---|
1 | 読み取りタイムアウト | 3600s(1 時間) | 定期的なデータ更新があるため中程度の値 |
2 | 送信タイムアウト | 3600s(1 時間) | 読み取りタイムアウトと同じ値で統一 |
3 | 接続タイムアウト | 10s | 通常の接続確立は数秒で完了 |
オンラインゲーム(リアルタイムマルチプレイ)
頻繁にデータの送受信が発生し、レイテンシが重要な用途です。
nginx# NGINX の場合
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 30分のタイムアウト
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
proxy_connect_timeout 5s;
}
haproxy# HAProxy の場合
backend websocket_back
timeout tunnel 1800s
timeout server 1800s
timeout client 1800s
timeout connect 5s
# | パラメータ | 推奨値 | 理由 |
---|---|---|---|
1 | 読み取りタイムアウト | 1800s(30 分) | 頻繁な通信があるため比較的短い値 |
2 | 送信タイムアウト | 1800s(30 分) | 読み取りタイムアウトと同じ値で統一 |
3 | 接続タイムアウト | 5s | 低レイテンシが要求されるため短く設定 |
IoT デバイス監視システム
デバイスからの定期的なステータス送信があり、非常に長時間の接続維持が必要です。
nginx# NGINX の場合
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 24時間のタイムアウト
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 30s;
}
haproxy# HAProxy の場合
backend websocket_back
timeout tunnel 86400s
timeout server 86400s
timeout client 86400s
timeout connect 30s
# | パラメータ | 推奨値 | 理由 |
---|---|---|---|
1 | 読み取りタイムアウト | 86400s(24 時間) | デバイスが長時間データを送らない可能性 |
2 | 送信タイムアウト | 86400s(24 時間) | 読み取りタイムアウトと同じ値で統一 |
3 | 接続タイムアウト | 30s | IoT デバイスは接続が遅い場合がある |
タイムアウト設定の決定フロー
以下のフローチャートを参考に、用途に応じた適切なタイムアウト値を選択しましょう。
mermaidflowchart TD
start["タイムアウト設定の検討"] --> freq{"データ送受信<br/>の頻度は?"}
freq -->|高頻度<br/>数秒〜数分| short["短いタイムアウト<br/>1800s〜3600s"]
freq -->|中頻度<br/>数分〜数十分| medium["中程度のタイムアウト<br/>3600s〜7200s"]
freq -->|低頻度<br/>数十分以上| long["長いタイムアウト<br/>7200s〜86400s"]
short --> resource{"同時接続数と<br/>リソースは?"}
medium --> resource
long --> resource
resource -->|多い接続数<br/>限られたリソース| reduce["タイムアウトを<br/>短く調整"]
resource -->|適切な範囲| keep["推奨値を<br/>そのまま使用"]
reduce --> monitor["運用監視開始"]
keep --> monitor
monitor --> adjust["必要に応じて<br/>値を調整"]
初期設定では推奨値から始め、実際の運用データ(接続切断の頻度、リソース使用量)を見ながら最適化していくアプローチが効果的です。
トラブルシューティング
WebSocket のプロキシ終端では、設定ミスやネットワーク環境の問題により、さまざまなエラーが発生する可能性があります。ここでは、よくある問題とその解決方法を具体的に解説しますね。
よくあるエラーと解決方法
Error 400: Bad Request
WebSocket の接続リクエストが正しく処理されない場合に発生します。
vbnetError 400: Bad Request
The plain HTTP request was sent to HTTPS port
原因と解決方法
# | 原因 | 解決方法 |
---|---|---|
1 | HTTP/1.1 が使用されていない | NGINX: proxy_http_version 1.1; を設定 |
2 | Upgrade ヘッダーが転送されていない | proxy_set_header Upgrade $http_upgrade; を設定 |
3 | Connection ヘッダーが正しくない | proxy_set_header Connection $connection_upgrade; を設定 |
4 | map ディレクティブが定義されていない | map $http_upgrade $connection_upgrade を追加 |
NGINX の場合、以下の設定が正しく記述されているか確認しましょう。
nginx# http ブロック内
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# location ブロック内
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1; # これが必須
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
Error 101: Switching Protocols が返されない
クライアントが 101 Switching Protocols
レスポンスを受け取れず、接続がアップグレードされません。
デバッグ方法
ブラウザの開発者ツールでネットワークタブを開き、WebSocket 接続リクエストのヘッダーを確認します。
makefile# クライアントのリクエストヘッダー(正常な場合)
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
# サーバーのレスポンスヘッダー(正常な場合)
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
レスポンスが 101
ではなく 200 OK
や 404 Not Found
などの場合、プロキシ設定またはバックエンドサーバーの設定に問題があります。
原因と解決方法
nginx# NGINX の場合:バックエンドへの接続を確認
location /ws {
proxy_pass http://backend; # バックエンドのアドレスが正しいか確認
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# デバッグ用にアクセスログを有効化
access_log /var/log/nginx/websocket_access.log;
error_log /var/log/nginx/websocket_error.log;
}
ログファイルを確認して、バックエンドサーバーへの接続が成功しているか、エラーが発生していないかをチェックしましょう。
Error 502: Bad Gateway
プロキシサーバーがバックエンドサーバーから有効なレスポンスを受け取れない場合に発生します。
javascriptError 502: Bad Gateway
主な原因
- バックエンドサーバーがダウンしている
- バックエンドサーバーへのネットワーク経路に問題がある
- バックエンドサーバーが WebSocket をサポートしていない
- ファイアウォールが WebSocket 通信をブロックしている
解決方法
bash# バックエンドサーバーの稼働確認
curl http://backend_server:8080/health
# ネットワーク経路の確認
ping backend_server
# ポートの疎通確認
telnet backend_server 8080
バックエンドサーバーが正常に応答する場合は、WebSocket のサポート状況を確認します。Node.js の場合、以下のような実装が必要です。
javascript// Node.js + Express + ws ライブラリの例
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
// WebSocket サーバーを作成
const wss = new WebSocket.Server({ server });
// 接続イベントのハンドリング
wss.on('connection', (ws) => {
console.log('WebSocket client connected');
ws.on('message', (message) => {
console.log('Received:', message);
});
});
server.listen(8080, () => {
console.log('Server listening on port 8080');
});
Error 504: Gateway Timeout
プロキシサーバーがバックエンドからのレスポンスを待っている間にタイムアウトが発生します。
javascriptError 504: Gateway Timeout
原因と解決方法
タイムアウト設定が短すぎることが主な原因です。WebSocket 接続では長時間の通信が想定されるため、タイムアウト値を増やしましょう。
nginx# NGINX の場合
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# タイムアウトを長く設定
proxy_read_timeout 3600s; # 1時間
proxy_send_timeout 3600s; # 1時間
proxy_connect_timeout 60s;
}
haproxy# HAProxy の場合
backend websocket_back
timeout tunnel 3600s # 1時間
timeout server 3600s # 1時間
timeout client 3600s # 1時間
timeout connect 60s
接続が切断される場合の対処法
WebSocket 接続が予期せず切断される問題は、タイムアウト設定やネットワーク環境に起因することが多いです。段階的に原因を特定していきましょう。
切断のタイミングを特定する
まず、どのくらいの時間が経過すると切断されるかを測定します。
javascript// クライアント側のデバッグコード
const ws = new WebSocket('wss://example.com/ws');
const startTime = Date.now();
ws.onopen = () => {
console.log(
'WebSocket connected at',
new Date().toISOString()
);
};
ws.onclose = (event) => {
const duration = (Date.now() - startTime) / 1000;
console.log(
`WebSocket disconnected after ${duration} seconds`
);
console.log('Close code:', event.code);
console.log('Close reason:', event.reason);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
切断までの時間が一定(例えば常に 60 秒後)である場合、プロキシサーバーのタイムアウト設定が原因である可能性が高いです。
Close コードによる原因特定
WebSocket の切断理由は、Close コードによって識別できます。
# | Close コード | 意味 | 対処法 |
---|---|---|---|
1 | 1000 | 正常なクローズ | 問題なし(アプリケーションが意図的に切断) |
2 | 1001 | サーバーがダウンまたは移動 | バックエンドサーバーの状態を確認 |
3 | 1006 | 異常なクローズ | プロキシのタイムアウト設定を確認 |
4 | 1009 | メッセージが大きすぎる | メッセージサイズの上限を調整 |
5 | 1011 | サーバー内部エラー | バックエンドのログを確認 |
Close コード 1006 の対処
Close コード 1006
は、TCP 接続が予期せず切断されたことを示します。これは最も一般的なタイムアウト関連の問題です。
nginx# NGINX の場合:タイムアウトを段階的に増やして確認
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# まず60秒→120秒→300秒→3600秒と段階的に増やす
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
タイムアウトを増やしても切断される場合は、ネットワーク経路上の別の機器(ファイアウォール、ロードバランサーなど)がタイムアウトを設定している可能性があります。
キープアライブ(Ping/Pong)の実装
タイムアウトを防ぐ最も確実な方法は、定期的に Ping フレームを送信して接続が生きていることを通知することです。
javascript// クライアント側のキープアライブ実装
const ws = new WebSocket('wss://example.com/ws');
let pingInterval;
ws.onopen = () => {
console.log('WebSocket connected');
// 30秒ごとに Ping を送信
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
console.log('Sent ping');
}
}, 30000);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'pong') {
console.log('Received pong');
} else {
// 通常のメッセージ処理
console.log('Received:', data);
}
};
ws.onclose = () => {
// Ping の送信を停止
clearInterval(pingInterval);
};
サーバー側でも Ping に対する Pong を返す実装が必要です。
javascript// Node.js サーバー側の Ping/Pong 実装
wss.on('connection', (ws) => {
console.log('Client connected');
// 定期的に Ping を送信(WebSocket API の組み込み機能)
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 30000);
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'ping') {
// Ping に対して Pong を返す
ws.send(JSON.stringify({ type: 'pong' }));
} else {
// 通常のメッセージ処理
console.log('Received:', data);
}
});
ws.on('close', () => {
clearInterval(pingInterval);
console.log('Client disconnected');
});
// WebSocket API の Pong イベントハンドリング
ws.on('pong', () => {
console.log('Received pong from client');
});
});
自動再接続の実装
接続が切断された場合に自動的に再接続する仕組みを実装することで、ユーザー体験を向上させられます。
javascript// 自動再接続機能付き WebSocket クラス
class ReconnectingWebSocket {
constructor(url) {
this.url = url;
this.reconnectDelay = 1000; // 初期再接続遅延:1秒
this.maxReconnectDelay = 30000; // 最大再接続遅延:30秒
this.reconnectAttempts = 0;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.reconnectDelay = 1000;
this.onopen?.();
};
this.ws.onmessage = (event) => {
this.onmessage?.(event);
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.scheduleReconnect();
this.onclose?.();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.onerror?.(error);
};
}
scheduleReconnect() {
this.reconnectAttempts++;
const delay = Math.min(
this.reconnectDelay *
Math.pow(2, this.reconnectAttempts),
this.maxReconnectDelay
);
console.log(
`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
);
setTimeout(() => this.connect(), delay);
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
} else {
console.warn(
'WebSocket is not open. Message not sent:',
data
);
}
}
}
// 使用例
const rws = new ReconnectingWebSocket(
'wss://example.com/ws'
);
rws.onopen = () => {
console.log('Connection established');
};
rws.onmessage = (event) => {
console.log('Received:', event.data);
};
rws.send(JSON.stringify({ message: 'Hello' }));
この実装では、接続が切断された場合に指数バックオフ(exponential backoff)アルゴリズムを使って再接続を試みます。最初は 1 秒後、次は 2 秒後、その次は 4 秒後…という具合に遅延を増やしていき、最大 30 秒まで到達したらその間隔で再接続を繰り返すのです。
ログによる診断
プロキシサーバーとバックエンドサーバーの両方でログを有効にし、切断のタイミングでどのようなエラーが記録されるかを確認しましょう。
nginx# NGINX のログ設定
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 詳細なログを記録
access_log /var/log/nginx/websocket_access.log combined;
error_log /var/log/nginx/websocket_error.log debug;
}
ログには、接続時間、転送データ量、エラーメッセージなどが記録されます。これらの情報から切断の原因を特定できるでしょう。
まとめ
WebSocket を NGINX や HAProxy で終端するには、アップグレードヘッダーの適切な設定とタイムアウトの調整が不可欠です。本記事で解説した内容を振り返りましょう。
重要なポイント
-
アップグレードヘッダーの必須設定
- HTTP から WebSocket へのプロトコル切り替えには
Upgrade
とConnection
ヘッダーの転送が必要 - NGINX では
map
ディレクティブとproxy_set_header
を使用 - HAProxy では ACL を使った判定とルーティング
- HTTP から WebSocket へのプロトコル切り替えには
-
HTTP/1.1 の使用
- WebSocket は HTTP/1.1 以降が必須
- NGINX では
proxy_http_version 1.1
を明示的に設定
-
タイムアウトの調整
- WebSocket は長時間接続を維持するため、デフォルトのタイムアウトでは不十分
- 用途に応じて 1800s〜86400s の範囲で設定
- NGINX:
proxy_read_timeout
とproxy_send_timeout
- HAProxy:
timeout tunnel
、timeout server
、timeout client
-
キープアライブの実装
- 定期的な Ping/Pong フレームでタイムアウトを防ぐ
- クライアント・サーバー双方で実装することが理想的
-
自動再接続
- 接続切断時の自動再接続により、ユーザー体験を向上
- 指数バックオフアルゴリズムで過負荷を防ぐ
設定のチェックリスト
WebSocket プロキシを設定する際は、以下の項目を確認しましょう。
# | 項目 | NGINX | HAProxy |
---|---|---|---|
1 | HTTP/1.1 の使用 | ✓ proxy_http_version 1.1 | ✓ デフォルトで有効 |
2 | Upgrade ヘッダー転送 | ✓ proxy_set_header Upgrade | ✓ デフォルトで転送 |
3 | Connection ヘッダー設定 | ✓ map + proxy_set_header | ✓ デフォルトで転送 |
4 | 読み取りタイムアウト | ✓ proxy_read_timeout | ✓ timeout server |
5 | 送信タイムアウト | ✓ proxy_send_timeout | ✓ timeout client |
6 | トンネルタイムアウト | - | ✓ timeout tunnel |
7 | SSL/TLS 終端 | ✓ ssl_certificate | ✓ bind *:443 ssl crt |
8 | 負荷分散 | ✓ upstream | ✓ backend + balance |
9 | ヘルスチェック | ✓ upstream + check | ✓ server + check |
トラブルシューティングの手順
問題が発生した場合は、以下の順序で確認していきます。
- ブラウザの開発者ツールでリクエスト/レスポンスヘッダーを確認
- プロキシサーバーのログを確認(
error.log
) - バックエンドサーバーの動作確認(直接接続テスト)
- タイムアウト設定を段階的に増やして検証
- Ping/Pong による キープアライブの実装
本記事で紹介した設定例は、多くの WebSocket アプリケーションで使用できる汎用的なものです。しかし、実際の運用では、トラフィックパターン、同時接続数、サーバーリソースなどの要因に応じて調整が必要になるでしょう。初期設定をベースとして、ログやモニタリングデータを参考に最適化を進めていくことをお勧めします。
WebSocket のプロキシ終端は、適切に設定すれば非常に安定した双方向通信を実現できます。本記事が、皆さんの WebSocket アプリケーションの構築に役立てば幸いです。
関連リンク
- article
WebSocket を NGINX/HAProxy で終端する設定例:アップグレードヘッダーとタイムアウト完全ガイド
- article
WebSocket vs WebTransport vs SSE 徹底比較:遅延・帯域・安定性を実測レビュー
- article
WebSocket 導入判断ガイド:SSE・WebTransport・長輪講ポーリングとの適材適所を徹底解説
- article
WebSocket 技術の全体設計図:フレーム構造・サブプロトコル・拡張の要点を一気に理解
- article
WebSocket と HTTP/2・HTTP/3 の違いを徹底比較
- article
WebSocket の仕組みを図解で理解する
- article
【2025 年 10 月版】 Claude Sonnet 4.5 登場! Claude Pro でも使える!Claude Code のアップデート手順まで紹介
- article
Turborepo で Zustand スライスをパッケージ化:Monorepo 運用の初期設定
- article
Nuxt を macOS + yarn で最短構築:ESLint/Prettier/TS 設定まで一気通貫
- article
キャッシュ比較:WordPress で WP Rocket/LiteSpeed/W3TC を検証
- article
Nginx を macOS で本番級に構築:launchd/ログローテーション/権限・署名のベストプラクティス
- article
WebSocket を NGINX/HAProxy で終端する設定例:アップグレードヘッダーとタイムアウト完全ガイド
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来