Live chat, a kitchen order screen or an online game must show new data instantly, without reloading the page, and WebSocket is the usual choice. It opens a two-way channel between browser and server once and keeps it open. The catch is that a WebSocket app that worked perfectly on a developer's laptop often refuses to connect at all after being uploaded to shared hosting. Here is why, and where to run it instead.
How WebSocket differs from a normal request
A normal site works request by request: the browser asks, PHP runs, the response goes back and the connection closes. A WebSocket starts as an HTTP request with an Upgrade: websocket header; if the server agrees it answers 101 Switching Protocols and the TCP connection stays open, so either side can send messages at any time. That requires a long-running process, usually in Node.js, Python or Go, that listens on its own port and holds thousands of open connections in memory.
Why shared hosting cannot do it
On shared hosting PHP scripts start per request and must finish within a time limit. Customers normally cannot open their own public port, long-running background processes may be stopped by resource limits, and the web server configuration is not under the customer's control, so you cannot add a proxy rule that forwards the Upgrade header. PHP WebSocket libraries such as Ratchet hit the same wall because they also need a persistent process and an open port.
Staying on shared hosting: alternatives
Often you do not really need WebSocket. A school timetable or an order status page can simply poll a small JSON endpoint every 5 to 10 seconds; each request is short and runs like any other PHP script. long polling and Server-Sent Events keep a PHP process busy per visitor, so a few dozen users can exhaust the process limit. Short polling is the safe option on shared hosting; real WebSocket belongs on a separate server.
Example: a restaurant kitchen screen
A Tashkent restaurant wants orders entered on waiters' tablets to appear on the kitchen screen immediately. The main website can stay on shared hosting while a small VPS runs the WebSocket server. This minimal Node.js server with the ws package broadcasts each order to all screens and drops dead connections:
// server.js (npm install ws)
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ host: '127.0.0.1', port: 8080 });
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', () => { ws.isAlive = true; });
ws.on('message', (data) => {
// broadcast the new order to every connected screen
for (const client of wss.clients) {
if (client.readyState === 1) client.send(data.toString());
}
});
});
// heartbeat every 30 seconds
setInterval(() => {
for (const ws of wss.clients) {
if (!ws.isAlive) { ws.terminate(); continue; }
ws.isAlive = false;
ws.ping();
}
}, 30000);
It listens only on 127.0.0.1; Nginx accepts outside connections, handles TLS and forwards them inside.
Setting up wss:// with Nginx
An HTTPS page cannot open plain ws:// because browsers block it as mixed content, so you need wss://. Keep the Let's Encrypt certificate in Nginx and proxy /ws/ to Node.js, passing the Upgrade and Connection headers over HTTP/1.1:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name misol.uz;
# ssl_certificate ... (Let's Encrypt certificate)
location /ws/ {
proxy_pass http://127.0.0.1:8080;
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;
}
}
By default Nginx closes a proxied connection after 60 seconds without data; the 30-second ping keeps traffic flowing and the longer timeout adds headroom. In the browser:
const sock = new WebSocket('wss://misol.uz/ws/');
sock.onmessage = (e) => showOrder(JSON.parse(e.data));
sock.onclose = () => setTimeout(() => location.reload(), 3000);
Testing and keeping the server running
A first response line of HTTP/1.1 101 Switching Protocols means both Nginx and Node.js are fine. A 400 or 502 usually means the Node.js process is down or the headers are missing.
curl -i -N \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
https://misol.uz/ws/
Do not leave Node.js running in an SSH terminal; make it a systemd service so it restarts after crashes and reboots.
# /etc/systemd/system/ws-kitchen.service
[Service]
WorkingDirectory=/opt/ws-kitchen
ExecStart=/usr/bin/node server.js
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now ws-kitchen
sudo nginx -t && sudo systemctl reload nginx
Scaling beyond one process
One Node.js process handles a moderate load. With several processes or servers, a client connected to one process will not see messages arriving at another, so processes need a shared channel, commonly Redis pub/sub. You can order a VPS from the sayt.uz cabinet and move only the real-time part there while the main site stays on hosting.