hashicorp/terraform · error

response has invalid Content-Type: must be application/json

Error message

response has invalid Content-Type: must be application/json

What it means

After successfully parsing the Content-Type media type, get() requires it to be exactly application/json. Any other media type (text/html, application/octet-stream, etc.) on a 200 response yields this error. Typically means an error/landing page was served with status 200 instead of the JSON index.

Source

Thrown at internal/getproviders/http_mirror_source.go:380

		if body == nil {
			resp.Body.Close()
		}
	}()
	// After this point, our final URL return value should always be the
	// one from resp.Request, because that takes into account any redirects
	// we followed along the way.
	finalURL = resp.Request.URL

	if resp.StatusCode == http.StatusOK {
		// If and only if we get an OK response, we'll check that the response
		// type is JSON and return the body reader.
		ct := resp.Header.Get("Content-Type")
		mt, params, err := mime.ParseMediaType(ct)
		if err != nil {
			return 0, nil, finalURL, fmt.Errorf("response has invalid Content-Type: %s", err)
		}
		if mt != "application/json" {
			return 0, nil, finalURL, fmt.Errorf("response has invalid Content-Type: must be application/json")
		}
		for name := range params {
			// The application/json content-type has no defined parameters,
			// but some servers are configured to include a redundant "charset"
			// parameter anyway, presumably out of a sense of completeness.
			// We'll ignore them but warn that we're ignoring them in case the
			// subsequent parsing fails due to the server trying to use an
			// unsupported character encoding. (RFC 7159 defines its own
			// JSON-specific character encoding rules.)
			log.Printf("[WARN] Network mirror returned %q as part of its JSON content type, which is not defined. Ignoring.", name)
		}
		body = resp.Body
	}

	return resp.StatusCode, body, finalURL, nil
}

func (s *HTTPMirrorSource) errQueryFailed(provider addrs.Provider, err error) error {

View on GitHub (pinned to c9def3e214)

Solutions

  1. Configure the mirror server to serve .json files as application/json.
  2. Point the base URL at the exact directory containing index.json and the version files.
  3. Remove catch-all routes that return HTML with 200 for missing paths.

Example fix

# before: nginx default type for unknown ext
http {
  default_type application/octet-stream;
}

# after
http {
  types {
    application/json json;
  }
  default_type application/octet-stream;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Preflight: require a JSON media type from the mirror.
mt, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if mt != "application/json" {
    return fmt.Errorf("expected application/json, got %q", mt)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "must be application/json") {
    // mirror served a non-JSON 200; report, do not retry
}

Prevention

When it happens

Trigger: Mirror or an intermediary returns 200 with a non-JSON body: a generic HTML page, a directory listing, or a binary. Common when a static file server doesn't set .json Content-Type or a proxy serves a fallback page.

Common situations: Mirror directory served by a web server without the .json MIME mapping; catch-all route returning an HTML SPA page with 200; wrong base URL path landing on a listing page.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/3090137606c60b2f. Report an issue: GitHub.