S3 compatibility¶
Nexo implements the Amazon S3 API used by the AWS SDKs, the AWS CLI and the most common tools (rclone, restic, s3cmd, Terraform, Velero, …). This page lists the supported APIs, the known differences and the results of the compatibility suite.
Endpoints and authentication¶
| Aspect | Support |
|---|---|
| Addressing | path-style (https://s3.example.com/bucket/key) always; virtual-host (https://bucket.s3.example.com/key) with NEXO_DOMAIN=s3.example.com |
| Region | a single one, us-east-1 by default (--region, NEXO_REGION) |
| Signatures | AWS Signature V4: header, query (presigned URLs, up to 7 days), chunked payload (aws-chunked, with trailers too), POST policies of HTML forms |
| Unsupported signatures | Signature V2 (deprecated by AWS) |
| Temporary credentials | STS AssumeRole (15 minutes – 12 hours) |
| Errors | standard S3 XML, with x-amz-request-id and x-amz-id-2 in every response; unsupported APIs answer 501 NotImplemented |
Supported APIs¶
Buckets: ListBuckets, CreateBucket, HeadBucket, DeleteBucket, GetBucketLocation,
Get/Put/DeleteBucketPolicy, GetBucketPolicyStatus, Get/PutBucketVersioning, Get/Put/DeleteBucketTagging,
Get/Put/DeleteBucketCors, Get/Put/DeleteBucketLifecycleConfiguration, Get/PutObjectLockConfiguration,
Get/Put/DeleteBucketEncryption, Get/PutBucketNotificationConfiguration, ListObjects, ListObjectsV2,
ListObjectVersions, ListMultipartUploads, Get/PutBucketAcl (private only).
Objects: PutObject, GetObject (ranges and conditional requests too), HeadObject, DeleteObject,
DeleteObjects, CopyObject, GetObjectAttributes, Get/Put/DeleteObjectTagging, Get/PutObjectRetention,
Get/PutObjectLegalHold, PostObject, Get/PutObjectAcl (private only).
Multipart: CreateMultipartUpload, UploadPart, UploadPartCopy, CompleteMultipartUpload,
AbortMultipartUpload, ListParts.
STS: AssumeRole.
Features: versioning with delete markers, Object Lock (governance and compliance, default retention,
legal hold), SSE-S3, SSE-KMS and SSE-C encryption, additional checksums (CRC32, CRC32C, CRC64NVME, SHA1,
SHA256, composite ones for multipart too), tags, lifecycle (expirations, noncurrent versions, delete markers,
incomplete uploads), event notifications, CORS, quotas (a Nexo extension).
Not supported¶
They answer 501 NotImplemented: bucket website (?website; the
distributions take its place), PublicAccessBlock (the Nexo
block public access takes its
place), replication (?replication), server access logging, accelerate, analytics, inventory, metrics,
intelligent tiering, ownership controls, request payment, RestoreObject, SelectObjectContent,
GetObjectTorrent. Lifecycle transitions to other storage classes are rejected. Planned after v1.0: full
ACLs, replication between clusters, tiering to remote S3, AssumeRoleWithWebIdentity.
Known differences¶
| Area | Nexo behavior |
|---|---|
| Keys and prefixes | A key cannot also be the prefix of other keys (as in MinIO): with a/b present, writing a/b/c is rejected with XNexoParentIsObject (400); with a/b/c present, writing a/b is rejected with XNexoObjectExistsAsDirectory (400) |
| ACLs | Every bucket behaves like an S3 bucket with Object Ownership BucketOwnerEnforced (the AWS default since 2023): ACLs other than private and explicit grants are rejected; access is granted with policies, anonymous access and access rules |
| Accounts | A single account: users have the rights of their policies, not those of "owner" of the buckets they create; an existing bucket always answers BucketAlreadyOwnedByYou |
| Authorization | A request on an object is authorized before the key is looked up: with only s3:ListBucket a missing key gives 403; with s3:GetObject it gives 404 even without s3:ListBucket |
| Non-ASCII metadata | x-amz-meta-* values are returned with the bytes received (UTF-8), without RFC 2047 encoding |
| CORS | A bucket without a CORS configuration follows the global policy api.cors_allow_origin (default *): the preflight answers 200 instead of S3's 403. A bucket with a CORS configuration behaves as in S3 |
| Notifications | Records in the S3 format with eventSource nexo:s3; the payload adds EventName and Key at the top level (like MinIO); retention and legal hold changes generate no events, as in S3 |
| Slow downloads | A client that stops reading the body of a GET for more than 20 s (NEXO_SEND_TIMEOUT) sees the response aborted (on HTTP/2 the connection that no longer accepts bytes is closed); a client that reads steadily is never aborted, even with many HTTP/2 streams on the same connection, as long as the connection receives at least about 40 KB/s |
| Specific errors | XNexoQuotaExceeded and XNexoStorageFull (507), XNexoParentIsObject and XNexoObjectExistsAsDirectory (400); a set without quorum answers 503 SlowDownWrite/SlowDownRead (XNexoLockTimeout if it cannot get the lock, ServiceUnavailable for an offline drive or node); the SDKs retry 503 responses, SlowDown remains for throttled requests |
| Block public access | Acts on the access rules and on the distributions, not on anonymous bucket policies (see Sharing) |
Otherwise the responses follow S3 even in the details the SDKs check: for example no
x-amz-version-id for the null version, If-Match on a missing key → 404 NoSuchKey, partNumber beyond
the parts → 416, different SSE-C key → 403, repeating a successful CompleteMultipartUpload → same
outcome, parts in strictly increasing order (InvalidPartOrder).
Ceph s3-tests results¶
The ceph/s3-tests suite runs every night in all three modes
(make test-s3compat, test/compat). Latest
published measurement (suite commit 5522d1c, test_s3.py, 838 tests, 2026-10-09):
| Mode | Applicable tests passed |
|---|---|
| Single node, multi drive | 422/422 |
| Cluster | 422/422 |
| Single node, single drive | 421/422 |
- In SNSD
test_atomic_read_4mbfailed in 2 runs out of 4: the suite's client writes the key while it is not yet reading the GET of the same key, and the read lock held during the transfer blocks the write untilNEXO_SEND_TIMEOUTexpires. - Main groups: versioning 28/28, Object Lock 39/39, multipart 31/31, tagging 17/17, SSE-C 18/18, checksums 11/11.
- 415 tests are excluded, each with its reason in
test/compat/excluded.txt: Ceph RGW extensions that AWS itself does not pass (markedfails_on_awsby the suite), full ACLs, Signature V2,PublicAccessBlock, server access logging and the known differences above.
The current status, with any new measurements, is in docs/05 (S3 compatibility status).
Trying a client¶
aws configure set aws_access_key_id admin
aws configure set aws_secret_access_key supersecret
aws --endpoint-url http://localhost:9000 s3 mb s3://test
aws --endpoint-url http://localhost:9000 s3 cp file.txt s3://test/
aws --endpoint-url http://localhost:9000 s3api list-object-versions --bucket test
With rclone:
[nexo]
type = s3
provider = Minio
endpoint = http://localhost:9000
access_key_id = admin
secret_access_key = supersecret
The rclone Minio provider uses path-style addressing, which works without configuring NEXO_DOMAIN.