WL Demo Cloud

S3 SDK setup

PutObject ready

Drop-in snippets per language so your first PutObject succeeds — pre-configured to bypass the AWS SDK ≥ 1.36 streaming-checksum default that causes silent 403s.

The default modern AWS SDK config will 403 on PutObject — and won't tell you why

Since early 2025, botocore, the JS SDK v3, and aws-sdk-go-v2 all default to streaming PUTs with a CRC32 trailer. Our S3-compatible gateway doesn't parse that wire format, so the edge proxy returns a generic 403 Forbidden HTML page with no S3 error code, no request-id, and no x-amz-* headers — looks identical to a permission bug, but it's not.

Every snippet below sets request_checksum_calculation = when_required so PutObject uses the classic non-streaming SigV4 shape and succeeds. ListBuckets, multipart upload, and presigned URLs always work — so if those succeed and PutObject 403s, you have this exact issue.

What you need

  • Endpoint URL
    https://s3south.storage.com.vn — issued with your bucket. Use HTTPS only.
  • Access key + secret key
    Sent in the bucket provisioning email. Treat the secret as a password — never commit to a repo or ship to a browser.
  • Region + addressing style
    Region s3south — SigV4 mathematically requires some region string; the gateway treats it as an opaque value (not an AWS region). Path-style addressing for the apex TLS cert.

Copy a snippet and PutObject

Replace <your-access-key> & <your-secret-key> with the values from your provisioning email.
htx_s3.py
import boto3
from botocore.client import Config

# Endpoint, access key, and secret come from your bucket provisioning email.
EP = "https://s3south.storage.com.vn"
AK = "<your-access-key>"
SK = "<your-secret-key>"

cfg = Config(
    signature_version="s3v4",
    region_name="s3south",                        # opaque SigV4 placeholder, not AWS
    s3={"addressing_style": "path"},
    request_checksum_calculation="when_required",   # opt out of CRC32 streaming
    response_checksum_validation="when_required",
)

s3 = boto3.client(
    "s3",
    aws_access_key_id=AK,
    aws_secret_access_key=SK,
    endpoint_url=EP,
    config=cfg,
)

# Some edge proxies reject these headers on small-body PUTs. Strip them once
# at client construction time and every PutObject will be happy.
def _strip(request, **_):
    for h in ("Expect", "expect", "amz-sdk-invocation-id", "amz-sdk-request"):
        request.headers.pop(h, None)

s3.meta.events.register("before-send.s3.*", _strip)

# Round-trip a small object to confirm the client is wired correctly.
s3.put_object(Bucket="my-bucket", Key="hello.txt", Body=b"hello from htx")
print(s3.get_object(Bucket="my-bucket", Key="hello.txt")["Body"].read())

What works today

  • ListBuckets / CreateBucket / HeadBucket / DeleteBucket
  • PutObject / GetObject / HeadObject / CopyObject / DeleteObject (all sizes)
  • Multipart upload (recommended for files larger than 5 MB)
  • Presigned PUT and GET URLs
  • Canned bucket ACL — public-read / private (the "Make public" flow)

Tier-gated (request to enable)

  • PutBucketPolicy (resource policies)
  • PutBucketVersioning
  • PutBucketLifecycleConfiguration
  • PutBucketCORS
  • PutBucketTagging
  • PutObjectAcl (per-object grants — only canned bucket ACL is exposed)

These return 403 even with correct client config — it's a backend tier setting, not a client bug. Contact sales@vinacis.com if you need them for a workload.

Troubleshoot a 403

  1. Look at the response body. An HTML 403 Forbidden with no x-amz-request-id header means the edge proxy rejected the request — apply the streaming-checksum opt-out above. An XML <Error> with an x-amz-request-id means the S3 backend saw the request and refused it (permissions, quota, or wrong bucket).
  2. Test ListBuckets first. If ListBuckets succeeds and PutObject 403s with the same credentials, it is the streaming-checksum gotcha — credentials are fine.
  3. Try a multipart upload. If multipart succeeds but single-shot PutObject 403s, it is the same gotcha (multipart uses non-streaming PUT under the hood).
  4. Capture the wire headers. In Python, set http.client.HTTPConnection.debuglevel = 1. Look for Transfer-Encoding: chunked, Content-Encoding: aws-chunked, or X-Amz-Trailer: x-amz-checksum-crc32 — any of those on a PutObject confirms the SDK is using streaming-checksum.

Frequently asked questions

That is the streaming-checksum failure mode. From botocore 1.36 / AWS SDK v3 3.729 / aws-sdk-go-v2 1.30 onward, the default PutObject sends Content-Encoding: aws-chunked + X-Amz-Trailer: x-amz-checksum-crc32. Our S3-compatible gateway does not parse that wire format, so the edge proxy returns a generic nginx 403 before the request ever reaches the S3 layer (which is why no S3 error code or request-id comes back). Applying the snippet on this page makes the PUT a classic non-streaming SigV4 request and the 403 goes away.

Still seeing a 403?

Send us the failing request — endpoint, method, key, and the response body — and we'll trace it from the edge proxy through to the gateway. Email sales@vinacis.com or file a ticket from Notifications.