Kubernetes
SignalR
Ingress
WebSockets
Error 1006

Kubernetes - SignalR Behind Ingress Connection Disconnected With 1006

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

WebSocket close code 1006 means the client observed an abnormal connection closure without receiving a proper close frame. When SignalR runs behind Kubernetes ingress, that usually points to a proxy or routing problem rather than a bug in SignalR itself. The most common causes are proxy timeouts, missing WebSocket support, pod churn, or load balancing that breaks a long-lived SignalR connection.

Why This Happens Behind Ingress

A SignalR connection is long-lived and stateful. An ingress controller or external load balancer sits between the browser and the pod, so several extra failure modes appear:

  • idle or read timeout closes the socket
  • WebSocket upgrade headers are not handled correctly
  • requests from one client hit different pods unexpectedly
  • the backend pod restarts or is drained during rollout
  • the external load balancer closes idle TCP sessions first

Code 1006 is not very specific by itself. It tells you the close was abnormal, not exactly which proxy layer did it.

Start With the Proxy Timeouts

Ingress defaults are often tuned for short HTTP requests, not for persistent WebSocket sessions. If the connection is closed at a fixed interval, timeout settings are the first thing to inspect.

For ingress-nginx, long-lived SignalR traffic often needs larger proxy timeouts:

yaml
1apiVersion: networking.k8s.io/v1
2kind: Ingress
3metadata:
4  name: signalr
5  annotations:
6    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
7    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
8    nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
9spec:
10  ingressClassName: nginx
11  rules:
12    - host: app.example.com
13      http:
14        paths:
15          - path: /
16            pathType: Prefix
17            backend:
18              service:
19                name: app-service
20                port:
21                  number: 80

These values are examples, not universal constants. The point is that WebSocket traffic often needs much longer read and send windows than ordinary REST endpoints.

Check Session Affinity and Routing Behavior

If you are self-hosting SignalR across multiple pods, connection routing matters. A client negotiates and then expects its ongoing transport to stay coherent. If different requests for the same session are routed inconsistently, disconnects and handshake failures can appear.

Possible fixes include:

  • enabling sticky sessions at the ingress layer
  • using a SignalR backplane or Azure SignalR Service for multi-instance fan-out
  • making sure negotiation and transport requests hit compatible backends

An ingress-nginx cookie-affinity example:

yaml
1metadata:
2  annotations:
3    nginx.ingress.kubernetes.io/affinity: "cookie"
4    nginx.ingress.kubernetes.io/session-cookie-name: "signalr-route"

Affinity is not always required, but in self-hosted multi-pod setups it is a common missing piece.

Verify the Application and Transport Settings

SignalR itself should also be configured for the deployment reality. If keepalive and timeout settings are too aggressive relative to proxy behavior, connections can flap unnecessarily.

A server-side example in ASP.NET Core:

csharp
1using System;
2using Microsoft.AspNetCore.Builder;
3using Microsoft.Extensions.DependencyInjection;
4
5var builder = WebApplication.CreateBuilder(args);
6
7builder.Services.AddSignalR(options =>
8{
9    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
10    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
11});
12
13var app = builder.Build();
14app.MapHub<ChatHub>("/hub");
15app.Run();
16
17public class ChatHub : Microsoft.AspNetCore.SignalR.Hub { }

These settings do not fix a bad ingress by themselves, but they help the server and client detect dead connections in a predictable way.

Look for Pod Restarts and Rollouts

A 1006 that appears during deployments or scale events is often not an ingress annotation problem at all. It may simply be that the SignalR pod is restarting, being rescheduled, or being terminated during a rollout.

Check:

bash
kubectl get pods
kubectl describe pod my-signalr-pod
kubectl rollout history deployment/my-app

If the disconnects line up with pod replacement, then graceful shutdown, readiness, and rollout strategy matter just as much as WebSocket configuration.

Confirm WebSocket Support End to End

The full path has to support WebSockets:

  • browser or client library
  • ingress controller
  • Kubernetes service
  • pod
  • any external load balancer in front of ingress

A surprising number of 1006 issues are really on the external load balancer side. The ingress might be configured correctly, but the cloud load balancer in front of it closes idle connections sooner than expected.

That is why you should inspect logs at multiple layers:

  • browser console
  • SignalR server logs
  • ingress controller logs
  • cloud load balancer or gateway metrics if available

Common Pitfalls

The biggest mistake is treating 1006 as a SignalR-only error. It is often a proxy or network-lifecycle problem.

Another mistake is increasing only the client timeout while leaving ingress read and send timeouts low. The outer proxy closes the connection first, so the application-level setting never gets a chance to help.

Teams also often scale SignalR to multiple pods without thinking about affinity or backplane design. Long-lived real-time connections need more routing care than stateless HTTP requests.

Finally, do not ignore deployments and pod churn. If a disconnect happens exactly when pods rotate, investigate rollout behavior before changing every ingress annotation in sight.

Summary

  • WebSocket 1006 behind ingress usually means an abnormal close caused by a proxy, routing, or pod-lifecycle issue.
  • Start with ingress and load-balancer timeout settings for long-lived connections.
  • In multi-pod self-hosted SignalR setups, check affinity or backplane strategy.
  • Verify SignalR keepalive settings, but do not expect them to compensate for broken proxy behavior.
  • Correlate disconnect timing with ingress logs, load-balancer behavior, and pod restarts.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.