AlistGo/alist · error

stopped after 10 redirects

Error message

stopped after 10 redirects

What it means

'stopped after 10 redirects' is produced by the CheckRedirect policy on AList's shared HTTP client (internal/net/serve.go). Any request made through net.HttpClient() that follows more than 10 redirects is aborted with this error; the policy also strips the Referer header on each hop. It mirrors net/http's own default redirect cap but with an explicit error string.

Source

Thrown at internal/net/serve.go:272

		}
		all, _ := io.ReadAll(reader)
		_ = res.Body.Close()
		msg := string(all)
		log.Debugln(msg)
		return res, fmt.Errorf("http request [%s] failure,status: %d response:%s", URL, res.StatusCode, msg)
	}
	return res, nil
}

var once sync.Once
var httpClient *http.Client

func HttpClient() *http.Client {
	once.Do(func() {
		httpClient = NewHttpClient()
		httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
			if len(via) >= 10 {
				return errors.New("stopped after 10 redirects")
			}
			req.Header.Del("Referer")
			return nil
		}
	})
	return httpClient
}

func NewHttpClient() *http.Client {
	return &http.Client{
		Timeout: time.Hour * 48,
		Transport: &http.Transport{
			Proxy:           http.ProxyFromEnvironment,
			TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify},
		},
	}
}

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Fetch the URL with curl -L -v and count the hops; eliminate the loop or shorten the chain at the source
  2. Update the stored URL to the final destination after the redirect chain
  3. If the origin legitimately needs more hops, build a custom client with NewHttpClient() and a raised CheckRedirect limit instead of the shared one
  4. Check for cookie/session redirects that make each hop re-redirect, and carry cookies so the chain terminates

Example fix

// before
resp, err := net.HttpClient().Get(url)

// after
client := net.NewHttpClient()
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
    if len(via) >= 20 { return errors.New("stopped after 20 redirects") }
    req.Header.Del("Referer")
    return nil
}
resp, err := client.Get(url)
Defensive patterns

Strategy: retry

Validate before calling

# pre-resolve the URL outside the app to bound redirect chains
curl -sIL -o /dev/null -w '%{num_redirects}' <url>  # keep only if <= 10

Type guard

func isRedirectLimit(err error) bool {
    if ue, ok := err.(*url.Error); ok { err = ue.Err }
    return err != nil && strings.Contains(err.Error(), "stopped after 10 redirects")
}

Try / catch

resp, err := net.HttpClient().Get(u)
if isRedirectLimit(err) {
    // resolve the final URL out-of-band and retry against it once
}

Prevention

When it happens

Trigger: Downloading or proxying a URL (offline download, remote fetch, any net.HttpClient() usage) whose chain of 3xx responses exceeds 10 hops; redirect loops (A->B->A...) between two URLs; a server that bounces between http/https or issues a session redirect on every request.

Common situations: Expired short-link services redirecting through many ad/interstitial pages; misconfigured origins with rewrite loops; offline-download URLs from link shorteners; CDNs repeatedly issuing trailing-slash redirects. Fixing the target URL or the origin's redirect logic is the real remedy.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/ae0518ec0d7d9a9e. Report an issue: GitHub.