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

  1. curl -sIL <url> and count the Location hops to find the loop
  2. Update the subscription URL to the final canonical endpoint
  3. 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

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


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/71e20336cdc1b853. Report an issue: GitHub.