Most tools that speak S3 can read and write OCI Object Storage through the Amazon S3 Compatibility API. Problems come from four places: the endpoint and region, the customer secret key, the compartment new buckets land in, and AWS SDK defaults that assume real S3. Keep this page next to the migration runbook.
The pieces you need
# 1. Object Storage namespace (tenancy-wide, not the tenancy name)
oci os ns get --query data --raw-output
# 2. Where S3-created buckets land (root compartment by default)
oci os ns get-metadata
# 3. Point S3 and Swift bucket creation at a real compartment
oci os ns update-metadata \
--default-s3-compartment-id ocid1.compartment.oc1..aaaa... \
--default-swift-compartment-id ocid1.compartment.oc1..aaaa...
# 4. Access key / secret key pair for a user
oci iam customer-secret-key create \
--user-id ocid1.user.oc1..aaaa... \
--display-name s3-etl-prod
The create output has id (the access key) and key (the secret key). The secret is shown once. A user can hold only two customer secret keys at a time, so rotate by deleting the older one after clients have moved.
Warning
Changing the default S3 compartment doesn't move existing buckets. It changes where the next
CreateBucketcall lands. Policies scoped to the old compartment stop applying to new buckets, so update IAM at the same time.
Endpoints
| Style | Host |
|---|---|
| Path-style | https://<namespace>.compat.objectstorage.<region>.oraclecloud.com/<bucket>/<object> |
| Virtual-hosted | https://<bucket>.vhcompat.objectstorage.<region>.oci.customer-oci.com/<object> |
Oracle's documentation also lists a customer-oci.com form of the path-style host. Both appear in current examples, so check the S3 Compatibility API page for your realm. Government and sovereign realms use different domains.
Rules that hold either way:
- The region in the hostname and the region used for SigV4 signing must be the OCI region identifier, for example
eu-frankfurt-1. AWS region names likeus-east-1fail. - A bucket is reachable only through its own region's endpoint. There's no global endpoint that redirects.
- Path-style works for every bucket name. Virtual-hosted needs a DNS-compatible bucket name.
IAM: what the key's user needs
The secret key acts as its user. There are no S3 bucket policies or ACLs. Access is plain OCI IAM:
Allow group S3EtlWriters to read buckets in compartment data-lake
Allow group S3EtlWriters to manage objects in compartment data-lake
# Only if tools create buckets (lands in the default S3 compartment)
Allow group S3EtlAdmins to manage buckets in compartment data-lake
Tools that call ListBuckets at startup need inspect buckets in the default S3 compartment, even if they only use one bucket.
AWS CLI v2 profile
~/.aws/config:
[profile oci-fra]
region = eu-frankfurt-1
endpoint_url = https://mynamespace.compat.objectstorage.eu-frankfurt-1.oraclecloud.com
s3 =
addressing_style = path
request_checksum_calculation = when_required
response_checksum_validation = when_required
~/.aws/credentials:
[oci-fra]
aws_access_key_id = <customer-secret-key id>
aws_secret_access_key = <customer-secret-key key>
aws s3 ls s3://landing/ --profile oci-fra
aws s3 cp ./export.parquet s3://landing/2026/11/ --profile oci-fra
aws s3api head-object --bucket landing --key 2026/11/export.parquet --profile oci-fra
Profile-level endpoint_url needs a recent AWS CLI v2. On older builds, pass --endpoint-url on every command.
Checksums: the failure after an SDK upgrade
In early 2025, AWS SDKs and the AWS CLI started calculating CRC-based checksums on uploads by default and sending them as trailing headers. The affected versions include AWS CLI v2 from 2.23, boto3/botocore 1.36 and AWS SDK for Java 2.30. Oracle's A-Team documented that newer AWS CLI releases then fail to create or update objects in OCI Object Storage. The fix is to compute checksums only when the operation requires them:
export AWS_REQUEST_CHECKSUM_CALCULATION=when_required
export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
Or set it in code:
import boto3
from botocore.config import Config
s3 = boto3.client(
"s3",
region_name="eu-frankfurt-1",
endpoint_url="https://mynamespace.compat.objectstorage.eu-frankfurt-1.oraclecloud.com",
aws_access_key_id=ACCESS_KEY,
aws_secret_access_key=SECRET_KEY,
config=Config(
s3={"addressing_style": "path"},
request_checksum_calculation="when_required",
response_checksum_validation="when_required",
),
)
For Java 2.x, set requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED) and responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED) on the S3 client builder. Oracle may add support for these checksums over time, so recheck before you treat this setting as permanent. Pin SDK versions in images so an unplanned rebuild doesn't change this behavior.
Spark and Hadoop s3a
spark.hadoop.fs.s3a.endpoint=https://mynamespace.compat.objectstorage.eu-frankfurt-1.oraclecloud.com
spark.hadoop.fs.s3a.endpoint.region=eu-frankfurt-1
spark.hadoop.fs.s3a.path.style.access=true
spark.hadoop.fs.s3a.access.key=<id>
spark.hadoop.fs.s3a.secret.key=<key>
spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem
Newer hadoop-aws releases are built on the AWS SDK v2 and inherit its checksum defaults. Set the environment variables above on the driver and executors, or use a Hadoop version that exposes the setting. Names vary by version, so check your build's documentation. For OCI-native Spark, the OCI HDFS connector (oci://bucket@namespace/) avoids the S3 layer entirely and authenticates with API keys or resource principals.
rclone
[oci]
type = s3
provider = Other
access_key_id = <id>
secret_access_key = <key>
region = eu-frankfurt-1
endpoint = https://mynamespace.compat.objectstorage.eu-frankfurt-1.oraclecloud.com
force_path_style = true
rclone lsd oci:
rclone copy /data/exports oci:landing/exports --transfers 16 --s3-chunk-size 64M --progress
rclone also has a native Oracle Object Storage backend that uses OCI auth instead of secret keys. Prefer it for bulk jobs running on OCI instances with instance principals.
Error → cause → fix
| Error | Usual cause | Fix |
|---|---|---|
SignatureDoesNotMatch | Signing region is an AWS name, the secret has trailing whitespace, or a proxy rewrote the host | Use the OCI region ID, re-copy the secret, bypass the proxy for the endpoint |
InvalidAccessKeyId | Key deleted, belongs to another tenancy or realm, or it's an API key fingerprint | Run oci iam customer-secret-key list --user-id ... and use the id |
NoSuchBucket | Wrong namespace in the host, bucket in another region, virtual-hosted host used with a non-DNS-compatible bucket name | Use oci os ns get and the bucket's region endpoint, switch to path-style |
AccessDenied on ListBuckets only | User lacks inspect buckets in the default S3 compartment | Add the policy, or configure the tool not to list |
| Bucket created in root | Default S3 compartment never set | oci os ns update-metadata --default-s3-compartment-id ..., then move the bucket |
| PUT fails after an SDK or CLI upgrade, GET still works | Default checksum calculation | when_required settings above |
RequestTimeTooSkewed | Client clock off | Fix NTP. SigV4 is time-sensitive |
NotImplemented on ACL or policy calls | S3 features OCI doesn't implement | Use IAM policies and pre-authenticated requests |
Quick validation
# Same object through the native API and the S3 API: sizes and ETags should line up
oci os object head -bn landing --name 2026/11/export.parquet
aws s3api head-object --bucket landing --key 2026/11/export.parquet --profile oci-fra
Multipart uploads have composite ETags in both APIs. Don't compare them with an MD5 of the file.
Prevent it next time
- Create one dedicated IAM user per S3 integration, scoped to one compartment, with its key stored in OCI Vault.
- Set the default S3 compartment once at tenancy setup and record it in the landing zone documentation.
- Bake
when_requiredchecksum settings into base images and connector configurations, and test SDK upgrades against a scratch bucket before rollout. - Alarm on customer secret key age. Keys don't expire on their own.