Proxying WebSocket connections involves configuring an intermediary server to correctly handle the HTTP Upgrade handshake and maintain the persistent, full-duplex TCP connection required for WebSocket communication. This setup allows for features like load balancing, SSL termination, caching, and access control for real-time applications.
Understanding WebSocket Proxying
WebSockets establish a persistent connection between a client and a server over a single TCP connection. This differs from traditional HTTP, which typically uses short-lived request/response cycles. The transition from HTTP to WebSocket is initiated via an "Upgrade" mechanism in the HTTP/1.1 protocol.
The client sends an initial HTTP GET request with specific headers:
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Key: [base64-encoded nonce]
* Sec-WebSocket-Version: 13
If the server supports WebSockets, it responds with an HTTP 101 Switching Protocols status, confirming the upgrade:
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: [response key derived from client's key]
After this handshake, the connection is no longer HTTP but a raw TCP socket, over which WebSocket frames are exchanged. A proxy server must be configured to correctly forward these Upgrade and Connection headers and then maintain the long-lived TCP connection without buffering or closing it prematurely.
Setup for Common Proxy Servers
Nginx
Nginx can proxy WebSocket connections effectively by handling the HTTP Upgrade handshake. The key is to pass the Upgrade and Connection headers from the client to the backend server and ensure HTTP/1.1 is used.
http {
upstream websocket_backend {
server backend_server_1:8080;
server backend_server_2:8080;
}
server {
listen 80;
server_name yourdomain.com;
location /ws/ {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host; # Important for backend if it relies on host header
proxy_read_timeout 86400s; # Adjust as needed for long-lived connections
proxy_send_timeout 86400s;
proxy_buffering off; # Disable buffering for real-time data
}
# Other HTTP locations
location / {
proxy_pass http://other_http_backend;
# ... other HTTP proxy settings
}
}
}
proxy_http_version 1.1;: Essential for theUpgradeheader mechanism.proxy_set_header Upgrade $http_upgrade;: Forwards the client'sUpgradeheader.proxy_set_header Connection "upgrade";: Forwards the client'sConnectionheader. Nginx automatically handles the transformation ofConnection: upgradetoConnection: keep-alivefor HTTP/1.1 connections if not explicitly set to "upgrade". Setting it explicitly ensures correct behavior for WebSockets.proxy_read_timeout/proxy_send_timeout: Increase these from default values to prevent Nginx from closing long-lived WebSocket connections due to inactivity.proxy_buffering off;: Prevents Nginx from buffering responses, which is critical for real-time WebSocket communication to ensure minimal latency.
Apache HTTP Server
Apache can proxy WebSocket connections using mod_proxy_wstunnel, which is part of mod_proxy. Ensure mod_proxy, mod_proxy_http, and mod_proxy_wstunnel are enabled.
<VirtualHost *:80>
ServerName yourdomain.com
# Enable proxy modules
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
# Proxy WebSocket connections
<Location /ws/>
ProxyPass ws://backend_server:8080/ws/
ProxyPassReverse ws://backend_server:8080/ws/
</Location>
# Proxy secure WebSocket connections (WSS)
<Location /wss/>
ProxyPass wss://backend_server:8443/wss/
ProxyPassReverse wss://backend_server:8443/wss/
</Location>
# Other HTTP locations
<Location />
ProxyPass http://other_http_backend/
ProxyPassReverse http://other_http_backend/
</Location>
</VirtualHost>
ProxyPass ws://...: Thews://schema tellsmod_proxy_wstunnelto handle the WebSocket upgrade. For secure WebSockets, usewss://.ProxyPassReverse: Ensures correct URL rewriting in response headers.- Apache's
mod_proxyhandles theUpgradeandConnectionheaders automatically whenws://orwss://is specified.
HAProxy
HAProxy supports WebSocket proxying since version 1.5. It can operate in http mode to inspect headers or tcp mode for raw forwarding. For WebSockets, it's common to use http mode with specific rules to detect the upgrade request.
global
log /dev/log local0 info
maxconn 4000
user haproxy
group haproxy
daemon
defaults
mode http
log global
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms # Increased for client-side WebSocket connections
timeout server 50000ms # Increased for backend-side WebSocket connections
frontend http_frontend
bind *:80
acl is_websocket hdr(Upgrade) -i websocket
use_backend ws_backend if is_websocket
default_backend http_backend
backend ws_backend
mode http
# This option is crucial for WebSockets in HTTP mode; it tells HAProxy to
# switch to TCP tunneling after the HTTP upgrade.
option http-tunnel
server backend_ws_1 backend_server_1:8080 check
server backend_ws_2 backend_server_2:8080 check
backend http_backend
mode http
server backend_http_1 backend_server_3:80 check
acl is_websocket hdr(Upgrade) -i websocket: Defines an Access Control List (ACL) that matches requests containing anUpgrade: websocketheader (case-insensitive).use_backend ws_backend if is_websocket: Directs WebSocket upgrade requests to thews_backend.option http-tunnel: This is critical. When HAProxy detects anUpgradeheader and this option is set, it switches from HTTP parsing mode to a raw TCP tunnel after the initial HTTP handshake. This allows the WebSocket protocol to operate unimpeded.timeout client/timeout server: These timeouts should be increased significantly for WebSocket backends to prevent HAProxy from closing long-lived connections.
Troubleshooting Common Issues
Connection Dropping or Timeout
- Proxy Timeouts: The most common cause. Ensure
proxy_read_timeout,proxy_send_timeout(Nginx),timeout client,timeout server(HAProxy), or equivalent settings in other proxies are sufficiently high (e.g., 24 hours or more) for WebSocket connections. - Idle Connections: Some proxies or network devices might aggressively close idle TCP connections. If the WebSocket protocol has a keep-alive mechanism (e.g., ping/pong frames), ensure it's configured on both client and server to send periodic pings.
- Load Balancer Inactivity: If a load balancer sits in front of the proxy, it might also have its own inactivity timeouts. Check these settings.
HTTP 400 Bad Request (Upgrade Header Missing or Incorrect)
- Client Request: Verify the client is correctly sending
Upgrade: websocketandConnection: Upgradeheaders. - Proxy Configuration: Ensure the proxy is configured to forward these headers to the backend.
- Nginx: Check
proxy_set_header Upgrade $http_upgrade;andproxy_set_header Connection "upgrade";. - Apache: Ensure
mod_proxy_wstunnelis enabled andProxyPassusesws://orwss://. - HAProxy: Confirm the
acl is_websocketcorrectly identifies theUpgradeheader anduse_backenddirects it to a backend withoption http-tunnel.
- Nginx: Check
HTTP 502 Bad Gateway
- Backend Server Issues: The proxy received an invalid response from the backend.
- Is the backend WebSocket server running and listening on the expected port?
- Is the backend correctly configured to handle WebSocket upgrades?
- Check backend server logs for errors.
- Network Connectivity: Verify the proxy can reach the backend server on the specified port.
- Proxy Buffering: While
proxy_buffering offis generally good for WebSockets, excessive data or misconfigurations can sometimes lead to 502s if the proxy struggles to handle the stream.
Security Considerations (SSL/TLS Termination)
- WSS (Secure WebSockets): For secure WebSocket connections (wss://), the proxy must handle SSL/TLS termination.
- The proxy will listen on port 443 (or another secure port), perform the TLS handshake, and then proxy the decrypted WebSocket traffic to the backend (often over plain
ws://orhttp://internally). - Example Nginx for WSS:
```nginx
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/nginx/ssl/yourdomain.crt;
ssl_certificate_key /etc/nginx/ssl/yourdomain.key;location /ws/ { proxy_pass http://websocket_backend; # Backend might be plain HTTP proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400s; proxy_send_timeout 86400s; proxy_buffering off; }}
`` * **Origin Headers:** WebSocket clients send anOrigin` header. Backend servers often validate this header for security reasons. Ensure your proxy is not stripping or altering this header if the backend relies on it.
- The proxy will listen on port 443 (or another secure port), perform the TLS handshake, and then proxy the decrypted WebSocket traffic to the backend (often over plain
Proxy Buffer Limits
- Nginx:
proxy_buffering off;is crucial. If not set, Nginx might buffer large WebSocket messages, introducing latency or causing timeouts. - HAProxy:
option http-tunnelhandles this by switching to raw TCP.
Comparison of Proxy Servers for WebSockets
| Feature | Nginx | Apache HTTP Server (mod_proxy_wstunnel) | HAProxy |
|---|---|---|---|
| Configuration | Manual header forwarding | Specific ws:// / wss:// directives |
ACLs and option http-tunnel |
| Complexity | Moderate | Moderate | Moderate to High (for advanced features) |
| Performance | High (Event-driven) | Moderate (Process/Thread-based) | High (Event-driven) |
| SSL/TLS Termination | Excellent | Good | Excellent |
| Load Balancing | Round-robin, IP hash, least_conn | Round-robin | Extensive (many algorithms, health checks) |
| Sticky Sessions | IP hash (for basic), third-party modules | Limited | Cookie-based, source IP |
| Buffering Control | proxy_buffering off |
Handled by mod_proxy_wstunnel |
option http-tunnel disables HTTP buffering |
Key Considerations
- Sticky Sessions: For some WebSocket applications, a client might need to remain connected to the same backend server throughout its session (e.g., if server-side state is maintained).
- Nginx: Use
ip_hashin theupstreamblock. - HAProxy: Use
balance sourceorcookiedirectives. - Ensure your backend application is designed for horizontal scaling without requiring sticky sessions if possible, as it simplifies load balancing.
- Nginx: Use
- Health Checks: Configure robust health checks for your backend WebSocket servers within the proxy. This ensures that only healthy servers receive WebSocket upgrade requests.
- Logging: Configure comprehensive logging on the proxy and backend. WebSocket connection issues can be transient, and detailed logs are essential for diagnosis.
- HTTP/2 and WebSockets: While WebSockets typically use HTTP/1.1 for the upgrade handshake, they can coexist with HTTP/2. However, the WebSocket protocol itself does not run over HTTP/2 frames directly. Proxies handling HTTP/2 from clients will typically downgrade to HTTP/1.1 for the WebSocket upgrade to the backend.