gohugoio/hugo · error

retry timeout (configured to %s) fetching remote resource: %

Error message

retry timeout (configured to %s) fetching remote resource: %s

What it means

Hugo's resources.GetRemote retry loop gave up because the cumulative time spent retrying a remote fetch (elapsed plus the next planned sleep, including any server-sent Retry-After) reached the configured timeout. Hugo retries transient HTTP failures (429, 5xx) with exponential backoff, and this error caps total retry time so a build doesn't hang on a misbehaving endpoint. The message includes the last HTTP status ('<nil>' if no response) and the server's Retry-After when present.

Source

Thrown at resources/resource_factories/create/remote.go:515

		if retry {
			sleep := nextSleep
			retryAfter := parseRetryAfter(resp)
			if retryAfter > 0 {
				sleep = retryAfter
			}
			if start.IsZero() {
				start = time.Now()
			}
			if d := time.Since(start) + sleep; d >= t.Cfg.Timeout() {
				msg := "<nil>"
				if resp != nil {
					msg = resp.Status
				}
				if retryAfter > 0 {
					msg = fmt.Sprintf("%s (server requested Retry-After: %s)", msg, retryAfter)
				}
				err := toHTTPError(fmt.Errorf("retry timeout (configured to %s) fetching remote resource: %s", t.Cfg.Timeout(), msg), resp, req.Method != "HEAD", nil)
				return resp, err
			}
			time.Sleep(sleep)
			if nextSleep < nextSleepLimit {
				nextSleep *= 2
			}
			continue
		}

		return
	}
}

// We need to send the redirect responses back to the HTTP client from RoundTrip,
// but we don't want to cache them.
func shouldCache(statusCode int) bool {
	switch statusCode {
	case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Check the reported status: for 429, reduce fetch frequency or add authentication/tokens to raise the rate limit.
  2. Increase the timeout, e.g. in hugo.toml: [HTTPCache]... or the resource-getting timeout option (timeout = "2m") for resources.GetRemote via the options map: {timeout = "2m"}.
  3. Enable/configure the HTTP cache (caches / httpcache config) so repeated builds don't re-fetch the resource.
  4. Verify the URL is reachable from the build environment (CI egress, proxy settings).
  5. Wrap the fetch with try/with .Err handling in templates to fail gracefully: {{ with try (resources.GetRemote $url) }}...{{ end }}.

Example fix

// before (template)
{{ $r := resources.GetRemote "https://api.example.com/data.json" }}
// after: longer timeout + error handling
{{ $opts := dict "timeout" "2m" }}
{{ with try (resources.GetRemote "https://api.example.com/data.json" $opts) }}
  {{ with .Err }}{{ warnf "%s" . }}{{ else }}{{ .Value.Content }}{{ end }}
{{ end }}
Defensive patterns

Strategy: retry

Validate before calling

// config.toml — size timeout to the slowest expected endpoint
// [security.http] / resources.GetRemote options:
// {{ $opts := dict "responseHeaders" true }}
// timeout = "60s"  # in [imaging]/site config: resourceGetRemote timeout via `timeout` key

Try / catch

{{ with try (resources.GetRemote $url) }}
  {{ with .Err }}{{ warnf "remote fetch failed: %s" . }}{{ else }}{{ with .Value }}{{ .RelPermalink }}{{ end }}{{ end }}
{{ end }}

Prevention

When it happens

Trigger: Calling resources.GetRemote (or css/js pipelines that fetch remote resources) against a URL that repeatedly returns retryable statuses (429/5xx) or errors, with total elapsed retry time + next sleep >= the timeout from security/httpcache config (default 1m). A server sending a Retry-After header longer than the remaining budget triggers it immediately.

Common situations: Rate-limited APIs (GitHub, font/CDN endpoints) returning 429 during CI builds with many parallel fetches; flaky or down remote hosts; a too-small custom timeout in site config; corporate proxies returning 503.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/22156780e75f8e0f.json. Report an issue: GitHub ↗.