{"record":{"id":"133fd2b56afdb61f","repo":"dotnet/aspnetcore","slug":"negotiate-redirection-limit-exceeded","errorCode":null,"errorMessage":"Negotiate redirection limit exceeded.","messagePattern":"Negotiate redirection limit exceeded\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/SignalR/clients/ts/signalr/src/HttpConnection.ts","lineNumber":298,"sourceCode":"                        throw new Error(\"Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.\");\n                    }\n\n                    if (negotiateResponse.url) {\n                        url = negotiateResponse.url;\n                    }\n\n                    if (negotiateResponse.accessToken) {\n                        // Replace the current access token factory with one that uses\n                        // the returned access token\n                        this._setTransportAccessToken(negotiateResponse.accessToken);\n                    }\n\n                    redirects++;\n                }\n                while (negotiateResponse.url && redirects < MAX_REDIRECTS);\n\n                if (redirects === MAX_REDIRECTS && negotiateResponse.url) {\n                    throw new Error(\"Negotiate redirection limit exceeded.\");\n                }\n\n                const finalNegotiateResponse = await this._createTransport(url, this._options.transport, negotiateResponse, transferFormat);\n                this._configureAuthenticationRefresh(finalNegotiateResponse);\n            }\n\n            if (this.transport instanceof LongPollingTransport) {\n                this.features.inherentKeepAlive = true;\n            }\n\n            if (this._connectionState === ConnectionState.Connecting) {\n                // Ensure the connection transitions to the connected state prior to completing this.startInternalPromise.\n                // start() will handle the case when stop was called and startInternal exits still in the disconnecting state.\n                this._logger.log(LogLevel.Debug, \"The HttpConnection connected successfully.\");\n                this._connectionState = ConnectionState.Connected;\n            }\n\n            // stop() is waiting on us via this.startInternalPromise so keep this.transport around so it can clean up.","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/SignalR/clients/ts/signalr/src/HttpConnection.ts#L280-L316","documentation":"Thrown at HttpConnection.ts:298 when the negotiate loop follows 100 consecutive redirects (MAX_REDIRECTS) and the server is still returning a `url` field. Negotiate redirection is a server feature for load balancing and sharding; a healthy server redirects at most once or twice. Hitting the cap indicates a redirect loop or a misconfigured server that keeps pointing back to itself.","triggerScenarios":"A backend/sticky-session LB misconfiguration where the negotiate endpoint at server A returns a url pointing to server B, whose negotiate returns A again; an Azure SignalR Service or backplane configured with a self-referencing redirect; a proxy rewriting the negotiate URL incorrectly.","commonSituations":"Misconfigured Azure SignalR Service with multiple endpoints looping; reverse proxy (Nginx/HAProxy) rewriting the Host header such that the absolute redirect URL re-enters negotiation; custom middleware that always appends a redirect; cyclic appService routing.","solutions":["Inspect the server-side negotiate response chain (log the `url` field of each response) to find the loop.","Fix the reverse proxy to preserve the original Host/path so redirects resolve absolutely.","Ensure sticky sessions or a proper backplane so the server doesn't keep redirecting.","If using Azure SignalR, verify the connection string / endpoint configuration is not pointing back to the app."],"exampleFix":"// server (ASP.NET Core) - ensure negotiate does not self-redirect\n// app.MapHub<MyHub>(\"/hubs/myhub\");  // consistent path\n\n// client - log redirects to diagnose\nconst conn = new HttpConnection(url, {\n  logger: LogLevel.Information,\n});","handlingStrategy":"try-catch","validationCode":"// cannot pre-check client-side; the redirect chain is server-driven.\n// Instrument the negotiate responses in a custom logger to detect loops early.","typeGuard":null,"tryCatchPattern":"try { await hub.start(); }\ncatch (e) {\n  if (/Negotiate redirection limit exceeded/.test(String(e))) {\n    // surface to ops: server-side redirect loop\n    reportToOps('signalr-negotiate-loop', e);\n  }\n  throw e;\n}","preventionTips":["Ensure your reverse proxy preserves Host and path so redirects resolve absolutely.","Validate Azure SignalR Service endpoint configuration.","Log negotiate responses at Information level during staging to detect redirect cycles."],"tags":["negotiate","redirect-loop","load-balancing","server-config"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}