Skip to main content

PutBucketCors

PutBucketCors sets a bucket's CORS configuration, replacing every rule it had with the set in the request. Changing one rule therefore means reading the current set with GetBucketCors, editing it and sending the whole set back; removing every rule is the work of DeleteBucketCors. The dashboard and this operation edit the same rules. CORS rules explains the rule format, the limits and how rules are stored.

Good to know on STRG
  • Changing CORS rules is the work of an access key for all buckets holding Manage buckets.
  • STRG checks each rule as it is saved, so a typo shows up right away rather than as a puzzling browser error later. A bucket holds up to 100 rules, MaxAgeSeconds goes up to 86,400, and each rule has a budget of 1,000 bytes.
  • Rules are stored in a tidy, consistent form: ID is set aside, origins and header names are lowercased, and methods may come back in a different order.
  • On a public bucket, anonymous readers can still get answers carrying the old CORS headers for up to 5 minutes after the change.
  • x-amz-expected-bucket-owner is welcome and has nothing to check.

When you'd use it​

  • A web app lets people upload a profile photo straight from the browser, with a presigned URL. The browser only sends that upload if the bucket's CORS rules allow the app's origin, so the app's deploy script sets them with PutBucketCors.
  • A team adds a staging site next to production. Its deploy script reads the current rules, adds the staging origin and writes the full set back, so both sites keep working.
  • A team keeps its bucket configuration in version control, and a release job writes the rules from the repository to the bucket, so what the repository says is what the bucket does.
  • An agent building a small static site for someone sets the rules its pages need to fetch images and data from the bucket.

Request​

PUT /?cors HTTP/1.1
Host: my-bucket.s3.eu-central-1.strg.com
Authorization: AWS4-HMAC-SHA256 Credential=...
x-amz-date: 20260926T120000Z
x-amz-content-sha256: <SHA-256 of the body, hex>
Content-MD5: <MD5 of the body, base64>
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>https://www.example.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
<ExposeHeader>ETag</ExposeHeader>
<MaxAgeSeconds>3600</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>

Path-style, the same request is PUT /my-bucket?cors on s3.eu-central-1.strg.com. Addressing describes both styles.

Permissions​

The access key needs Manage buckets (buckets:write), a permission that belongs to keys for all buckets. A key without it, or a key limited to certain buckets, gets 403 AccessDenied. Access keys has the details.

URI parameters​

NameTypeRequiredDescription
BucketStringYesThe bucket whose CORS configuration is set.
corsFlagYesSelects the CORS subresource. Sent without a value.

STRG answers any other query parameter with 501 NotImplemented, so a client knows straight away that the request was not what it meant.

Request headers​

NameTypeRequiredDescription
Content-MD5StringOne integrity header is requiredThe base64 MD5 digest of the body. STRG checks it and answers 400 BadDigest if it does not match.
x-amz-checksum-crc32, x-amz-checksum-crc32c, x-amz-checksum-crc64nvme, x-amz-checksum-sha1, x-amz-checksum-sha256StringOne integrity header is requiredA checksum of the body, which works in place of Content-MD5.
x-amz-sdk-checksum-algorithmStringNoThe algorithm of the x-amz-checksum-* header the SDK sends. Accepted.
x-amz-expected-bucket-ownerStringNoWelcome, and has nothing to check: an access key only ever reaches its own account's buckets.

Every request carries either Content-MD5 or an x-amz-checksum-* header, and one with neither gets 400 InvalidRequest. The AWS CLI and SDKs add one automatically.

The body arrives whole. STRG answers a streaming aws-chunked upload of the body with 501 NotImplemented; the AWS CLI and SDKs never send one for this operation.

Request body​

A CORSConfiguration document of at most 2 MiB, holding 1 to 100 CORSRule elements. The document is plain XML: a comment, CDATA section, DOCTYPE, processing instruction or namespace prefix gets 400 MalformedXML.

ElementTypeRequiredDescription
CORSConfigurationContainerYesThe root element.
CORSRuleContainerYesOne rule, repeated for each. 1 to 100.
AllowedOriginStringYes, at least one per ruleAn origin the rule allows. * for every origin, alone in its rule; https for every HTTPS origin; or http:// or https:// plus a host and an optional port, with no path and no trailing slash. The host may be *, or start with one *. label, as in https://*.example.com. Stored lowercased.
AllowedMethodStringYes, at least one per ruleGET, HEAD, PUT, POST or DELETE, in capitals. Any other method gets 400 InvalidRequest.
AllowedHeaderStringNoA request header the rule allows. * allows any and is then the only one; a trailing * matches a prefix, as in x-amz-*. Stored lowercased.
ExposeHeaderStringNoA response header browsers may expose to scripts. Named in full, without *. Stored lowercased.
MaxAgeSecondsIntegerNoHow long a browser may cache the preflight answer, in seconds. 0 to 86,400.
IDStringNoAccepted and set aside, so it is never returned. Configurations copied from elsewhere work as they are.

Each origin, method and header appears once per rule; origins and headers compare case-insensitively, so https://Example.com and https://example.com count as the same. Each rule has a budget of 1,000 bytes, made up of a fixed overhead per rule and the length of every origin, method and header - the rule size budget shows how it is counted. An element not in this table gets 400 InvalidRequest, so a misspelt name shows up straight away instead of being quietly dropped.

Response​

A successful request answers 200 OK with no body. The new rules replace the old ones.

The rules apply on the S3 endpoint:

  • A browser preflight (OPTIONS) is answered from them. A preflight no rule allows - or for a bucket that does not exist - gets 403 AccessForbidden.
  • Responses to an allowed origin carry the matching Access-Control-* headers.
  • On a bucket with Bucket Protection off, an anonymous read may be answered from STRG's cache, with the CORS headers of the rules in force when it was cached, for up to 5 minutes after the change. STRG keeps no anonymous answer longer than that, and caches one only when the object's own Cache-Control allows a shared cache to keep it. Anonymous reads has the details.
  • The STRG dashboard reaches your buckets whatever the rules say, so a rule set that leaves out the dashboard never locks you out of your own bucket.

Response headers​

NameDescription
x-amz-request-idThe request ID, for support requests.

Response body​

None.

Errors​

These are the errors this operation can answer, and the situation behind each.

CodeHTTP statusWhen
InvalidRequest400A rule goes past one of the limits above - its message names the rule and the field - or the document contains an element or method STRG does not take, or the request carries neither Content-MD5 nor an x-amz-checksum-* header.
MalformedXML400The body is not well-formed XML, its root is not CORSConfiguration, it contains no CORSRule, or it uses a comment, CDATA section, DOCTYPE, processing instruction or namespace prefix.
BadDigest400Content-MD5 does not match the body.
XAmzContentSHA256Mismatch400x-amz-content-sha256 does not match the body.
MaxMessageLengthExceeded400The body is larger than 2 MiB.
NoSuchBucket404The bucket does not exist, it is in a region other than the endpoint's, or your account has no storage in this endpoint's region yet.
AccessDenied403The access key lacks Manage buckets, is limited to certain buckets, or your account is not active.
InvalidAccessKeyId403The access key does not exist, has expired or been revoked, or was created so recently that it is not yet usable in this endpoint's region - a moment later it is.
SignatureDoesNotMatch403The request signature is wrong.
RequestTimeTooSkewed403The request time is more than 15 minutes from the server's.
AuthorizationHeaderMalformed400The signature's credential scope names a region other than the endpoint's.
NotImplemented501The request carries a query parameter this operation does not take, or sends the body as a streaming aws-chunked upload.
SlowDown503Too many requests at once; a retry after the Retry-After interval goes through.
ServiceUnavailable503A temporary failure; a retry usually succeeds.

Differences from Amazon S3​

A few things work a little differently on STRG, each listed here with what it means for your requests.

AspectAmazon S3STRG
Permissions3:PutBucketCORS, granted per identityManage buckets, on an access key for all buckets
Anonymous reads after a changeNot applicableOn a public bucket, cached anonymous answers can carry the old CORS headers for up to 5 minutes
Number of rulesLimited by a 64 KB documentUp to 100 rules
Size of a ruleNone beyond the 64 KB documentUp to 1,000 bytes per rule, counted as the rule size budget describes
MaxAgeSecondsAny non-negative integer0 to 86,400
AllowedOriginAny string with at most one **, https, or http(s)://host[:port] with * as the whole host or its first label
AllowedMethodGET, PUT, HEAD, POST, DELETEThe same five
Rule IDStored and returnedSet aside when the rules are saved
Case of origins and headersStored as sentLowercased
Order of AllowedMethodStored as sentNot guaranteed, so comparing methods as a set is the reliable check
Bucket in another region301 redirect with x-amz-bucket-region404 NoSuchBucket. Each bucket answers on its own regional endpoint.
x-amz-expected-bucket-ownerChecked against the bucket ownerWelcome, with nothing to check: a key only ever reaches buckets of its own account

Examples​

Each example lets pages on https://www.example.com read and upload objects in my-bucket. The CLI reads the rules from a file in the same JSON form the dashboard edits, wrapped in CORSRules:

cors.json
{
"CORSRules": [
{
"AllowedOrigins": ["https://www.example.com"],
"AllowedMethods": ["GET", "HEAD", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
}
aws s3api put-bucket-cors \
--bucket my-bucket \
--cors-configuration file://cors.json \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1

The command prints nothing on success.

Reading the rules back with GetBucketCors returns etag rather than ETag, because header names are stored lowercased.

When a rule goes past a limit, nothing is saved and the bucket keeps its previous rules. The error message names the rule and the field - here, for a MaxAgeSeconds of 90000 in the first rule:

An error occurred (InvalidRequest) when calling the PutBucketCors operation: Invalid CORS configuration - Rule 1, MaxAgeSeconds: at most 86400 seconds (one day).
View as Markdown