Solid Socket.IO + nginx + Redis outline. The ops failure mode we still hit is idle disconnects at exactly sixty seconds: that is almost always proxy_read_timeout / proxy_send_timeout staying at the default while the client thinks the socket is alive. Raise both well above your heartbeat interval, keep proxy_http_version 1.1, and forward Upgrade plus a mapped Connection so the 101 handshake actually sticks. Separate the WebSocket location from normal HTTP so you do not clear Connection for keepalive on the same block that must send Connection upgrade.
Across machines, Redis adapter alone is not enough if nginx fans long-polling handshake steps across replicas without affinity. Prefer ip_hash or a sticky cookie only for the socket path, put each PM2 host once in upstream (not every worker port), and after deploy watch for reconnect storms plus 502s right when a node drains. If sockets die only behind the proxy but survive direct-to-Node, diff the timeout and Upgrade headers first before blaming Redis.