How to Configure Nginx WebSocket Proxying on RHEL 7
WebSockets provide a full-duplex communication channel over a single TCP connection, enabling real-time features such as live chat, push notifications, collaborative editing, and live dashboards. Unlike traditional HTTP requests, a WebSocket connection is established through an HTTP upgrade handshake and then kept open indefinitely. Nginx can act as a reverse proxy for WebSocket servers — but doing so correctly requires specific configuration: the Upgrade and Connection headers must be forwarded, HTTP/1.1 must be enforced for the upstream connection, and appropriate timeouts must be set to keep long-lived connections alive. This tutorial covers everything you need to proxy WebSocket traffic through Nginx on RHEL 7, including SELinux considerations and testing with wscat or websocat.
Prerequisites
- RHEL 7 server with root or sudo access.
- Nginx installed and running (install via EPEL:
sudo yum -y install nginx). - A WebSocket application server listening on a local port (e.g., a Node.js
wsserver on port 3000). - Basic understanding of Nginx server block configuration.
- Node.js installed if you want to run the example WebSocket server for testing.
Step 1: Install Nginx on RHEL 7
Nginx is available via EPEL on RHEL 7. If not already installed:
sudo yum -y install epel-release
sudo yum -y install nginx
sudo systemctl enable nginx
sudo systemctl start nginx
Verify Nginx is running:
sudo systemctl status nginx
curl -I http://localhost
Step 2: Understand WebSocket Upgrade Headers
A WebSocket connection starts as a standard HTTP/1.1 request with two critical headers:
Upgrade: websocket— tells the server that the client wants to switch protocols.Connection: Upgrade— instructs Nginx (and any intermediate proxy) to forward the Upgrade header rather than consuming it.
By default, Nginx strips hop-by-hop headers (including Upgrade and Connection) before forwarding requests upstream. You must explicitly re-add them. Additionally, WebSockets require HTTP/1.1 — Nginx’s default upstream protocol is HTTP/1.0, which does not support the Upgrade mechanism, so you must override this with proxy_http_version 1.1.
Step 3: Configure Nginx for WebSocket Proxying
Open or create the Nginx configuration file for your site. A common location on RHEL 7 is /etc/nginx/conf.d/websocket.conf:
sudo vi /etc/nginx/conf.d/websocket.conf
Add the following configuration:
server {
listen 80;
server_name ws.example.com;
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header 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_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 60s;
}
}
Key directives explained:
proxy_http_version 1.1: Forces Nginx to use HTTP/1.1 when communicating with the upstream, which is required for the Upgrade handshake.proxy_set_header Upgrade $http_upgrade: Passes the client’sUpgrade: websocketheader through to the backend.proxy_set_header Connection "upgrade": Overrides the defaultConnection: closethat Nginx would otherwise send.proxy_read_timeout 86400s: Keeps the connection alive for up to 24 hours without data. Without this, Nginx will close idle WebSocket connections after the default 60-second read timeout.
Step 4: Use a map Block for Dynamic Upgrade Handling
For more robust handling, especially when your Nginx proxy serves both regular HTTP and WebSocket traffic on the same upstream, use a map block to dynamically set the Connection header. This avoids sending Connection: upgrade for non-WebSocket requests, which would be invalid.
Add this block inside the http {} context in /etc/nginx/nginx.conf (or in a separate include file):
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ws.example.com;
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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_read_timeout 86400s;
proxy_send_timeout 86400s;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
}
The map block evaluates $http_upgrade: if the client sends an Upgrade header (WebSocket), $connection_upgrade becomes upgrade; if not, it becomes close. This is the pattern recommended in the official Nginx documentation.
Step 5: Configure Timeout Settings
WebSocket connections are long-lived by nature. Without proper timeout configuration, Nginx will terminate them prematurely. The three relevant timeout directives are:
proxy_read_timeout 86400s; # How long to wait for data from upstream (24h)
proxy_send_timeout 86400s; # How long to wait when sending data to upstream (24h)
proxy_connect_timeout 60s; # How long to wait when establishing the upstream TCP connection
If your application sends periodic pings (heartbeats) to keep the connection alive, you may be able to use a shorter proxy_read_timeout — just make sure it exceeds the interval between heartbeats. A common pattern is to send a ping every 30 seconds and set the timeout to 120 seconds.
Step 6: Configure SELinux Booleans for Nginx Proxying
On RHEL 7 with SELinux in enforcing mode, Nginx is blocked from making outbound network connections by default. This will cause WebSocket proxy connections to fail silently. Enable the necessary boolean:
sudo setsebool -P httpd_can_network_connect 1
Verify the boolean is set:
getsebool httpd_can_network_connect
Expected output: httpd_can_network_connect --> on
If your WebSocket backend listens on a non-standard port, you may also need to allow that port:
sudo semanage port -a -t http_port_t -p tcp 3000
Verify the port is now labeled correctly:
sudo semanage port -l | grep http_port_t
Step 7: Test the Nginx Configuration and Reload
sudo nginx -t
sudo systemctl reload nginx
If nginx -t returns errors, review the output carefully — common issues include mismatched braces, incorrect directive placement, or the map block being inside a server block instead of the http block.
Step 8: Set Up a Simple WebSocket Test Server
To verify proxying is working end-to-end, create a minimal Node.js WebSocket echo server. First install Node.js:
sudo yum -y install nodejs npm
Install the ws package:
mkdir -p ~/wstest && cd ~/wstest
npm init -y
npm install ws
Create server.js:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });
wss.on('connection', function connection(ws) {
console.log('Client connected');
ws.on('message', function incoming(message) {
console.log('Received: %s', message);
ws.send('Echo: ' + message);
});
ws.on('close', function() {
console.log('Client disconnected');
});
});
console.log('WebSocket server listening on port 3000');
Start the server:
node server.js &
Step 9: Test with wscat or websocat
Install wscat globally via npm:
sudo npm install -g wscat
Connect to your WebSocket endpoint through Nginx:
wscat -c ws://ws.example.com/ws/
Type a message and press Enter. The echo server should respond with Echo: your message. If you prefer a statically compiled binary, websocat is an alternative:
# Download websocat binary for Linux x86_64
curl -L https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl
-o /usr/local/bin/websocat
chmod +x /usr/local/bin/websocat
# Test the connection
websocat ws://ws.example.com/ws/
You can also verify the WebSocket upgrade handshake using curl with verbose output:
curl -i -N
-H "Connection: Upgrade"
-H "Upgrade: websocket"
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ=="
-H "Sec-WebSocket-Version: 13"
http://ws.example.com/ws/
A successful upgrade returns HTTP 101 Switching Protocols.
Conclusion
Nginx WebSocket proxying on RHEL 7 requires three key elements: proxy_http_version 1.1 to support the upgrade handshake, explicit forwarding of the Upgrade and Connection headers, and sufficiently long timeout values to prevent Nginx from closing idle long-lived connections. The map block pattern is the cleanest approach for mixed HTTP/WebSocket traffic because it dynamically sets the Connection header based on whether an upgrade is being requested. Remember to enable the httpd_can_network_connect SELinux boolean, which is otherwise the most common reason WebSocket proxying silently fails on RHEL 7.