Skip to main content

Erro 104 no NGINX com .NET - Visão 2 | (104: Connection reset by peer) while reading response header from upstream | Ambiente .NET

  • Erro 104 no NGINX com .NET - Visão 2 | (104: Connection reset by peer) while reading response header from upstream | Ambiente .NET

Erro 104 (ECONNRESET) em Nginx com SignalR, Redis e MongoDB

Visão geral

O erro 104 (ECONNRESET – Connection reset by peer) não é um erro do SignalR, do MongoDB ou do Redis. Ele é um erro de baixo nível do sistema operacional (TCP/IP) que aparece quando uma conexão é encerrada abruptamente enquanto ainda está sendo utilizada.

No contexto de aplicações .NET + SignalR atrás do Nginx, esse erro normalmente surge quando operações demoradas ocorrem durante uma requisição ativa, como no OnConnectedAsync ou em métodos do Hub.


Onde o erro 104 é definido

O código 104 corresponde a:

ECONNRESET

Esse valor é definido no kernel Linux (POSIX / errno.h) e significa:

A conexão TCP foi resetada pelo outro lado.

O Nginx apenas registra esse erro, ele não o cria.


Papel de cada camada

Cliente

Nginx (proxy reverso)

Kestrel (.NET)

SignalR Hub

Redis

MongoDB
  • SignalR: executa o método do Hub
  • Redis: cache rápido
  • MongoDB: fallback com latência variável
  • Nginx: aguarda resposta do backend
  • Kernel TCP/IP: decide o estado final da conexão

Fluxo causal completo do erro 104

Cliente chama método do SignalR

Nginx encaminha requisição e inicia contagem de timeout

Método do Hub executa

Consulta Redis (cache miss)

Fallback para MongoDB (latência imprevisível)

Método do Hub fica bloqueado

Timeout do Nginx é atingido

Nginx encerra o socket TCP

TCP envia pacote RST

Kernel marca conexão como resetada

Tentativa de leitura/escrita no socket

Kernel retorna errno = ECONNRESET (104)

Nginx loga: recv() failed (104: Connection reset by peer)

Relação entre timeout e erro 104

Timeout não lança erro 104 diretamente.

Encadeamento real:

Timeout lógico (Nginx)

Decisão de encerrar conexão

TCP envia RST

Aplicação tenta usar o socket

Kernel retorna ECONNRESET (104)

FIN vs RST (conceito essencial)

FIN (fechamento gracioso)

  • Encerramento ordenado
  • Dados pendentes podem ser transmitidos
  • Não gera erro 104
FIN → ACK → FIN → ACK

RST (reset abrupto)

  • Encerramento imediato
  • Buffers descartados
  • Próxima leitura/escrita gera ECONNRESET (104)
RST → conexão inválida imediatamente

Timeouts quase sempre terminam em RST.


A chamada recv() e o nascimento do erro 104

No nível mais baixo, o NGINX e o Kestrel utilizam system calls do Linux para ler dados do socket TCP.

A chamada central é:

ssize_t recv(int socket_fd, void *buffer, size_t size, int flags);

O comportamento do erro 104 acontece exatamente aqui.


Exemplo conceitual em C (nível kernel/user space)

ssize_t n = recv(socket_fd, buffer, sizeof(buffer), 0);

if (n == -1) {
if (errno == ECONNRESET) {
// errno = 104
printf("Connection reset by peer\n");
}
}

Nesse ponto:

  • O socket já recebeu um RST
  • O kernel marca a conexão como inválida
  • Qualquer recv() ou send() retorna -1
  • errno é preenchido com ECONNRESET (104)

Como o Nginx traduz isso para log

Fluxo interno simplificado do Nginx:

n = recv(fd, buf, size, 0);

if (n == -1) {
ngx_log_error(
NGX_LOG_ERR,
log,
errno,
"recv() failed"
);
}

Se o kernel retornou:

errno = 104

O log final será:

recv() failed (104: Connection reset by peer)

O Nginx não interpreta a causa, apenas registra o erro retornado pelo kernel.


Exemplo comparativo: FIN vs RST no recv()

Caso FIN (fechamento normal)

n = recv(socket_fd, buffer, size, 0);

// n == 0
// significa EOF (conexão fechada normalmente)
  • Nenhum erro
  • Nenhum errno
  • Conexão encerrada de forma limpa

Caso RST (erro 104)

n = recv(socket_fd, buffer, size, 0);

// n == -1
// errno == ECONNRESET (104)
  • Encerramento abrupto
  • Socket inválido imediatamente

Por que Redis + Mongo agrava o problema

Redis é rápido e previsível. MongoDB é variável.

Quando o Redis não contém o dado:

Redis (ms)
Mongo (10ms → vários segundos)

Isso cria um caminho de latência não determinístico dentro de um método de Hub, que mantém a conexão aberta e bloqueada.


SignalR e o problema de acoplamento

Métodos de Hub são executados dentro de uma conexão persistente.

Quando o método depende de:

  • fallback de cache
  • consultas de banco
  • operações externas
  • latência variável

o tempo de resposta da conexão passa a depender do pior cenário.


OnConnectedAsync e sua posição correta

O método base.OnConnectedAsync() deve ser chamado no final do método sobrescrito.

public override async Task OnConnectedAsync()
{
var connectionId = Context.ConnectionId;

await base.OnConnectedAsync();
}

O que não deve existir no OnConnectedAsync

  • Consulta ao Redis
  • Fallback para MongoDB
  • Lógica de negócio
  • Operações bloqueantes
  • Chamadas HTTP
  • Qualquer I/O lento

Exemplo problemático

public override async Task OnConnectedAsync()
{
var data = await redis.GetAsync(key);

if (data == null)
data = await mongo.FindAsync(...);

await base.OnConnectedAsync();
}

Exemplo arquitetural recomendado

public override async Task OnConnectedAsync()
{
var connectionId = Context.ConnectionId;

await base.OnConnectedAsync();

_ = Task.Run(() => InicializacaoPesadaAsync(connectionId));
}

Ou:

public async Task InitAsync()
{
var data = await redis.GetAsync(key);

if (data == null)
data = await mongo.FindAsync(...);

await Clients.Caller.SendAsync("InitCompleted", data);
}

Diferença entre 104, 502, 504 e 499

CódigoOrigemSignificado
104Kernel TCPConexão resetada (RST)
502NginxBackend fechou inválido
504NginxTimeout aguardando resposta
499NginxCliente fechou a conexão

Onde o erro 104 não é definido

  • Não é definido no SignalR
  • Não é definido no ASP.NET
  • Não é definido no MongoDB
  • Não é definido no Redis
  • Não é definido na configuração do Nginx

Ele é definido exclusivamente no TCP/IP do kernel.


Insight arquitetural central

Métodos de Hub não devem ser usados como:

  • camada síncrona de acesso a dados
  • mecanismo de fallback de cache
  • ponto de espera para operações lentas

SignalR deve responder rápido e delegar processamento pesado para fluxos desacoplados.