gohugoio/hugo · error

failed to fetch remote resource from '%s': %s

Error message

failed to fetch remote resource from '%s': %s

What it means

The remote fetch completed but the server answered with a non-2xx status (other than 404, which Hugo maps to a nil resource to mirror missing local resources). Hugo wraps the status text into an HTTP error carrying the response so templates can inspect it via `.Err`. This is a server-side/authorization problem, not a Hugo bug.

Source

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

		if cancel != nil {
			defer cancel()
		}
		if err != nil {
			return nil, err
		}
		defer res.Body.Close()

		c.configurePollingIfEnabled(uri, optionsKey, getRes)

		if res.StatusCode == http.StatusNotFound {
			// Not found. This matches how lookups for local resources work.
			// To cache this, we need to make sure the body is read.
			io.Copy(io.Discard, res.Body)
			return nil, nil
		}

		if res.StatusCode < 200 || res.StatusCode > 299 {
			return nil, toHTTPError(fmt.Errorf("failed to fetch remote resource from '%s': %s", uri, http.StatusText(res.StatusCode)), res, !isHeadMethod, options.ResponseHeaders)
		}

		var (
			body      []byte
			mediaType media.Type
		)
		// A response to a HEAD method should not have a body. If it has one anyway, that body must be ignored.
		// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD
		if !isHeadMethod && res.Body != nil {
			body, err = io.ReadAll(res.Body)
			if err != nil {
				return nil, fmt.Errorf("failed to read remote resource %q: %w", uri, err)
			}
		}

		filename := path.Base(rURL.Path)
		if _, params, _ := mime.ParseMediaType(res.Header.Get("Content-Disposition")); params != nil {
			if _, ok := params["filename"]; ok {

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Handle it gracefully in the template: check `.Err` on the returned resource (`{{ with try (resources.GetRemote $u) }}...`) and log via `errorf`/`warnf` instead of crashing the build.
  2. Add required auth/User-Agent headers via the options dict and ensure CI secrets are actually set.
  3. For 429s, authenticate to raise rate limits or cache the response (use a stable `key` option) so rebuilds don't refetch.
  4. Verify the URL responds correctly with `curl -i` from the same environment as the build.

Example fix

{{/* before */}}
{{ $data := resources.GetRemote $u | transform.Unmarshal }}
{{/* after */}}
{{ with resources.GetRemote $u (dict "headers" (dict "Authorization" (printf "Bearer %s" (getenv "API_TOKEN")))) }}
  {{ with .Err }}{{ errorf "remote fetch failed: %s" . }}{{ else }}{{ $data := . | transform.Unmarshal }}{{ end }}
{{ end }}
Defensive patterns

Strategy: fallback

Try / catch

{{/* GetRemote does not fail the build by default — check .Err yourself */}}
{{ $r := resources.GetRemote $url }}
{{ with $r }}
  {{ with .Err }}
    {{ warnf "remote fetch failed: %s — using local fallback" . }}
    {{ $r = resources.Get "fallback/placeholder.svg" }}
  {{ end }}
{{ end }}

Prevention

When it happens

Trigger: `resources.GetRemote` receiving 401/403 (missing or invalid auth headers), 429 (rate limiting, common with GitHub/API endpoints during CI builds), 5xx from the origin, or 3xx loops the client won't follow — anything outside 200–299 except 404.

Common situations: Expired or unset API tokens in CI environment variables, GitHub API rate limits on unauthenticated builds, hotlink protection or bot blocking (Cloudflare 403) against Hugo's requests, endpoints requiring a User-Agent header, or transient upstream outages during site builds.

Related errors


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