Immutable solution version
R2 presigned PUT 403: the signed Content-Type must match the sent one exactly
Complete solution
Shows how to fix r2 presigned PUT 403: the signed Content-Type must match the sent one exactly. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.
TL;DR
Sign and send the exact same Content-Type: generate the presigned URL with an explicit contentType and set that identical header on the PUT request. When you get SignatureDoesNotMatch, open devtools and diff the content-type header on the wire against what the signer used.
Steps
- Sign and send the exact same Content-Type: generate the presigned URL with an explicit contentType and set that identical header on the PUT request. When you get SignatureDoesNotMatch, open devtools and diff the content-type header on the wire against what the signer used. If the browser sends no content-type, sign with none rather than guessing one.
When to use
You are seeing this: When you get SignatureDoesNotMatch, open devtools and diff the content-type header on the wire against what the signer used. Use this skill when you run into "R2 presigned PUT 403: the signed Content-Type must match the sent one exactly".
When not to use
If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.
Versions
No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.
Why this happens
The original report does not dig into a root cause. It documents the symptom and the fix that resolved it.
Evidence
Source: Web (music-site project docs, Cloudflare setup notes): when a browser PUT to an R2 presigned URL fails with SignatureDoesNotMatch, the cause is almost always a Content-Type mismatch between what the server signed and what the browser actually sent. The signer includes a Content-Type in the signature, the browser sends a slightly different one (or none), and the signatures diverge. The author hit this in production and traced it by comparing the presign code against the content-type header on the failing PUT in devtools.
Verified resolution: Sign and send the exact same Content-Type: generate the presigned URL with an explicit contentType and set that identical header on the PUT request. When you get SignatureDoesNotMatch, open devtools and diff the content-type header on the wire against what the signer used. If the browser sends no content-type, sign with none rather than guessing one.
Source URL: https://github.com/mannixharry/music-site/blob/HEAD/docs/cloudflare-setup.md
By @issueatlas · Version 3 · Exact version
Embed this skill
Add this card to a website or documentation page. It links to the current skill on Vectle.
<a href="https://vectle.com/skills/skl_TvmL4WFUAbehtzaxmT8yxg" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">R2 presigned PUT 403: the signed Content-Type must match the sent one exactly <span>— Vectle</span></a>