gohugoio/hugo · error

stopped after 10 redirects

Error message

stopped after 10 redirects

What it means

When fetching remote resources, Hugo's HTTP client sets a `CheckRedirect` cap of 10 hops (create.go:105-107) to prevent redirect loops. If a request follows 10 redirects and still isn't done, the client returns this error rather than following further.

Solutions

  1. Resolve the final URL (follow redirects once with curl) and configure Hugo to fetch that final URL directly.
  2. Fix the redirect loop/misconfiguration on the origin server.
  3. Cache the resource locally and reference it as a local file if the remote is unstable.

Example fix

# before
resources.GetRemote "https://short.url/abc"

# after: use the resolved final URL
resources.GetRemote "https://cdn.example.com/assets/abc.png"
Defensive patterns

Strategy: fallback

Try / catch

res, err := resources.GetRemote(url)
if err != nil && strings.Contains(err.Error(), "stopped after 10 redirects") {
    res, err = resources.GetRemote(resolvedFinalURL)
}

Prevention

When it happens

Trigger: A `resources.GetRemote` (or similar remote-resource fetch) whose URL redirects more than 10 times before reaching a final 200 response — e.g. a misconfigured CDN, a redirect loop, or a chain of shortlink hops.

Common situations: Short URL services that chain many 301/302; a URL that bounces between http and https repeatedly; CDN misconfiguration causing loops; a moved resource whose redirect chain exceeds the cap.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/adc999e39a9d1e47. Report an issue: GitHub.

Appendix: source

Thrown at resources/resource_factories/create/create.go:106

	httpTimeout := 2 * time.Minute // Need to cover retries.
	if httpTimeout < (rs.Cfg.Timeout() + 30*time.Second) {
		httpTimeout = rs.Cfg.Timeout() + 30*time.Second
	}

	return &Client{
		rs:                    rs,
		httpCacheConfig:       httpCacheConfig,
		resourceIDDispatcher:  resourceIDDispatcher,
		remoteResourceChecker: remoteResourceChecker,
		remoteResourceLogger:  rs.Logger.InfoCommand("remote"),
		httpClient: &http.Client{
			Timeout: httpTimeout,
			CheckRedirect: func(req *http.Request, via []*http.Request) error {
				if err := rs.ExecHelper.Sec().CheckAllowedHTTPURL(req.URL.String()); err != nil {
					return err
				}
				if len(via) >= 10 {
					return errors.New("stopped after 10 redirects")
				}
				return nil
			},
			Transport: &httpcache.Transport{
				Cache: fileCache.AsHTTPCache(),
				CacheKey: func(req *http.Request) string {
					return resourceIDDispatcher.Get(req.Context())
				},
				Around: func(req *http.Request, key string) func() {
					return fileCache.NamedLock(key)
				},
				AlwaysUseCachedResponse: func(req *http.Request, key string) bool {
					return !httpCacheConfig.For(req.URL.String())
				},
				ShouldCache: func(req *http.Request, resp *http.Response, key string) bool {
					return shouldCache(resp.StatusCode)
				},
				CanStore: func(reqCacheControl, respCacheControl httpcache.CacheControl) (canStore bool) {

View on GitHub (pinned to 52c9bd7908)