WebSockets provide a persistent, full-duplex communication channel over a single TCP connection, making them essential for real-time applications such as chat systems, live dashboards, and collaborative tools. Nginx can act as a reverse proxy that transparently upgrades incoming HTTP connections to the WebSocket protocol and forwards them to a backend application server. This tutorial walks through configuring Nginx on RHEL 8 to proxy WebSocket traffic, handling connection upgrades, timeouts, and secure wss:// connections.
Prerequisites
- RHEL 8 server with Nginx installed and running
- A backend application listening on a local port that speaks WebSocket (e.g., a Node.js app on port 3000)
- Root or sudo access
firewalldconfigured to allow the relevant ports- Optional:
wscator a browser WebSocket client for testing
Step 1 — Install Nginx
If Nginx is not yet installed, add it from the RHEL 8 AppStream repository and enable the service.
sudo dnf install -y nginx
sudo systemctl enable --now nginx
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Step 2 — Configure the WebSocket Proxy Block
Create a dedicated Nginx server block that upgrades incoming HTTP connections to WebSocket and proxies them to the backend. The critical headers are Upgrade and Connection; without them Nginx will not forward the upgrade handshake.
sudo tee /etc/nginx/conf.d/websocket.conf > /dev/null <<'EOF'
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_cache_bypass $http_upgrade;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx
The map block sets the Connection header to upgrade when the client sends an Upgrade header, and to close otherwise, correctly handling both WebSocket and regular HTTP requests on the same server block.
Step 3 — Set Appropriate Timeouts
WebSocket connections are long-lived. The default Nginx proxy read timeout of 60 seconds will silently close idle connections. Increase the timeout to keep connections alive during quiet periods.
sudo tee -a /etc/nginx/conf.d/websocket.conf > /dev/null <<'EOF'
# Add inside the location /ws/ block above, then reload
# proxy_read_timeout 3600s;
# proxy_send_timeout 3600s;
# keepalive_timeout 3600s;
EOF
# Edit the file to insert the timeout directives inside location /ws/
sudo sed -i '/proxy_cache_bypass/a proxy_read_timeout 3600s;n proxy_send_timeout 3600s;'
/etc/nginx/conf.d/websocket.conf
sudo nginx -t && sudo systemctl reload nginx
3600 seconds (one hour) is a sensible upper bound. Adjust to match your application’s heartbeat interval.
Step 4 — Test the WebSocket Connection
Install wscat (a Node.js command-line WebSocket client) and verify the proxy is forwarding connections to the backend.
sudo dnf install -y nodejs npm
sudo npm install -g wscat
# Connect through Nginx to the backend WebSocket server
wscat -c ws://ws.example.com/ws/
# You should see:
# Connected (press CTRL+C to quit)
# > type a message and hit Enter to test bidirectional comms
Step 5 — Configure Secure WebSocket (wss://)
For production, WebSocket traffic should run over TLS using wss://. Add an SSL listener to the server block, referencing your certificate and key.
sudo tee /etc/nginx/conf.d/websocket-ssl.conf > /dev/null <<'EOF'
server {
listen 443 ssl;
server_name ws.example.com;
ssl_certificate /etc/ssl/myserver/server.crt;
ssl_certificate_key /etc/ssl/myserver/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
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_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx
# Test the secure endpoint
wscat -c wss://ws.example.com/ws/ --no-check
Step 6 — Check SELinux and Firewall
If Nginx cannot connect to the backend port, SELinux may be blocking the outbound connection. Set the appropriate boolean and confirm the backend port is not blocked.
sudo setsebool -P httpd_can_network_connect 1
# If your backend uses a non-standard port, label it for Nginx
sudo semanage port -a -t http_port_t -p tcp 3000
# Verify Nginx can reach the backend
curl -s http://127.0.0.1:3000/
Conclusion
Nginx on RHEL 8 is now configured to proxy WebSocket connections by upgrading the HTTP protocol, passing the correct headers, and extending proxy timeouts to accommodate long-lived connections. Both plain ws:// and encrypted wss:// endpoints are supported using the same location block pattern. SELinux booleans and firewalld have been adjusted to allow Nginx to reach the backend application.
Next steps: How to Harden Nginx: Security Headers, TLS 1.3 and OCSP Stapling on RHEL 8, How to Configure SSL/TLS with OpenSSL and Self-Signed Certificates on RHEL 8, and How to Monitor Nginx with Prometheus nginx-exporter on RHEL 8.