Ask
79
@boundaries_bo ·

r2 multipart upload from the browser fails cors preflight but curl works fine R2

Presigned PUT straight from the browser to R2, signing with aws4fetch in a Worker. Browser says:

Access to fetch at 'https://...r2.cloudflarestorage.com/...' has been blocked by CORS policy: Response to preflight request doesn't pass access control check

The bucket CORS rules have AllowedOrigins: ["*"] and AllowedMethods: ["PUT"]. The exact same signed URL works from curl. Single-part uploads under 5 MB were fine, this started when I switched to multipart for large files.

9 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @seedstart_sim · last mo. · 3 replies

    curl does not send a preflight, so "it works in curl" tells you nothing at all about CORS. Useful for checking the signature, useless for checking this.

    The preflight is failing on headers, not on origin or method. The browser sends Access-Control-Request-Headers listing everything your request will carry, and multipart signing adds x-amz-content-sha256, x-amz-date, authorization and content-type on top of whatever else. If those are not in AllowedHeaders, the preflight is rejected before the upload is even attempted.

    Then the one that catches people immediately after they fix that:

    "ExposeHeaders": ["ETag"]
    

    Without it every part upload succeeds and the completion call fails, because JavaScript cannot read the ETag header it needs to send back for each part. It looks like a completely different bug and it is the same config block.

    Also add POST to AllowedMethods — multipart initiate and complete are POSTs, not PUTs.

    58
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @boundaries_bo · last mo.

      ExposeHeaders was it, second half exactly as described — preflight passed, all six parts uploaded, complete threw because every ETag came back null. Would not have connected those two things on my own.

      22
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @ridgeline_rowan · last mo.

      Worth knowing CORS rule changes take a minute or two to propagate, so do not conclude your fix failed after one immediate retry. I have reverted two correct fixes that way.

      17
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @deploy_friday_ok · last mo. · 2 replies

    Alternative that deletes this whole category of problem: upload through a Worker with an R2 binding instead of presigning. No signing, no CORS matrix, no x-amz-* guessing, and authorisation happens in the same place as the rest of your auth.

    Costs you Worker CPU and it is not the right answer for every file size, but for the common case it is a smaller system.

    36
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @visa_run_val · last mo.

      Good up to a point — past a hundred megabytes or so you want the direct multipart path anyway, because request body handling and CPU limits start deciding things for you. Fine rule: through the Worker for user avatars and documents, direct for video.

      14
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @orm_tamsin · last mo.

    Separate landmine: AllowedOrigins: ["*"] is silently ignored by browsers on any request sent with credentials. If anything in this flow ever carries a cookie you will get a preflight failure that looks identical to what you have now and no config change will fix it until you list the origin explicitly.

    24
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @edge_runtime_bo · last mo.

    Cost note while you are in there: multipart bills a Class A operation per part, plus initiate and complete. 5 MB parts on a 2 GB file is 400-odd Class A ops for one upload. Unless you specifically need fine-grained resume, use much bigger parts — 64 or 128 MB — and your operation count drops by an order of magnitude.

    18
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @first_repo_finn · last mo. · 2 replies

    Put a custom domain on the bucket and CORS stops being a problem entirely.

    12
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @seedstart_sim · last mo.

      Only if that domain is same-origin with the page doing the upload. files.example.com from app.example.com is still cross-origin and still preflights. It does help with cookie and caching setups, just not with this.

      9
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report