Skip to content
AnyStorage
Free Download

S3 Error Codes

MinIO SignatureDoesNotMatch: Causes and Fixes

SignatureDoesNotMatch from MinIO has seven realistic causes: the secret, the clock, the region, the addressing style, a proxy, the key encoding, a tampered presigned URL.

What MinIO means by SignatureDoesNotMatch, how to tell it apart from the neighbouring errors it is confused with, and the check for each of the seven causes.

signaturedoesnotmatch minio, the request signature we calculated does not match, minio signature error

The response, word for word

xml

<Error>
  <Code>SignatureDoesNotMatch</Code>
  <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
</Error>

That Message is not paraphrased: it is the exact string MinIO's server carries for this code, returned with HTTP status 403. SDKs wrap it differently — botocore.exceptions.ClientError, S3Error, a bare 403 in a log line — but the server said the same sentence to all of them.

The meaning is narrow and useful. Your access key was found, the request was parsed, and then MinIO recomputed the Signature Version 4 signature over what it received and got different bytes than you sent. The question is never "is my password wrong", it is "what did the server see that I did not send". Seven things change that answer.

Symptom, cause and fix for SignatureDoesNotMatch on MinIO
What you observeCauseFix
Fails from every client, immediatelyWrong secret key, or trailing whitespace in itRe-set the credential from a file with no newline
Works on one machine, fails on anotherClock skew beyond 15 minutesSync time; expect RequestTimeTooSkewed if you are lucky
us-east-1 works, your region does notRegion in the credential scope differs from the server'sMatch MINIO_SITE_REGION or leave the client region empty
GET works, PUT fails; or bucket-in-hostname failsVirtual-hosted addressing against a path-style serverForce path addressing
Direct to port 9000 works, through nginx it failsA proxy rewriting Host or other signed headersPass Host through untouched
Only keys with spaces, + or non-ASCII failThe path was re-encoded between client and serverStop the proxy decoding the path
A presigned URL fails after you edited itAny change after signingRegenerate; do not append query parameters

1. Prove the secret, not the key ID

MinIO has a separate error for a bad key ID: InvalidAccessKeyId, with the message "The Access Key Id you provided does not exist in our records." If you are seeing SignatureDoesNotMatch instead, the ID matched something. Only the secret half, or the signing, is wrong.

The usual culprit is copy-paste damage: a newline, a trailing space, a shell that ate a $. Set the alias explicitly and test one call:

sh

mc alias set local http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
mc ls local/

MINIO_ROOT_USER and MINIO_ROOT_PASSWORD are the variable names MinIO reads for the initial root credentials, and MINIO_ROOT_USER_FILE / MINIO_ROOT_PASSWORD_FILE read the same values from a secret file — the better habit, because a file avoids shell quoting entirely.

2. Check the clock before you change anything else

Signature Version 4 puts a timestamp inside the signed data, so a wrong clock produces a wrong signature. MinIO allows a fixed window: its source defines globalMaxSkewTime = 15 * time.Minute // 15 minutes skew allowed. MinIO's own software checklist says the same thing about a cluster: "All servers in a distributed deployment must have clocks synchronized to within 15 minutes of each other", with ntp, timedatectl or timesyncd as the recommended tools.

sh

date -u                          # on the client
timedatectl status | head -5     # on the server

When MinIO catches the skew itself it answers RequestTimeTooSkewed — "The difference between the request time and the server's time is too large." — which is the friendlier outcome. A container with a frozen clock or a VM without NTP can also land just inside the window and still fail.

3. Match the region, or stop sending one

The region is part of the SigV4 credential scope, so us-east-1 and eu-central-1 produce different signatures for identical requests. MinIO's current setting is MINIO_SITE_REGION (mc admin config set ALIAS site region="us-east-1"); MINIO_REGION and MINIO_REGION_NAME still exist in the source as legacy names.

MinIO also has a dedicated complaint when it can tell: AuthorizationHeaderMalformed, "The authorization header is malformed; the region is wrong; expecting 'us-east-1'." Read the expected value out of that message and give your client exactly that.

4. Path-style versus virtual-hosted

A plain MinIO deployment answers http://host:9000/bucket/key. It only treats the bucket as part of the hostname when you set MINIO_DOMAIN, which is the variable its source parses into the list of virtual-host domain names. Point a virtual-hosted client at a server without that setting and the host it signs is not the host MinIO expects to see.

The AWS CLI documents three values for the addressing style — path, virtual and auto — and states "The default value in the CLI is to use auto, which will attempt to use virtual where possible, but will fall back to path style if necessary."

sh

aws configure set default.s3.addressing_style path
aws --endpoint-url http://127.0.0.1:9000 s3 ls s3://my-bucket/

5. The reverse proxy is the usual villain

host is a signed header. A proxy that rewrites it — to localhost, to an upstream name, to a different port — changes the string to sign after the client signed it, and MinIO computes a different signature. In nginx the relevant line is proxy_set_header Host $http_host;, which forwards the original header instead of substituting the upstream name.

Two more lines come from MinIO's load-balancing guidance: client_max_body_size 0; to "Allow unlimited upload size", plus proxy_request_buffering off; and proxy_buffering off;. Buffering does not break signatures, but it breaks large transfers on the same afternoon and gets blamed on them.

The same class of bug explains keys with spaces or +: if anything in the path normalises or re-decodes them, the canonical request no longer matches. Test with a plain-ASCII key; if that works and the odd one does not, the path is being rewritten in transit.

6. Presigned URLs: signed means frozen

A presigned URL carries X-Amz-SignedHeaders and a signature over the method, path, query and expiry. Appending a parameter, switching http to https, changing the port or re-encoding the path all invalidate it, as does rotating the credential that signed it.

An *expired* URL is a different message: code AccessDenied, description "Request has expired". So SignatureDoesNotMatch on a presigned URL means something rewrote it — a link shortener, an email client, a CDN appending tracking parameters.

Reading the neighbouring errors

MinIO's verbatim descriptions for the errors confused with this one
CodeDescription as MinIO sends itWhat it really tells you
InvalidAccessKeyIdThe Access Key Id you provided does not exist in our records.Wrong key ID, or wrong server
RequestTimeTooSkewedThe difference between the request time and the server's time is too large.Clock, not credentials
AuthorizationHeaderMalformedThe authorization header is malformed; the region is wrong; expecting 'us-east-1'.Region mismatch, with the answer included
InvalidRegionRegion does not match.Same family, terser
XAmzContentSHA256MismatchThe provided 'x-amz-content-sha256' header does not match what was computed.The body changed in transit
AccessDeniedRequest has expiredA presigned URL past its deadline

Connecting to MinIO without signing anything yourself

This is the argument for letting a client do the SigV4 arithmetic. AnyStorage (v0.2.24; macOS 11+, Windows 10+, Ubuntu 20.04+) connects to any S3-compatible endpoint, MinIO included: endpoint URL, access key, secret key, with path-style addressing and a custom region as explicit settings — exactly the two switches behind causes 3 and 4 above. The free tier allows two connections. Mounting goes through a built-in local WebDAV server on http://127.0.0.1:3211, so a MinIO bucket can appear as a drive with no FUSE, WinFsp or macFUSE driver involved.

Why does the same request work with curl but not my SDK?

Because they sign different things. An SDK adds headers — x-amz-content-sha256, checksums, a content type — and any of them may appear in SignedHeaders. Turn on the SDK's debug logging and compare its canonical request with the one you signed by hand.

MinIO logs nothing useful. Where do I look?

The response body is the log. Capture the raw XML rather than the SDK's exception text: the code in it distinguishes six failures the exception flattens into one.

Does this error ever mean the bucket policy is wrong?

No. Authorisation failures arrive as AccessDenied, with "Access Denied." as the description. A signature failure happens before any policy is evaluated.

Is us-east-1 special?

Only by convention. MinIO's malformed-header message names it because it is the common default, and R2 documents that empty values and us-east-1 both alias to its auto region. Any other region works if both sides agree.

Next steps