S3 SignatureDoesNotMatch — Fix SigV4 Signing Fast
SignatureDoesNotMatch means your secret, clock, region, or encoding differs from AWS.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Basic AWS IAM and S3 concepts
- ✓Comfortable with AWS CLI commands
- ✓Understanding of HTTP requests
- SignatureDoesNotMatch means the signature you sent doesn't match the one AWS computed — same request, different inputs on each side
- The four usual suspects: wrong/rotated secret key, clock skew over 15 minutes, bad URL encoding of special chars, region/endpoint mismatch
- Confirm identity and clock first: aws sts get-caller-identity plus NTP offset check take 30 seconds
- For presigned URLs, compare the canonical request byte-for-byte — one encoded slash breaks the whole signature
Imagine you and a bank clerk each solving the same puzzle with your account number, date, and a secret code — matching answers mean trust. Now your watch runs 20 minutes fast, or you miscopy the secret. Your answer won't match, and the bank rejects you though you're the real holder. That's SignatureDoesNotMatch: you and AWS ran the same signing math with different inputs — key, clock, encoding, or region. Find the differing input and the signatures agree again.
Your S3 uploads worked yesterday. Today every PutObject fails with SignatureDoesNotMatch, and the error message helpfully shows two long base64 strings that look identical but aren't. No code shipped, no keys changed (that anyone admits to), and the bucket policy is untouched. The signing math is deterministic — same inputs, same signature — so something in your inputs drifted.
This error loves credential rotations, fresh EC2 instances with unsynced clocks, and filenames with plus signs, tildes, or Unicode characters. It also haunts presigned URLs generated in one region and used against another, and SDK calls where a proxy quietly rewrites the query string after signing. Each cause produces the identical error message, which is why teams burn hours rechecking the key that's actually fine.
The trap is regenerating keys as the first move. New keys fix a rotated-secret cause but change nothing for clock skew, encoding, or region bugs — and now you've invalidated every other service sharing those credentials. Diagnose before you rotate.
By the end of this article you'll run the 30-second identity-plus-clock check, decode what AWS expected via the StringToSign, fix each of the four root causes, and build signing code that survives rotations, special characters, and multi-region deployments.
How SigV4 Turns Four Inputs Into One Signature
SigV4 is a chain of HMACs, and every link names its inputs explicitly. First your SDK builds the canonical request: HTTP method, canonical URI, canonical (sorted, encoded) query string, canonical headers, signed-header list, and the payload hash. That blob is hashed, then embedded in the StringToSign alongside the algorithm name, the request timestamp, and the credential scope (date, region, service, terminator). Finally the signing key — derived by HMAC-chaining your secret with date, region, and service — signs that string. Change any input bit and the output is unrecognizably different; that's cryptographic design, not flakiness.
This determinism is your debugging superpower: identical inputs always reproduce identical signatures. When AWS rejects you, one of your inputs differs from what AWS used — and AWS's inputs are ground truth (its stored secret for your key, its clock, the region the bucket lives in, the bytes it actually received). Your job is re-deriving each of your four inputs independently until you find the liar.
The error message's two strings are the two HMAC outputs — yours and AWS's — and comparing them character-by-character is theater. They differ everywhere because HMAC avalanches; position of first difference carries zero information. Ignore the strings, interrogate the inputs.
Wrong or Rotated Secrets: Verify Before You Rotate
Secret problems come in three flavors: the key was rotated (old secret deleted, app still holds it), the key was copied wrong (trailing newline, truncated env var, shell escaping mangled a slash or plus), or the app loads a different credential than you think (stale profile, expired SSO token, instance-profile race during rotation). All three produce identical SignatureDoesNotMatch, and only the first is fixed by rotation — the other two survive new keys untouched.
Verify before rotating with the cheapest possible probe: aws sts get-caller-identity using the exact credentials the app loads (same env vars, same profile, same container). Success proves the pair is live and correctly copied, eliminating secrets from suspicion in seconds. Failure with InvalidClientTokenId names the access key as the problem; failure with SignatureDoesNotMatch on STS itself implicates the secret bytes or the clock — check both.
Audit the credential path, not just the value. Dump aws configure list to see which profile, env, or role actually supplies credentials, check for a trailing %0A from a copy-paste newline, and confirm SSO tokens haven't expired (sso sessions silently go stale and some SDKs fall back to older cached keys). When rotation is genuinely needed, generate the new pair first, deploy everywhere, verify, then delete the old — overlap, never gap.
Clock Skew Past 15 Minutes: The Silent Signature Killer
SigV4 bakes the request timestamp into the StringToSign, and AWS rejects any request whose timestamp differs from its own clock by more than 15 minutes. This is replay-attack protection, and it makes every unsynced host in your fleet a signature-failure machine — while HTTP, DNS, database, and all other traffic work perfectly. The selectivity is what makes clock skew so misleading: 'everything works except S3' reads like a credentials problem, not a time problem.
Fresh instances, new container hosts, and laptops off the corporate VPN are the classic victims. The incident pattern is unmistakable in hindsight: old hosts fine, new hosts failing, identical code and keys. AMI baking commonly masks chrony, Docker hosts without time-sync drift on suspend/resume, and some minimal images ship no NTP client at all — inheriting whatever the hypervisor clock says.
Build time-sync into the platform, not the runbook. Images should assert sub-second offset at boot and refuse traffic until synced; orchestrators should expose clock offset as a health signal; and the signing-failure runbook must list the clock check before key rotation, with rotation gated behind it. A one-line date comparison during the incident would have saved the 18,000 failed uploads in this article's story.
Special Characters and Double Encoding in Keys and URLs
S3 object keys with spaces, plus signs, tildes, or non-ASCII characters are the encoding minefield of SigV4. The canonical URI must encode each path segment exactly once with a precise safe set — and the classic failures are double-encoding (%2B becoming %252B when two layers each encode), plus-as-space confusion (form-encoding + versus path-encoding %20), and SDK-versus-proxy disagreements where a proxy normalizes the URL after your code signed the original. Each produces a signature over bytes AWS never received.
Presigned URLs concentrate every encoding risk into one string that passes through browsers, email clients, and chat apps — any of which may re-encode or wrap it. A presigned URL generated with one encoding and fetched with another fails exactly like a bad key. The diagnostic is byte comparison: log the canonical request your signer built, capture the actual request line S3 received (SDK debug logging shows both), and diff them. They must match byte-for-byte; the first differing byte names the layer that re-encoded.
Standardize encoding at the SDK boundary and never hand-roll it. Use your language's canonical quoter with the S3 safe set, generate presigned URLs server-side with the SDK (not string templates), and add integration tests with adversarial filenames — spaces, plus, tilde, Unicode, and a literal % — so the next encoding regression fails in CI instead of production.
Region and Endpoint Mismatches That Masquerade as Bad Keys
The credential scope embeds the region, so a request signed for us-west-2 and sent to a us-east-1 bucket fails with the same SignatureDoesNotMatch as a wrong secret. The usual setups: a client defaulting to us-east-1 against a bucket that lives elsewhere, a presigned URL minted in one region and redeemed in another, or an S3-compatible endpoint (MinIO, LocalStack, VPC endpoint) where the SDK signs for 's3' in one region while the endpoint expects another. Multi-region deployments hit this the moment traffic shifts.
Diagnose with ground truth, not config archaeology: ask AWS where the bucket lives (get-bucket-location; null means us-east-1), then check what region your signer used (SDK debug shows the credential scope line: date/region/s3/aws4_request). Mismatched strings are a complete diagnosis. Also verify the endpoint hostname matches the signing style — path-style versus virtual-hosted-style addressing changes the canonical URI, and mixing styles between signer and sender breaks signatures identically.
Fix by making region explicit everywhere: client constructors with the bucket's region, presigned-URL generation in the bucket's region, and per-bucket clients in multi-region apps rather than one global client. Environment-variable region leaks (AWS_REGION set for one service, inherited by another) are a recurring source — scope region config to the S3 client, not the process.
SDK Signing Pitfalls: Proxies, Versions, and Credential Chains
Modern SDKs sign correctly out of the box — failures come from what surrounds them. Egress proxies that rewrite query strings or normalize paths invalidate signatures computed pre-proxy; test by comparing a direct SDK call against an identical proxied one. SDK upgrades occasionally change default signing behavior (addressing style, payload-signing for streaming, checksum headers added to the signed set); pin versions and read changelogs when failures coincide with a bump. Custom middleware that adds headers after signing (or strips signed ones) breaks the signed-headers contract just as thoroughly.
Credential chains add a second failure class that wears the same error costume. SSO sessions expire, instance metadata serves briefly inconsistent keys during rotation, and shared config files get edited by automation that appends duplicate profiles. When failures are intermittent and host-correlated, suspect the chain: dump the resolved source (aws configure list), check SSO token freshness, and correlate failure windows with rotation timestamps.
Harden the signing path as infrastructure: wire-log canonical requests in staging, pin SDK versions with lockfiles, forbid proxies from mutating S3 URLs (CONNECT tunneling instead of rewriting), and retry signing failures once after refreshing credentials — absorbing rotation transients without paging anyone.
The NTP Drift That Rejected 18,000 Uploads Overnight
- Clock skew is a signing input, not background trivia. Anything past 15 minutes breaks every SigV4 call on the host while leaving all other traffic healthy — check the clock before touching keys.
- Key rotation as a first move can widen the outage. Rotating shared credentials invalidates healthy consumers too; diagnose (identity + clock + region + encoding) before rotating anything.
- Fresh instances are the prime suspects when old ones work. Identical code failing only on new hosts points at the environment (clock, AMI config, subnet endpoints) — diff the hosts, not the code.
| File | Command / Code | Purpose |
|---|---|---|
| aws sts get-caller-identity --query Arn --output text | How SigV4 Turns Four Inputs Into One Signature | |
| aws configure list | Wrong or Rotated Secrets | |
| date -u # what the host believes | Clock Skew Past 15 Minutes | |
| python3 - <<'EOF' | Special Characters and Double Encoding in Keys and URLs | |
| aws s3api get-bucket-location --bucket my-bucket --query LocationConstraint | Region and Endpoint Mismatches That Masquerade as Bad Keys | |
| aws configure list # which source actually supplies creds? | SDK Signing Pitfalls |
Key takeaways
Common mistakes to avoid
5 patternsRotating keys as the first response to every signing failure
Comparing the two signatures in the error character by character
Editing IAM or bucket policies to fix a signing error
Hand-building presigned URLs with string templates
Assuming us-east-1 (null LocationConstraint) means 'no region needed'
Interview Questions on This Topic
Every S3 call fails with SignatureDoesNotMatch but nothing shipped. What's your first command?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Cloud. Mark it forged?
5 min read · try the examples if you haven't