Skip to main content
DevOps Insightsguides & answers

AWS · troubleshooting

SignatureDoesNotMatch

AWS recalculated your request's signature and got a different one: the secret key, a header, the URL or the clock differs from what was signed. With presigned S3 URLs it is usually a Content-Type or a URL that changed on the way.

First published

New to this? Read what is actually happening first, then come back to the steps.

On this page · 10 sections

Before you start

  • The full error response, and the code or command that made the request.
  • For presigned URLs: the code that generated the URL, including any content type it set.
  • The AWS CLI configured with the same keys, to test them with a known-good signer.

Run this first

step 1 of 2
aws sts get-caller-identity --profile NAME

Look for Success (your request or signing is the problem) or the same error (the keys are)

Tests the same keys with AWS's own signer. It separates bad keys from a bad request.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

Is the failing request a presigned URL?

Cause space

4 of 4 still possible

  • A presigned URL is used with a different method, header or URLCommon
  • The secret key does not belong to the access key, or has stray charactersCommon
  • A proxy or client changed the request after it was signedOccasional
  • Your own code computes the signature differentlyOccasional

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Check it with a tool

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    aws sts get-caller-identity --profile NAME

    Tests the same keys with AWS's own signer. It separates bad keys from a bad request.

    Look for Success (your request or signing is the problem) or the same error (the keys are)

  2. Step 2

    date -u

    A clock far off produces signature date errors, and AWS lists clock drift first for presigned URLs.

    Look for The current UTC time, within a few minutes

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

A presigned URL is used with a different method, header or URL

Common

The URL was generated for a PUT with one Content-Type and the upload sends another (browsers and libraries set their own), or the URL was altered: re-encoded, truncated, or split by the shell because it was not quoted.

Confirm

grep -o 'X-Amz-SignedHeaders=[^&]*' <<< "$URL"

content-type (or another header) listed as signed, while the request sends a different value or none

Fix

  • Send exactly the Content-Type the URL was generated with, or generate it without a content type if clients cannot control the header.
  • Use the URL byte for byte: no re-encoding, and in a shell quote it: curl -X PUT -T FILE -H "Content-Type: TYPE" "$URL".
  • Generate the URL for the bucket's own Region, and a new one if it has expired.
curl -X PUT -T FILE -H "Content-Type: application/octet-stream" "$PRESIGNED_URL"

The Content-Type must equal the one used to generate the URL, and the URL must be quoted.

The secret key does not belong to the access key, or has stray characters

Common

A rotated or mistyped secret, a trailing space or newline copied into an environment variable or secret store, or access key and secret taken from two different keys.

Confirm

aws sts get-caller-identity --profile NAME

The same SignatureDoesNotMatch with the CLI means the keys are wrong; success means your own signing or request is

Fix

  • Re-enter the secret exactly, without surrounding whitespace, or create a new access key and replace both halves together.
  • Prefer roles and temporary credentials over long-lived keys, so there is no secret to copy at all.

A proxy or client changed the request after it was signed

Occasional

A corporate proxy, gateway or middleware rewrote a signed header, the query string or the body after signing. AWS's documentation names proxies explicitly.

Confirm

env -u HTTPS_PROXY -u HTTP_PROXY aws s3 ls s3://BUCKET

The request succeeds without the proxy variables, from a network where AWS is reachable directly

Fix

  • Test without the proxy; if that works, have the proxy pass AWS traffic through unchanged, or exempt the AWS endpoints.
  • Make sure no middleware adds or changes headers after the SDK signs the request.

Your own code computes the signature differently

Occasional

Hand-written SigV4 code, or a non-AWS client talking to S3, builds the canonical request differently: path encoding, query parameter order, header case or the payload hash. Or it still uses SigV2, which S3 no longer accepts.

Confirm

aws s3api head-object --bucket BUCKET --key KEY --debug 2>&1 | grep -A12 -m1 'CanonicalRequest'

A canonical request from the CLI to compare, line by line, with the one your code builds

Fix

  • Use an AWS SDK or the CLI instead of hand-written signing wherever possible, as AWS recommends.
  • If you must sign yourself, compare your canonical request and string to sign with the CLI's for the same call, and fix the first line that differs.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

In plain terms

Every AWS request carries a signature made from your secret key and the exact request: method, path, query string and certain headers. AWS repeats that calculation on its side. If one character differs, the two signatures differ and AWS refuses with 403. It does not tell you which part changed, so the work is finding what was signed differently from what was sent.

Words used on this page

SigV4
AWS Signature Version 4, the signing scheme for AWS requests. S3 and newer Regions no longer accept the older SigV2.
Canonical request
The normalised form of method, path, query string, signed headers and payload hash that both sides sign. Any difference breaks the match.
Signed headers
The headers included in the signature, listed in SignedHeaders or X-Amz-SignedHeaders. Changing one of them after signing breaks it.
Presigned URL
A URL with the signature in its query string, letting someone without credentials make that one request until it expires.
What is actually happening

AWS's IAM documentation describes it plainly: HTTP 403 SignatureDoesNotMatch means the signature in your request did not match the signature the service calculated. When the canonical request or string to sign differs, the message is The request signature we calculated does not match the signature you provided.

It is not a permission problem. A wrong or unknown access key gives a different error, and missing permissions give AccessDenied. Here the key ID was accepted and the signature was not.

Time matters but usually has its own messages: Signature expired, Signature not yet current, or a date in the credential scope that does not match the request. For presigned URLs, AWS still lists clock drift and an expired URL among the first things to check.

For presigned S3 URLs, AWS's list is: system clock synchronised, the URL used exactly as generated, not expired, the same Content-Type as when it was generated, the bucket's correct Region, and the URL quoted in curl so the shell does not mangle it. The method, headers and query string must all match what was signed.

How to tell this is your problem
WhereWhat you see
AWS response (403)<Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
SDK or CLIAn error occurred (SignatureDoesNotMatch) when calling the OPERATION operation: The request signature we calculated does not match the signature you provided
Presigned upload from a browser or curl403 on PUT, while the same URL works from the code that generated it

How to know it is actually fixed

  • The request returns 200 (or the expected status) instead of 403.
  • For presigned uploads, the same client that failed can upload with the regenerated URL and matching Content-Type.

Stopping it happening again

  • Generate presigned upload URLs with the exact Content-Type the client will send, and document it for whoever uses the URL.
  • Keep clocks synchronised with NTP on every machine that signs requests.
  • Use SDKs, not hand-written signing, and roles instead of long-lived keys.

Security considerations

  • Never paste the secret access key into a ticket, a log or a chat to debug this. Test it locally with the AWS CLI instead.
  • Do not disable certificate checks or route around a proxy permanently to make signatures match; find the header the proxy rewrites and exclude that traffic properly.

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it