MHSanaei/3x-ui · warning
stopped after 10 redirects
Error message
stopped after 10 redirects
What it means
The subscription fetch client installs a CheckRedirect hook that re-validates every hop against private addresses (SSRF guard) and hard-caps the chain at 10 via the standard net/http idiom: returning an error when len(via) >= 10. 'stopped after 10 redirects' fires exactly when the 11th consecutive redirect is attempted — independent of the AllowPrivate setting.
Source
Thrown at internal/web/service/outbound_subscription.go:350
// any direct DB tampering). Private targets are blocked unless this
// subscription was explicitly created with AllowPrivate.
cleanURL, err := SanitizePublicHTTPURL(sub.Url, sub.AllowPrivate)
if err != nil {
s.recordError(sub, err)
return nil, err
}
if cleanURL == "" {
return nil, common.NewError("subscription has no valid URL")
}
sub.Url = cleanURL // persist the cleaned version
client := s.subscriptionFetchClient(30*time.Second, sub.AllowInsecure)
// Re-validate every redirect hop: the initial host is checked above, but a
// redirect could still point at a private/internal address (SSRF). Cap the
// redirect chain as well.
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return fmt.Errorf("stopped after 10 redirects")
}
if sub.AllowPrivate {
return nil
}
ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second)
defer cancel()
return rejectPrivateHost(ctx, req.URL.Hostname())
}
reqCtx := netsafe.ContextWithAllowPrivate(context.Background(), sub.AllowPrivate)
req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, sub.Url, nil)
if err != nil {
s.recordError(sub, err)
return nil, err
}
req.Header.Set("User-Agent", "3x-ui-outbound-sub/1.0")
resp, err := client.Do(req)View on GitHub (pinned to ad32144c42)
Solutions
- curl -sIL <url> and count the Location hops to find the loop
- Update the subscription URL to the final canonical endpoint
- Log in again / refresh the token if the provider requires auth and is bouncing to a login page
Example fix
# before sub.Url = "https://s.example/r/abc" # loop of 12 redirects # after sub.Url = "https://provider.example/api/sub?key=..." # final destination from the last Location header
Defensive patterns
Strategy: validation
Validate before calling
// pre-resolve the redirect chain once
if err := checkRedirectDepth(sub.Url, 10); err != nil {
return fmt.Errorf("subscription URL redirect problem: %w", err)
} Try / catch
_, err := client.Do(req)
if err != nil && strings.Contains(err.Error(), "stopped after 10 redirects") {
// url.Error wraps it; fix/replace the subscription URL, no point retrying
} Prevention
- Store the final canonical subscription URL, not shortlinks
- Periodically validate stored subscription URLs with a HEAD chain walk
When it happens
Trigger: The subscription URL (after the initial host validation) redirects more than 10 times in a chain — redirect loops (A -> B -> A), or a CDN/login wall that keeps bouncing.
Common situations: Expired provider session redirecting to a login page that redirects back; misconfigured shortener loop; a provider that moved and chained forwarding rules.
Related errors
- blocked private/internal address %s
- outbound subscription response body exceeds size limit (limi
- http %d
- blocked private/internal address %s
- cannot resolve host %s: %w
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/71e20336cdc1b853.
Report an issue: GitHub.