PutBucketLifecycleConfiguration
PutBucketLifecycleConfiguration sets a bucket's lifecycle configuration: the rules that expire current object versions, delete noncurrent versions and abort incomplete multipart uploads. The request replaces the bucket's whole configuration, rules set in the dashboard included - the dashboard shows and edits the same rules. The deprecated PutBucketLifecycle sends exactly the same request, so this operation is the better choice. Lifecycle policies explains what the rules do.
- Rules hold the three actions STRG offers -
ExpirationwithDays,NoncurrentVersionExpirationwithNoncurrentDaysandAbortIncompleteMultipartUpload- each filtered by key prefix. STRG answers anything else with501 NotImplemented, so a client knows straight away. - Each prefix has one rule, which carries all of its actions. Prefixes stay apart - no prefix starts with another - and a rule for the whole bucket is the bucket's one rule, so it is always clear which rule applies to an object.
- A bucket holds up to 100 rules.
- Every rule with
Expirationgets a companion rule from STRG that tidies away expired delete markers, and GetBucketLifecycleConfiguration returns it. x-amz-expected-bucket-owneris welcome and has nothing to check, andx-amz-transition-default-minimum-object-sizeis set aside, as it concerns storage-class transitions.
NoncurrentVersionExpiration deletes object versions permanently; they cannot be restored.
Every bucket keeps versions, so a rule with Expiration alone frees no storage: an expired object
stays on as a noncurrent version, billed as stored data, until a NoncurrentVersionExpiration
rule removes it or it is deleted.
Keeping 30 days of old versions
shows a rule that does both.
When you'd use it
- Keeping storage costs in check. Every version of every object is billed as stored data, so a team whose documents change daily keeps the last 30 days of old versions and lets STRG delete the rest - the Keeping 30 days of old versions recipe.
- Tidying up after interrupted uploads. A mobile app uploading large videos in parts leaves
unfinished uploads behind whenever a phone loses its connection, and their parts are billed
too. A rule with
AbortIncompleteMultipartUploadclears them away after a day - Cleaning up abandoned multipart uploads. - Scratch space and logs. An agent keeping intermediate results under
tmp/, or a service writing logs underlogs/, has that data expire on its own after a few days - Expiring atmp/prefix. - Settings kept in code. A deploy script or infrastructure tool that manages each bucket's settings writes the rules from the repository, so what the repository says is what the bucket does.
Request
PUT /?lifecycle 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>
Content-MD5: <base64 MD5 of the body>
Content-Type: application/xml
<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Rule>
<ID>expire-logs</ID>
<Filter>
<Prefix>logs/</Prefix>
</Filter>
<Status>Enabled</Status>
<Expiration>
<Days>30</Days>
</Expiration>
<NoncurrentVersionExpiration>
<NoncurrentDays>7</NoncurrentDays>
</NoncurrentVersionExpiration>
</Rule>
</LifecycleConfiguration>
Path-style, the same request is PUT /my-bucket?lifecycle on s3.eu-central-1.strg.com.
Addressing in the S3 API overview has both styles.
Permissions
The access key needs Manage lifecycle rules (bucket-lifecycle:write). A key limited to
certain buckets works too, as long as this bucket is one of them. A key prefix does not narrow
this operation, because the configuration belongs to the whole bucket.
Access keys has every permission.
URI parameters
| Name | Type | Required | Description |
|---|---|---|---|
Bucket | String | Yes | The bucket whose lifecycle configuration is set. |
lifecycle | Flag | Yes | Selects the lifecycle subresource. Sent without a value. |
STRG answers 501 NotImplemented to any other query parameter, so a client knows straight
away. The X-Amz-* parameters of a presigned URL and the x-id parameter the JavaScript SDK
adds are welcome.
Request headers
| Name | Type | Required | Description |
|---|---|---|---|
Content-MD5 | String | One integrity header is required | The 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-sha256 | String | One integrity header is required | A checksum of the body, which works in place of Content-MD5. The AWS SDKs and CLI send one by default. |
x-amz-sdk-checksum-algorithm | String | No | The algorithm of the x-amz-checksum-* header the SDK sends. Accepted. |
x-amz-expected-bucket-owner | String | No | Welcome, with nothing to check: an access key only ever reaches its own account's buckets. |
x-amz-transition-default-minimum-object-size | String | No | Set aside. It concerns storage-class transitions, and STRG has one storage class. |
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 LifecycleConfiguration element holding up to 100 Rule elements, at most 2 MiB. The document
is plain XML: a comment, CDATA section, DOCTYPE, processing instruction, namespace prefix or
attribute other than xmlns gets 400 MalformedXML. A configuration with no rules is
400 MalformedXML too - removing every rule is the work of
DeleteBucketLifecycle.
| Element | Required | Description |
|---|---|---|
Rule | Yes | One lifecycle rule. Up to 100, not counting companion rules. |
Rule/ID | No | The rule's ID, at most 255 characters, unique within the configuration. For a rule with Expiration/Days, the ID plus the _marker suffix of its companion rule also fits in 255 characters and stays clear of every other ID. A rule sent without one gets an ID generated by STRG. The ID is kept, including across a save in the dashboard. |
Rule/Status | Yes | Enabled. STRG answers Disabled with 501 NotImplemented; a rule that should stop applying is simply left out of the configuration. |
Rule/Filter | No | The objects the rule applies to. An empty or missing Filter means every object in the bucket. |
Rule/Filter/Prefix | No | The key prefix the rule applies to, at most 1,024 bytes, without control characters. It does not overlap another rule's prefix - one prefix never starts with another. |
Rule/Filter/Tag | No | STRG answers 501 NotImplemented: rules here filter by key prefix. |
Rule/Filter/ObjectSizeGreaterThan, Rule/Filter/ObjectSizeLessThan | No | STRG answers 501 NotImplemented: rules here filter by key prefix. |
Rule/Filter/And | No | STRG answers 501 NotImplemented: rules here filter by key prefix. |
Rule/Prefix | No | The deprecated top-level prefix. STRG answers 501 NotImplemented; Filter/Prefix sets the same thing. |
Rule/Expiration/Days | No | Days after an object is created when its current version becomes a noncurrent version (a delete marker is placed on top of it). A whole number of at least 1. |
Rule/Expiration/Date | No | STRG answers 501 NotImplemented; Days sets an expiration. |
Rule/Expiration/ExpiredObjectDeleteMarker | No | true is accepted in a rule on the same prefix as a rule with Expiration/Days, and is then replaced by the companion rule STRG adds - its ID is not kept. Anywhere else, or false, STRG answers 501 NotImplemented. |
Rule/NoncurrentVersionExpiration/NoncurrentDays | No | Days after a version becomes noncurrent when it is permanently deleted. A whole number of at least 1. |
Rule/NoncurrentVersionExpiration/NewerNoncurrentVersions | No | STRG answers 501 NotImplemented. |
Rule/AbortIncompleteMultipartUpload/DaysAfterInitiation | No | Days after a multipart upload starts when it is aborted if not completed. A whole number of at least 1. |
Rule/Transition | No | STRG answers 501 NotImplemented: there is one storage class, so nothing to move objects between. |
Rule/NoncurrentVersionTransition | No | STRG answers 501 NotImplemented, for the same reason. |
A day count that is not a whole number of at least 1 gets 501 NotImplemented. A rule with no
action at all is set aside.
The companion rule. For every rule with Expiration/Days, STRG stores a second rule on
the same prefix, with the ID <ID>_marker and Expiration/ExpiredObjectDeleteMarker set to
true, which removes a delete marker once no versions remain beneath it. There is no need to send
it. GetBucketLifecycleConfiguration returns it, and a
configuration read from there can be sent back unchanged.
Response
A successful request answers 200 OK with an empty body once the configuration is stored. The
new rules replace the old ones.
Response headers
| Name | Description |
|---|---|
x-amz-request-id | The request ID, useful in a support request. |
x-amz-transition-default-minimum-object-size | Relates to storage-class transitions, which STRG has no use for, so this header carries no meaning for a STRG bucket. |
Response body
None.
Errors
These are the answers a client can get instead of 200, and the situation behind each.
| Code | HTTP status | When |
|---|---|---|
MalformedXML | 400 | The body is not well-formed XML, its root is not LifecycleConfiguration, it uses a comment, CDATA section, DOCTYPE, processing instruction, namespace prefix or attribute other than xmlns, or it holds no rule with an action. |
InvalidRequest | 400 | The request carries neither Content-MD5 nor an x-amz-checksum-* header. Or the rules go past a limit: more than 100, two rules for one prefix or overlapping prefixes, a whole-bucket rule beside another rule, a prefix over 1,024 bytes, or a control character in a prefix or ID. The message names the problem. |
InvalidArgument | 400 | A rule ID is longer than 255 characters (with _marker, for a rule with Expiration/Days), or two rules share an ID. |
BadDigest | 400 | Content-MD5 does not match the body. |
XAmzContentSHA256Mismatch | 400 | x-amz-content-sha256 does not match the body. |
MaxMessageLengthExceeded | 400 | The body is larger than 2 MiB. |
NotImplemented | 501 | The configuration uses an element STRG does not take (see Request body), the request carries a query parameter this operation does not take, or it sends the body as a streaming aws-chunked upload. |
NoSuchBucket | 404 | The bucket does not exist, it is in a region other than the endpoint's, or the account stores nothing in this endpoint's region yet. |
AccessDenied | 403 | The access key does not hold Manage lifecycle rules, the bucket is outside its scope, or the account is not active. |
InvalidAccessKeyId | 403 | The access key does not exist, has expired or has been revoked - or it was created moments ago and is not yet usable in this region, where the account already stores data. A retry a moment later succeeds. |
SignatureDoesNotMatch | 403 | The request signature does not match. |
RequestTimeTooSkewed | 403 | The request time is more than 15 minutes from the server's. |
AuthorizationHeaderMalformed | 400 | The signature's credential scope names a region other than the endpoint's. |
SlowDown | 503 | Too many requests at once. A retry after the Retry-After interval goes through. |
ServiceUnavailable | 503 | A temporary failure. A retry of the same request is safe. |
Differences from Amazon S3
| Aspect | Amazon S3 | STRG |
|---|---|---|
| Actions | Expiration by days or date, transitions, noncurrent expiration and transitions, NewerNoncurrentVersions, abort incomplete uploads | Expiration/Days, NoncurrentVersionExpiration/NoncurrentDays and AbortIncompleteMultipartUpload - three actions that cover expiring data, old versions and abandoned uploads. The rest gets 501 NotImplemented |
| Filters | Prefix, tags, object size, And | By key prefix |
| Rules per prefix | Any number, overlapping allowed | One rule per prefix, holding all its actions, and prefixes that never overlap; a whole-bucket rule is the bucket's one rule. It is always clear which rule applies to an object |
| Rule count | 1,000 | 100, not counting companion rules |
| Disabled rules | Stored with Status Disabled | 501 NotImplemented. A rule that should stop applying is left out of the configuration |
| Expiration rules | Stored as sent | A companion <ID>_marker rule with ExpiredObjectDeleteMarker is added to every rule with Expiration/Days, so expired delete markers are tidied away without asking; a client's own ExpiredObjectDeleteMarker rule is replaced by it |
Top-level Prefix | Accepted for backward compatibility | 501 NotImplemented. Filter/Prefix sets the same thing |
| Versioning | Rules apply to unversioned or versioned buckets | Every bucket keeps versions, so Expiration leaves a noncurrent version behind; a NoncurrentVersionExpiration action beside it frees the storage |
| Bucket in another region | 301 redirect with x-amz-bucket-region | 404 NoSuchBucket, also when the account stores nothing in the endpoint's region. The bucket's own regional endpoint reaches it |
x-amz-expected-bucket-owner | Checked against the bucket owner | Welcome, with nothing to check: a key only reaches its own account's buckets |
Examples
Expiring objects under logs/ after 30 days, deleting their noncurrent versions 7 days later,
and aborting multipart uploads under uploads/ that are not completed within a day:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
{
"Rules": [
{
"ID": "expire-logs",
"Filter": { "Prefix": "logs/" },
"Status": "Enabled",
"Expiration": { "Days": 30 },
"NoncurrentVersionExpiration": { "NoncurrentDays": 7 }
},
{
"ID": "abort-uploads",
"Filter": { "Prefix": "uploads/" },
"Status": "Enabled",
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 1 }
}
]
}
aws s3api put-bucket-lifecycle-configuration \
--bucket my-bucket \
--lifecycle-configuration file://lifecycle.json \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
s3.put_bucket_lifecycle_configuration(
Bucket="my-bucket",
LifecycleConfiguration={
"Rules": [
{
"ID": "expire-logs",
"Filter": {"Prefix": "logs/"},
"Status": "Enabled",
"Expiration": {"Days": 30},
"NoncurrentVersionExpiration": {"NoncurrentDays": 7},
},
{
"ID": "abort-uploads",
"Filter": {"Prefix": "uploads/"},
"Status": "Enabled",
"AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 1},
},
]
},
)
import { PutBucketLifecycleConfigurationCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
await client.send(
new PutBucketLifecycleConfigurationCommand({
Bucket: "my-bucket",
LifecycleConfiguration: {
Rules: [
{
ID: "expire-logs",
Filter: { Prefix: "logs/" },
Status: "Enabled",
Expiration: { Days: 30 },
NoncurrentVersionExpiration: { NoncurrentDays: 7 },
},
{
ID: "abort-uploads",
Filter: { Prefix: "uploads/" },
Status: "Enabled",
AbortIncompleteMultipartUpload: { DaysAfterInitiation: 1 },
},
],
},
}),
);
Adding a rule to an existing configuration means reading the current rules first, appending the new one and sending the whole list back. The companion rules in what was read can go back as they are:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api get-bucket-lifecycle-configuration \
--bucket my-bucket \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1 \
--query '{Rules: Rules}' > lifecycle.json
With the new rule added to lifecycle.json, the same put-bucket-lifecycle-configuration
command as above sends it.
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
try:
rules = s3.get_bucket_lifecycle_configuration(Bucket="my-bucket")["Rules"]
except s3.exceptions.ClientError as error:
if error.response["Error"]["Code"] != "NoSuchLifecycleConfiguration":
raise
rules = []
rules.append(
{
"ID": "delete-old-versions",
"Filter": {"Prefix": "documents/"},
"Status": "Enabled",
"NoncurrentVersionExpiration": {"NoncurrentDays": 90},
}
)
s3.put_bucket_lifecycle_configuration(
Bucket="my-bucket",
LifecycleConfiguration={"Rules": rules},
)
import {
GetBucketLifecycleConfigurationCommand,
PutBucketLifecycleConfigurationCommand,
S3Client,
} from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
let rules = [];
try {
const output = await client.send(
new GetBucketLifecycleConfigurationCommand({ Bucket: "my-bucket" }),
);
rules = output.Rules ?? [];
} catch (error) {
if (error.name !== "NoSuchLifecycleConfiguration") throw error;
}
rules.push({
ID: "delete-old-versions",
Filter: { Prefix: "documents/" },
Status: "Enabled",
NoncurrentVersionExpiration: { NoncurrentDays: 90 },
});
await client.send(
new PutBucketLifecycleConfigurationCommand({
Bucket: "my-bucket",
LifecycleConfiguration: { Rules: rules },
}),
);
The new rule's prefix stays clear of every existing one. A prefix that overlaps gets
400 InvalidRequest, and the configuration stays as it was.