gohugoio/hugo · error

failed to fetch remote resource from

Error message

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

What it means

Returned by `Client.FromRemote` (remote.go:285-287) when the remote server responds with a status outside 200-299 (and not 404, which is treated as nil/miss). The error is wrapped in an `*HTTPError` (via `toHTTPError`) carrying `StatusCode`, `Status`, `Headers`, and (for non-HEAD) the response body as `Data` -- useful for diagnostics. Note: this is the single-request failure; the retry loop at the transport layer (error 598) handles transient codes before this is reached.

Solutions

  1. Inspect the `HTTPError.Data.StatusCode` and `Body` to identify the server's complaint.
  2. For 401/403, provide correct auth in `headers` (`Authorization`, cookies, API key).
  3. For 429/5xx, raise the retry timeout via `http.timeout` config or per-call `timeout`, and check `Retry-After` handling.
  4. For 404 that should be tolerated, handle nil return (Hugo returns nil for 404 already); for other 4xx, fix the URL/method/headers.
  5. Verify the endpoint with `curl -i` from the build host to rule out network/firewall blocks.

Example fix

// template -- before
{{ $r := resources.GetRemote $url }}

// after
{{ $r := resources.GetRemote $url (dict "headers" (dict "Authorization" "Bearer $TOKEN")) }}
Defensive patterns

Strategy: try-catch

Validate before calling

// Reachability + auth pre-check.
import (
	"net/http"
	"fmt"
)
func probe(url string, headers map[string]string) error {
	req, _ := http.NewRequest("GET", url, nil)
	for k, v := range headers { req.Header.Add(k, v) }
	resp, err := http.DefaultClient.Do(req)
	if err != nil { return err }
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		return fmt.Errorf("upstream returned %d", resp.StatusCode)
	}
	return nil
}

Try / catch

// Inspect HTTPError.Data for actionable codes.
if he, ok := err.(*create.HTTPError); ok {
	switch he.Data["StatusCode"] {
	case 401, 403:
		return fmt.Errorf("auth failed: %v", he.Data["Body"])
	case 429:
		return fmt.Errorf("rate limited; retry later")
	}
}

Prevention

When it happens

Trigger: `resources.GetRemote` returns this for any 4xx/5xx after retries are exhausted (or for non-transient codes like 401/403/410 that are never retried). The error includes the URL and `http.StatusText` of the code.

Common situations: 401/403 from missing or expired auth token in `headers`; 410 from a deprecated endpoint; 5xx that the retry budget couldn't resolve; CORS/CDN returning 403 for blocked User-Agent; rate-limit 429 that exceeded `httpcache.timeout`; wrong path 404-vs-410 mix-up; the server requires a header the request didn't send.

Related errors


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

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