gohugoio/hugo · error

retry timeout (configured to ) fetching remote resource

Error message

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

What it means

Returned by the custom `transport.RoundTrip` (remote.go:469-527) when the cumulative retry budget exceeds `Cfg.Timeout()`. The transport retries on transient status codes (408, 429, 500, 502, 503, 504) with exponential backoff, optionally honoring `Retry-After`. Once `time.Since(start) + nextSleep >= Timeout`, the loop aborts with this `*HTTPError` carrying the last response status and, if present, the server's `Retry-After` value.

Solutions

  1. Raise the global HTTP timeout: `httpTimeout = "60s"` (or higher) in hugo config; the configured value appears in the error message.
  2. Reduce the request rate or move the fetch off the critical build path (pre-fetch via a script, commit the asset).
  3. If 429, ensure `Retry-After` is honored (current Hugo does); contact the provider about quota.
  4. Retry the build outside the provider's peak window.
  5. Verify the endpoint is healthy via `curl -i` -- sustained 5xx may indicate a real outage, not a transient blip.

Example fix

# hugo.toml -- before
httpTimeout = "10s"

# after
httpTimeout = "60s"
Defensive patterns

Strategy: retry

Validate before calling

// Estimate retry budget against provider's typical 5xx/429 behaviour before building.
// If provider SLA needs > Hugo timeout, pre-fetch outside the build.

Try / catch

// Build-level retry with backoff for known-flaky upstreams.
for i := 0; i < 3; i++ {
	if err := hugoBuild(); err == nil { break }
	if !isRetryTimeoutErr(err) { break }
	time.Sleep(time.Duration(i+1) * time.Minute)
}

Prevention

When it happens

Trigger: `resources.GetRemote` to an endpoint returning repeated 503/429 with the server's `Retry-After` exceeding Hugo's configured `httpTimeout`; or sustained 500/502 where exponential backoff accumulates past the budget. The error surfaces the last HTTP status so the caller can see what the server was saying.

Common situations: Upstream rate-limits (429) with a long `Retry-After`; overloaded origin returning 503 during deploy; CI build hitting a third-party API during its maintenance window; default `httpTimeout` too low for an endpoint that legitimately needs retries; missing `Retry-After` parsing bug leading to under-waiting (Hugo now parses it, but older builds didn't).

Understand the failure class

Related errors


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

Appendix: 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 52c9bd7908)