Article View

Scroll down to read the full article.

Node.js ECONNRESET in Docker: The Conntrack Connection Killer

calendar_month July 13, 2026 |
Quick Summary: Debugging intermittent Node.js ECONNRESET in Docker on Linux kernels. Solve the netfilter conntrack keep-alive mismatch leading to silent connecti...

Alright, settle in. We've all been there: a critical Node.js service, running in Docker, suddenly starts throwing ECONNRESET or socket hang up errors. Not all the time, oh no. Just enough to drive you absolutely insane, usually under load, usually when you're not watching. Your monitoring is screaming, users are complaining, and you're staring at logs wondering if you've angered a cosmic entity.

This isn't your garden-variety network timeout. This is deeper. This is insidious. This is netfilter conntrack silently murdering your perfectly valid keep-alive connections.

A rusty
Visual representation

You see errors like:

  • Error: read ECONNRESET
  • Error: socket hang up
  • Sometimes, requests just hang until an application-level timeout.

These aren't immediate connection failures. The connection establishes fine. Data flows. Then, after some idle time, or perhaps on the second request over a reused connection, BAM. Reset.

Don't lie. Your first instinct was probably:

  1. "It's the target service!" (Nope, other apps talk to it fine).
  2. "It's a firewall!" (Checked, ports are open, rules are permissive).
  3. "It's network congestion!" (Maybe, but tcpdump doesn't show retransmits before the RST).
  4. "Node.js http.Agent is buggy!" (Closer, but not quite).

You tweaked agent.maxSockets, agent.keepAlive, timeout settings. You probably even threw more CPU and RAM at the container. Nothing. The ghost persists.

This specific flavor of hell seems to thrive in these conditions:

Component Version(s) Where Issue Triggers Notes
Host OS Kernel Linux 5.4.x (e.g., Ubuntu 20.04 LTS) Older kernels with default netfilter settings.
Docker Engine 20.10.x - 24.0.x Standard Docker networking setup, bridge mode often.
Node.js Runtime 16.x, 18.x (especially default http.Agent) Versions relying on Node.js's default keep-alive agent.
Container OS Debian 11 (Bullseye), Alpine 3.14+ Standard base images.

This is where you earn your stripes. Standard tcpdump inside the container or on the host might show an abrupt RST from the host network stack, seemingly out of nowhere, without a corresponding FIN or previous retransmissions.

The real key is looking at the host's connection tracking table. Use conntrack -L -p tcp on the host, filtering for connections related to your container's outbound IP. You'll observe entries with a timeout field. What often happens is that the conntrack entry for an idle TCP connection expires on the host before Node.js's keep-alive agent decides to close it.

Node.js, oblivious to the host's netfilter shenanigans, tries to reuse this "active" connection. But netfilter on the host has forgotten about it. When Node.js sends data, the host's kernel sees an unexpected packet for a non-existent connection, drops it, and often sends an RST back, leading to your ECONNRESET.

This is a classic example of mismatched state. The application (Node.js) thinks one thing, the kernel (host's netfilter) thinks another. Similar state management issues can plague other parts of the network stack, such as when systemd-resolved interferes with UDP sockets in Docker, leading to equally baffling timeouts or connection failures.

The Root Cause

The fundamental flaw lies in the default netfilter connection tracking (conntrack) timeout for established TCP connections on many Linux distributions, especially older ones, not being aligned with common application keep-alive durations. Docker uses netfilter for its network address translation (NAT). When a Node.js http.Agent keeps a connection alive for, say, 60 seconds (Node.js 16/18 default keepAliveMsecs is 1000ms but the socket might be held longer by the agent), but the host's netfilter tcp_timeout_established is, for instance, only 300 seconds (5 minutes), an idle connection can expire in netfilter before the application is done with it.

If no traffic flows for tcp_timeout_established seconds, netfilter purges the entry. The connection still technically exists from the Node.js process's perspective inside the container, but the host's NAT layer has forgotten about it. The next attempt to send data over that "reused" connection hits a black hole, provoking the RST. This is particularly insidious because the target server might still think the connection is active too, exacerbating the problem.

A padlock dissolving into a digital void
Visual representation

The simplest, most effective fix is to increase the netfilter TCP established connection timeout on the host machine. This ensures netfilter holds onto the connection state for longer than your application's keep-alive agents typically do, preventing premature eviction.

You need to become root on the Docker host and execute this:


sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=86400
Explanation:
  • net.netfilter.nf_conntrack_tcp_timeout_established: This kernel parameter controls the timeout for established TCP connections in the netfilter conntrack table.
  • 86400: This value is in seconds, representing 24 hours. This is a significantly safer margin than the default (often 300-600 seconds) and virtually guarantees your Node.js keep-alive connections won't be silently terminated by the host's kernel.

Important: This change is not persistent across reboots. To make it permanent, add the line net.netfilter.nf_conntrack_tcp_timeout_established=86400 to /etc/sysctl.conf or a new file in /etc/sysctl.d/. Then run sudo sysctl -p.

This problem sits at the intersection of application-level networking (Node.js http.Agent), container networking (Docker's use of netfilter), and host OS kernel parameters. Each layer works "correctly" in isolation, but their default timings clash. It’s not a bug in any single component as much as a misconfiguration of default parameters for a specific workload. This is why when you're looking at complex interactions, you often need to consider how something like scaling distributed systems introduces these deeply intertwined dependencies.

If you've been battling ECONNRESETs in your Node.js Docker containers, especially after idle periods, check your host's netfilter conntrack timeout. It's a subtle killer that can wreak havoc on connection-oriented applications. Don't let default kernel settings silently undermine your application's stability. Dig deep, question everything, and remember: the network is always telling a story, you just need the right tools to listen.

Read Next