JuliusBrussee/caveman · error
awscreds: : http
Error message
awscreds: %s: http %d
What it means
doJSON performs one HTTP attempt against an AWS credential endpoint (ECS container metadata or IMDS) and returns the response body only when the status is 2xx. When the endpoint answers with any non-2xx status (404, 403, 500, etc.), it aborts with this error, deliberately reporting only the numeric status because a metadata response body can contain credential material. It is a generic HTTP status failure from the AWS metadata service.
Solutions
- Identify which 'what' (imds token, imds role, imds credentials, container metadata) failed from the message and check the corresponding endpoint: verify AWS_CONTAINER_CREDENTIALS_RELATIVE_URI or the IMDS base (AWS_EC2_METADATA_SERVICE_ENDPOINT).
- For 'imds credentials: http 404', attach an IAM instance profile to the EC2 instance (aws ec2 associate-iam-instance-profile) or pick a role listed at /latest/meta-data/iam/security-credentials/.
- For http 403/404 on the token request, confirm IMDSv2 is enabled in the instance metadata options and no firewall blocks 169.254.169.254.
- For 5xx, retry after a short delay; metadata services throttle briefly.
- If running in a non-AWS environment (local dev, other clouds), stop relying on metadata credentials and supply explicit credentials.
Example fix
// before creds, err := provider.Credentials(ctx) // fails: awscreds: imds credentials: http 404 // after // ensure the instance has a role: // aws ec2 associate-iam-instance-profile --instance-id i-123 --iam-instance-profile Name=my-profile creds, err := provider.Credentials(ctx)
Defensive patterns
Strategy: retry
Validate before calling
// best-effort precheck
if u := os.Getenv("AWS_EC2_METADATA_SERVICE_ENDPOINT"); u == "" && os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") == "" {
// may still be EC2 IMDS; nothing to precheck client-side
} Try / catch
creds, err := provider.Credentials(ctx)
if err != nil {
if strings.Contains(err.Error(), "http 4") {
// misconfiguration (no role / bad path): do not retry, fix IAM or env
} else if strings.Contains(err.Error(), "http 5") {
// transient: retry with backoff
}
return fmt.Errorf("aws credential lookup: %w", err)
} Prevention
- Attach an IAM instance profile/task role before deploying so the metadata paths exist.
- Add NO_PROXY=169.254.169.254 so proxies never intercept metadata traffic.
- Verify IMDSv2 is enabled in instance metadata options.
- Distinguish 4xx (config) from 5xx (transient) in your error handling.
When it happens
Trigger: Calling fromContainer when AWS_CONTAINER_CREDENTIALS_RELATIVE_URI points at a wrong/nonexistent path (404); calling fromIMDS or imdsGet when the EC2 instance has no attached IAM role (404 on the security-credentials path), the IMDSv2 token request is rejected (403), or the metadata service returns 5xx; also raised for the imds token PUT when IMDS is throttled or disabled.
Common situations: Running outside ECS/E2 where the metadata env vars are set incorrectly; an EC2 instance whose IAM instance profile was detached after boot; IMDS hop limit or token endpoint misconfigured; IMDS disabled via instance metadata options (returns 403/404); proxy or security software interfering with 169.254.169.254.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- awscreds: credential expiry
- awscreds: returned an unparseable response
- awscreds: returned code
- AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set…
- awscreds: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/62c015f7b5fa15a9.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/awscreds/awscreds.go:613
}
req.Header.Set("X-aws-ec2-metadata-token", token)
return p.doJSON(p.link, req, what)
}
// doJSON performs one attempt and returns the bounded body. A non-2xx response
// is reported by status only: a metadata body holds credential material.
func (p *Provider) doJSON(client *http.Client, req *http.Request, what string) ([]byte, error) {
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("awscreds: %s request failed: %w", what, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, maxBody))
if err != nil {
return nil, fmt.Errorf("awscreds: read %s response: %w", what, err)
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return nil, fmt.Errorf("awscreds: %s: http %d", what, resp.StatusCode)
}
return body, nil
}
func credentialsFromJSON(body []byte, source string) (*result, error) {
var parsed credentialJSON
if err := json.Unmarshal(body, &parsed); err != nil {
return nil, fmt.Errorf("awscreds: %s returned an unparseable response", source)
}
if parsed.Code != "" && !strings.EqualFold(parsed.Code, "Success") {
return nil, fmt.Errorf("awscreds: %s returned code %q", source, parsed.Code)
}
expires, err := parseExpiry(parsed.Expiration)
if err != nil {
return nil, fmt.Errorf("awscreds: %s credential expiry: %w", source, err)
}
return &result{
creds: awssig.Credentials{View on GitHub (pinned to 3ee70a1026)