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
- Resolve the final URL (follow redirects once with curl) and configure Hugo to fetch that final URL directly.
- Fix the redirect loop/misconfiguration on the origin server.
- 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
- Resolve redirect chains upstream and store the final URL.
- Cache flaky remote resources locally.
- Watch origin CDN configuration for redirect loops.
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
- abort: jekyll root contains neither posts nor drafts
- aborted
- aligny must be one of top, center, bottom
- --appendPort=false not supported when in multihost mode
- argument must be a slice
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)