Object Storage - Conditional writes
Find out how to use If-Match and If-None-Match conditional headers to prevent overwrites and avoid race conditions on your OVHcloud Object Storage buckets
Objective
This guide explains how to use the If-Match and If-None-Match HTTP conditional headers on PutObject, DeleteObject, and CompleteMultipartUpload requests on your OVHcloud Object Storage buckets.
Requirements
- A Public Cloud project in your OVHcloud account
- An Object Storage user already created
- AWS CLI installed and configured
Instructions
Why use conditional writes?
Object Storage is inherently concurrent - multiple application instances, microservices, or background jobs may read and write the same object at the same time. Without coordination, concurrent writes can silently overwrite each other, causing data loss that is difficult to detect and reproduce.
Conditional writes solve this problem at the storage layer, without requiring an external coordination service such as a database or a distributed lock manager. By attaching a precondition to an S31 API call, you ensure the operation only proceeds if the object is in the expected state at the moment the request is evaluated.
Key benefits:
- Data integrity in concurrent environments: Prevent a write from silently overwriting changes made by another process between your read and your write (a classic check-then-act race condition).
- Distributed locking primitive: Implement a lightweight, storage-native lock using
If-None-Match: *onPutObject- only the first writer succeeds; all others receive412 Precondition Failed. - Atomic compare-and-swap: Read an object's ETag, modify it, then write back with
If-Match: <original_etag>. The write is rejected if the object was modified in between, giving you a safe optimistic concurrency pattern. - No extra infrastructure: Coordination happens inside the Object Storage service itself - no additional databases, queues, or lock managers required.
- Any S3-compatible client: The conditional headers are part of the HTTP standard (RFC 9110) and the S3 API - they work with AWS CLI, any S3 SDK, and raw HTTP calls.
Overview
Conditional requests allow you to attach a precondition to an S3 API call based on the current state of the target object. OVHcloud Object Storage evaluates the condition atomically before executing the operation. If the condition is not met, the request is rejected without modifying any data.
Two HTTP headers are supported, based on RFC 9110:
Supported operations:
ETag format: When using If-Match, provide the ETag as a bare string without surrounding quotes - for example d41d8cd98f00b204e9800998ecf8427e, not "d41d8cd98f00b204e9800998ecf8427e".
To retrieve an object's current ETag, use aws s3api head-object --bucket <bucket_name> --key <object_key> and read the ETag field from the response, stripping the surrounding quotes.
You cannot send both If-Match and If-None-Match in the same request. Doing so returns an HTTP 400 Bad Request error.
Response codes
Versioning and delete markers
All conditions are evaluated against the current version of the object, regardless of whether versioning is enabled on the bucket.
A delete marker in a versioned bucket is not considered an existing object:
If-Match(with an ETag or*) →404 Not Foundwhen the current version is a delete marker.If-None-Match: *→ succeeds when the current version is a delete marker (treated as non-existent).
Encryption support: Conditional writes are supported for unencrypted objects and objects encrypted with SSE-S3. Support for SSE-C encrypted objects will be available in a future release.
PutObject - conditional write
Add a precondition to an object upload to prevent accidental overwrites or to implement first-write-wins logic.
Use cases:
- First-write-wins / distributed lock: Multiple processes race to create the same object. Only the first call with
If-None-Match: *succeeds - all others receive412. - Safe overwrite: Read an object, modify it, write it back with
If-Match: <original_etag>. If another client modified the object in between, the write is rejected. - Idempotent initialisation: Create a configuration object without risk of overwriting an existing version.
Successful upload:
Rejected upload (condition not satisfied):
Versioning behaviour for PutObject
DeleteObject - conditional delete
Delete an object only if the ETag condition is satisfied. Only If-Match is supported for DeleteObject - If-None-Match is not applicable.
Use cases:
- Safe delete: Read an object's ETag, then delete it with
If-Match: <etag>. The delete is rejected if the object was modified between your read and your delete. - Delete-if-exists: Use
If-Match: *to delete an object only if it currently exists.
When --version-id is specified, the ETag is evaluated against that specific version, not the current version.
In a versioned bucket, a successful DeleteObject creates a delete marker as the new current version - it does not permanently delete the object. To permanently delete a specific version, include --version-id in the request.
Versioning behaviour for DeleteObject
CompleteMultipartUpload - conditional finalisation
Attach a precondition to the final step of a multipart upload. The condition is evaluated at the time CompleteMultipartUpload is called, not when CreateMultipartUpload was initiated.
Use cases:
- Atomic large-file replacement: Begin a multipart upload of a large replacement file. At finalisation, use
If-Match: <etag>to ensure the target object has not been modified during the upload. - First-write-wins for large files: Use
If-None-Match: *at finalisation so that only one concurrent multipart upload permanently creates the object.
If you receive a 409 ConditionalRequestConflict error on CompleteMultipartUpload, you must restart the entire multipart upload sequence:
- Call
AbortMultipartUploadto clean up the in-progress upload. - Retrieve the current ETag with
HeadObject. - Call
CreateMultipartUploadto start a new upload. - Re-upload all parts with
UploadPart. - Retry
CompleteMultipartUploadwith the updated condition.
Versioning behaviour for CompleteMultipartUpload
Important considerations
- Atomicity: The condition check and the operation execute as a single atomic unit. No concurrent operation can alter the object between the check and the write.
- Single header per request: You cannot combine
If-MatchandIf-None-Matchin the same request - this results in400 Bad Request. - 409 retry for PutObject / DeleteObject: On
409 ConditionalRequestConflict, re-fetch the object's current ETag withHeadObjectand retry your request with the updated value. - No IAM changes required: No additional permissions are needed. The existing
s3:PutObjectands3:DeleteObjectpermissions are sufficient. - Object Lock compatibility: Conditional headers are evaluated independently from Object Lock (WORM) rules. Both constraints apply.
Go further
Join our community of users.
1: S3 is a trademark of Amazon Technologies, Inc. OVHcloud's service is not sponsored by, endorsed by, or otherwise affiliated with Amazon Technologies, Inc.