dotnet/aspnetcore · error · RuntimeException
Negotiate redirection limit exceeded.
Error message
Negotiate redirection limit exceeded.
What it means
A RuntimeException thrown by startNegotiate when the server keeps returning redirect URLs and the negotiate attempt count reaches MAX_NEGOTIATE_ATTEMPTS (100). SignalR supports negotiate redirection (e.g. for sticky sessions or regional redirect), but unbounded redirects indicate a misconfiguration or a redirect loop, so the client caps attempts.
Source
Thrown at src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java:374
}
localStart.onError(error);
});
} finally {
this.state.lock.unlock();
}
return localStart;
}
private Single<NegotiateResponse> startNegotiate(String url, int negotiateAttempts, Map<String, String> localHeaders) {
if (this.state.getHubConnectionState() != HubConnectionState.CONNECTING) {
throw new RuntimeException("HubConnection trying to negotiate when not in the CONNECTING state.");
}
return handleNegotiate(url, localHeaders).flatMap(response -> {
if (response.getRedirectUrl() != null && negotiateAttempts >= MAX_NEGOTIATE_ATTEMPTS) {
throw new RuntimeException("Negotiate redirection limit exceeded.");
}
if (response.getRedirectUrl() == null) {
Set<String> transports = response.getAvailableTransports();
if (this.transportEnum == TransportEnum.ALL) {
if (transports.contains("WebSockets")) {
response.setChosenTransport(TransportEnum.WEBSOCKETS);
} else if (transports.contains("LongPolling")) {
response.setChosenTransport(TransportEnum.LONG_POLLING);
} else {
throw new RuntimeException("There were no compatible transports on the server.");
}
} else if (this.transportEnum == TransportEnum.WEBSOCKETS && !transports.contains("WebSockets") ||
(this.transportEnum == TransportEnum.LONG_POLLING && !transports.contains("LongPolling"))) {
throw new RuntimeException("There were no compatible transports on the server.");
} else {
response.setChosenTransport(this.transportEnum);
}View on GitHub (pinned to 294cab2f9b)
Solutions
- Inspect the negotiate response (network trace) to see the redirect chain and identify the loop source.
- Fix the reverse proxy / load balancer / Azure SignalR Service configuration so negotiate stabilizes (no redirect or a single redirect to a valid endpoint).
- Ensure the base URL scheme/host is correct (https vs http, correct region) to avoid bouncing redirects.
- If using Azure SignalR Service, verify the connection string and endpoint configuration.
Example fix
// before: misconfigured base URL causes redirect loop on negotiate
HubConnection conn = HubConnectionBuilder.create("http://app.example.com/chat").build();
// after: point directly at the correct SignalR endpoint, no redirect loop
HubConnection conn = HubConnectionBuilder.create("https://signalr.example.com/chat").build(); Defensive patterns
Strategy: retry
Validate before calling
// Pre-validate the negotiate redirect chain (manual check) before relying on the client.
// Inspect one negotiate response: if it always redirects to the same host, suspect a loop.
NegotiateResponse r = probeNegotiate(url);
if (r.getRedirectUrl() != null && r.getRedirectUrl().equals(url)) {
logger.error("Self-referential negotiate redirect detected; fix server/proxy config");
} Try / catch
connection.start().subscribe(onComplete, error -> {
if (error.getMessage().contains("redirection limit exceeded")) {
// do not retry blindly; fix server/proxy config first
logger.error("Negotiate redirect loop; check reverse proxy / Azure SignalR config", error);
}
}); Prevention
- Inspect the negotiate response chain (curl -L or a network trace) to find the loop source.
- For Azure SignalR Service, verify the endpoint and connection string are correct and not bouncing between regions.
- Ensure HTTPS is used consistently to avoid http<->https redirect loops.
- Do not auto-retry start() indefinitely when a redirect loop is the cause.
When it happens
Trigger: The /negotiate response includes a Url (redirect) field on every attempt, causing startNegotiate to recurse with attempt+1 until negotiateAttempts >= 100, at which point the error is thrown. Typical of a redirect loop where the target redirects back to the origin.
Common situations: A misconfigured reverse proxy or load balancer that always returns a redirect on negotiate (e.g. Azure SignalR Service or backplane misconfiguration). An HTTPS<->HTTP redirect loop. A sticky-session cookie problem causing repeated re-negotiation. The server's redirect URL is unreachable or self-referential.
Related errors
- Unexpected status code returned from negotiate: %d %s.
- Negotiation can only be skipped when using the WebSocket tra
- Error reading NegotiateResponse
- A valid url is required.
- The HubConnection url must be a valid url.
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/cece881db27f27e7.
Report an issue: GitHub.