Proxy de WebSocket com Nginx

WebSocket exige configuração especial no Nginx, diferente de HTTP, o protocolo é persistente e requer o mecanismo de upgrade de conexão. Sem a configuração correta, o Nginx fecha a conexão WebSocket após um timeout curto ou retorna 502. Com os headers de upgrade e os timeouts corretos, o proxy funciona transparentemente.

Configuração básica de proxy WebSocket

Headers essenciais para o upgrade de protocolo HTTP → WebSocket:

nginx
# /etc/nginx/conf.d/websocket.conf

# Map para suportar WebSocket upgrade
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl http2;
    server_name api.seudominio.com.br;

    ssl_certificate     /etc/letsencrypt/live/api.seudominio.com.br/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.seudominio.com.br/privkey.pem;

    # Proxy para API REST normal
    location /api/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Proxy para WebSocket
    location /ws/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Headers de upgrade — OBRIGATÓRIOS para WebSocket
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Headers padrão
        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_set_header X-Forwarded-Proto $scheme;

        # Timeouts para conexões persistentes
        proxy_read_timeout 3600s;    # 1 hora de timeout de inatividade
        proxy_send_timeout 3600s;
    }
}

Socket.IO com Nginx

Socket.IO tenta WebSocket primeiro e faz fallback para long-polling, configuração específica:

nginx
# Socket.IO usa o path /socket.io/ por padrão
# e precisa suportar tanto HTTP (polling) quanto WebSocket

server {
    listen 443 ssl http2;
    server_name chat.seudominio.com.br;

    # Proxy para Socket.IO (websocket + long-polling)
    location /socket.io/ {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;

        # Upgrade para WebSocket
        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_set_header X-Forwarded-Proto $scheme;

        # Sem buffering para long-polling funcionar
        proxy_buffering off;

        # Timeout longo para WebSocket
        proxy_read_timeout 86400s;   # 24 horas

        # Cache off para SSE/polling
        proxy_cache off;
    }

    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_set_header Host $host;
    }
}

Load balancing de WebSocket com sticky sessions

WebSocket precisa de sticky sessions no load balancer, a mesma conexão deve ir sempre ao mesmo backend:

nginx
# Para WebSocket com múltiplos backends:
# Conexões WebSocket são persistentes — uma vez estabelecida,
# deve continuar no mesmo backend pelo ciclo de vida da conexão

upstream ws_cluster {
    # IP hash: garante que o mesmo IP vai ao mesmo servidor
    ip_hash;

    server 10.0.0.10:3000;
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;

    keepalive 100;    # manter conexões abertas entre Nginx e backends
}

server {
    listen 443 ssl http2;
    server_name ws.seudominio.com.br;

    location / {
        proxy_pass http://ws_cluster;
        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;
    }
}

Configurar Server-Sent Events (SSE)

SSE é mais simples que WebSocket para streaming unidirecional e precisa de configuração específica:

nginx
# SSE usa HTTP regular mas com resposta infinita
# Requer desabilitar buffering e keepalive com timeout longo

server {
    location /api/events {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # OBRIGATÓRIO para SSE funcionar:
        proxy_set_header Connection "";        # sem Connection: close
        proxy_buffering off;                   # enviar bytes imediatamente
        proxy_cache off;                       # sem cache

        # Timeout longo (SSE pode durar horas)
        proxy_read_timeout 3600s;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Headers CORS para SSE cross-origin:
        add_header Access-Control-Allow-Origin $http_origin always;
        add_header Access-Control-Allow-Credentials true always;
        add_header Cache-Control no-cache always;
        add_header X-Accel-Buffering no;       # desabilitar buffering do Nginx
    }
}

Diagnosticar problemas com WebSocket

Como identificar por que conexões WebSocket estão falhando:

bash
# Testar WebSocket via wscat:
npm install -g wscat
wscat -c wss://api.seudominio.com.br/ws/

# Verificar logs do Nginx em tempo real:
sudo tail -f /var/log/nginx/error.log | grep -i "websocket\|upgrade\|101"

# Erros comuns e soluções:

# 1. "502 Bad Gateway" ao conectar
# → Backend não está rodando na porta configurada
# → Verificar: curl http://127.0.0.1:3000/ws/

# 2. Conexão cai após 60 segundos
# → proxy_read_timeout muito curto
# → Aumentar para 3600s e adicionar heartbeat no cliente

# 3. "426 Upgrade Required"
# → Faltam headers Upgrade e Connection no proxy
# → Verificar configuração do location

# 4. "mixed content" no browser
# → Tentando conectar ws:// em página https://
# → Usar wss:// (WebSocket Secure) na aplicação

# Verificar handshake WebSocket com curl:
curl -I -H "Upgrade: websocket" -H "Connection: Upgrade" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZQ==" \
  -H "Sec-WebSocket-Version: 13" \
  https://api.seudominio.com.br/ws/
# Deve retornar: HTTP/1.1 101 Switching Protocols

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

ws:// é WebSocket sem criptografia (porta 80 por convenção). wss:// é WebSocket Secure, criptografado com TLS (porta 443). Assim como http:// vs https://, use sempre wss:// em produção. O Nginx termina o TLS e faz proxy para o backend como ws:// interno (mesmo conceito que http→https para proxy reverso).

Causas: (1) proxy_read_timeout muito baixo (padrão: 60s), aumente para 3600s; (2) firewall/NAT fechando conexões idle, adicione heartbeat (ping/pong a cada 30s) na aplicação; (3) load balancer com timeout de sessão, configure sticky sessions; (4) servidor reiniciado ou deploy, clientes devem implementar reconexão automática com exponential backoff.

Após o handshake inicial (HTTP → WebSocket), a conexão é persistente TCP. Se o load balancer rotear a mesma conexão para outro backend em um momento seguinte, o backend não tem o estado daquela sessão WebSocket, a conexão é encerrada com erro. Sticky sessions garantem que toda a comunicação de uma sessão WebSocket vai ao mesmo backend.

Não diretamente, WebSocket usa o mecanismo de upgrade do HTTP/1.1 que não existe em HTTP/2. O Nginx lida com isso automaticamente: negocia HTTP/2 com o cliente mas faz a conexão WebSocket via HTTP/1.1 com o backend. O cliente e backend não precisam se preocupar com isso, o Nginx faz a tradução.

SSE (Server-Sent Events): streaming unidirecional do servidor para o cliente, usa HTTP regular, mais simples, reconexão automática nativa no browser, funciona bem com HTTP/2. WebSocket: bidirectional (cliente e servidor podem enviar), menor overhead por mensagem após o handshake, necessário para jogos, chat e colaboração em tempo real. Para notificações e feeds de dados: SSE é mais simples e suficiente.