nginx
tcp stream
Kubernetes
client connection
upstream connection

nginx tcp stream k8s - keep client connection open when upstream closes

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Usually, no. In NGINX stream mode, a client TCP connection and an upstream TCP connection form one proxied session, and NGINX does not transparently migrate an arbitrary live TCP stream to a different upstream after the backend closes. That kind of failover is easy for HTTP because NGINX understands requests and responses, but for raw TCP it is largely protocol-blind.

Why This Is Hard in Raw TCP Proxying

With HTTP, NGINX can often retry a failed upstream request because it understands request boundaries. With generic TCP, NGINX does not know whether the bytes already sent belong to a complete logical message, half a message, or a protocol state machine that cannot be replayed safely.

That is why settings such as:

nginx
proxy_next_upstream on;
proxy_next_upstream_timeout 600s;
proxy_next_upstream_tries 3;

do not solve the "backend closed after the session was already in progress" problem. Those directives help when NGINX cannot establish an upstream connection, not when an established raw TCP session disappears and you expect NGINX to keep the client socket alive and reattach it elsewhere seamlessly.

What NGINX Stream Actually Does

A minimal stream proxy looks like this:

nginx
1stream {
2    upstream backend_pool {
3        server app1:7550;
4        server app2:7550;
5    }
6
7    server {
8        listen 7550;
9        proxy_pass backend_pool;
10        proxy_timeout 600s;
11    }
12}

Once a client is connected and an upstream is selected, NGINX is relaying bytes between two TCP endpoints. If the upstream fully closes, the proxied session is effectively over. NGINX does not have enough protocol knowledge to invent a new backend session that preserves application correctness.

What proxy_half_close Can and Cannot Do

Recent NGINX stream versions added proxy_half_close:

nginx
1server {
2    listen 7550;
3    proxy_pass backend_pool;
4    proxy_half_close on;
5}

This enables independent closing of the two TCP directions. In other words, if one side half-closes its send direction, NGINX can keep the session alive until both sides close.

That is useful for protocols that legitimately use TCP half-close semantics. But it still does not mean:

  • automatic reconnection to a different upstream
  • replay of in-flight application messages
  • seamless failover of a stateful TCP session during backend shutdown

It only affects how close events are handled within the same proxied session.

What Usually Works Instead

If you need uninterrupted client connections during backend restarts or redeployments, the solution usually has to live above raw TCP pass-through. Common options are:

  • a protocol-aware proxy that understands message boundaries
  • application-level reconnect and retry behavior in the client
  • graceful backend draining before pod termination
  • session replication or failover inside the application protocol itself

For example, in Kubernetes, you often reduce disruption by draining pods before termination so existing long-lived sessions can finish naturally.

Kubernetes Operational Mitigations

In a Kubernetes environment, this problem is often less about NGINX syntax and more about rollout behavior. Practical mitigations include:

  • 'preStop hooks'
  • sufficient termination grace period
  • readiness changes before shutdown
  • avoiding abrupt backend exit during rolling updates

A backend that stops accepting new traffic first and only exits after existing sessions drain is much easier on old TCP clients than trying to fake a session handoff in the proxy.

When You Need a Different Proxy

If the requirement is truly "never close the client socket, even if the backend disappears, and silently attach the client to a replacement backend," plain NGINX stream mode is usually the wrong tool unless your protocol is simple enough for a custom man-in-the-middle implementation.

That is not a configuration gap so much as a protocol limitation. Transparent migration of arbitrary TCP conversations is not something a generic relay can safely guarantee.

Common Pitfalls

One common mistake is assuming proxy_next_upstream means mid-stream failover for active TCP sessions. It does not.

Another issue is confusing TCP keepalive with session migration. Keepalive can detect dead peers or keep idle sockets from timing out, but it does not preserve application state across a closed upstream.

Developers also sometimes expect HTTP-style reverse-proxy behavior from the NGINX stream module. Stream mode is much lower level and much less aware of what the bytes mean.

Finally, proxy_half_close is easy to misread. It helps with half-closes on one proxied session, not with hopping the client to a fresh backend after the upstream fully exits.

Summary

  • NGINX stream mode generally cannot keep a client TCP session alive by transparently switching to a new upstream after the current upstream closes.
  • 'proxy_next_upstream helps mainly during upstream connection establishment, not after a live raw TCP session is already in progress.'
  • 'proxy_half_close on; only changes half-close behavior and does not provide seamless backend failover.'
  • In Kubernetes, graceful draining and application-level reconnect logic are usually the practical solutions.
  • If you need true protocol-aware failover, use a proxy or service layer that understands the application protocol rather than raw TCP relay alone.

Course illustration
Course illustration

All Rights Reserved.