Skip to content

Wasabi S3 Configuration


Bucket Details

Property Value
Provider Wasabi Cloud Storage
Region ap-southeast-1 (Asia Pacific — Southeast)
Endpoint https://s3.ap-southeast-1.wasabisys.com
Bucket access Private (no public access)
Versioning Disabled
Object lifecycle No expiry (permanent storage)

boto3 Client Setup (wasabiboto3.py)

```python import boto3 from botocore.client import Config

s3 = boto3.client( 's3', endpoint_url='https://s3.ap-southeast-1.wasabisys.com', region_name='ap-southeast-1', aws_access_key_id=os.getenv('WASABI_ACCESS_KEY'), aws_secret_access_key=os.getenv('WASABI_SECRET_KEY'), config=Config( signature_version='s3v4', max_pool_connections=50, retries={'max_attempts': 3, 'mode': 'adaptive'}, connect_timeout=10, read_timeout=60 ) )

bucket_name = os.getenv('WASABI_BUCKET_NAME') ```


Key Configuration Parameters

Parameter Value Reason
signature_version s3v4 Required by Wasabi (SigV4)
max_pool_connections 50 50 concurrent S3 connections for parallel downloads
connect_timeout 10s Fast failure if Wasabi unreachable
read_timeout 60s Enough for encrypted chunk streaming (10 MB)
max_attempts 3 Retry transient errors
mode adaptive Auto back-off on throttling

Object Namespace

All user files are stored under their email as prefix: {user_email}/{folder_name}/{filename}

Examples: studio@gmail.com/Wedding_2026/IMG_0001.CR2 studio@gmail.com/Wedding_2026/IMG_0001.CR2 ← encrypted version: [16B IV][ciphertext]

No two users share a prefix. Storage isolation is enforced at the prefix level.


Multipart Upload

All large file uploads use S3 Multipart Upload:

```python

Initiate

response = s3.create_multipart_upload(Bucket=bucket_name, Key=s3_key) upload_id = response['UploadId']

Upload parts (via presigned URL or direct upload)

s3.generate_presigned_url('upload_part', Params={...})

OR

s3.upload_part(Bucket=..., Key=..., UploadId=..., PartNumber=..., Body=...)

Complete

s3.complete_multipart_upload( Bucket=bucket_name, Key=s3_key, UploadId=upload_id, MultipartUpload={'Parts': etags_list} ) ```

Part size: 10 MB (enforced in frontend JS). Minimum part size (S3 requirement): 5 MB.


Presigned URLs

Used for browser-direct S3 access without exposing credentials:

```python

Generate presigned URL for browser to PUT a part directly

url = s3.generate_presigned_url( 'upload_part', Params={ 'Bucket': bucket_name, 'Key': s3_key, 'UploadId': upload_id, 'PartNumber': part_number }, ExpiresIn=3600 # 1 hour )

Generate presigned URL for browser to GET a file

url = s3.generate_presigned_url( 'get_object', Params={'Bucket': bucket_name, 'Key': s3_key}, ExpiresIn=86400 # 24 hours ) ```


S3 Object Metadata (Encrypted Files)

Encrypted files carry Wasabi S3 user metadata:

python Metadata = { 'lenzeye-encrypted': 'true', 'lenzeye-key-version': key_version, # SHA-256 of user's active key 'lenzeye-hmac': hmac_hex, # 64-char hex HMAC-SHA256 }

This metadata is read on download to select the correct decryption key and verify integrity.


Wasabi vs AWS S3 Differences

Feature Wasabi AWS S3
Egress fees None Charged
API fees None Charged per request
S3 API compatibility 100% Reference
Eventual consistency window Short Immediate (since 2020)
Multipart abort Manual Manual

Eventual consistency

After complete_multipart_upload, Wasabi may take 1–5 seconds before head_object returns the new metadata. The code retries head_object up to 5 times with 1-second sleep to handle this.


TL;DR

Wasabi AP Southeast-1, private bucket, SigV4 auth. 50-connection pool, adaptive retry, 60s read timeout. All files prefixed by {email}/. Encrypted files carry lenzeye-hmac and lenzeye-key-version in S3 metadata. Presigned URLs for all browser-direct access.