Summary
When self-hosting InstantDB with Cloudflare R2 as the S3 backend, presigned URLs for Storage ($files) are always generated in path-style (https:////). Cloudflare R2 custom domains do not support presigned URLs at all (Cloudflare docs: "Presigned URLs work with the S3 API domain and cannot be used with custom domains"), and R2 custom domains serve the bucket at the root (/), so any URL that includes the bucket name in the path returns 404 NoSuchKey.
Current State
- config/s3-public-endpoint + presign path (server/src/instant/storage/s3.clj:156-158) force :path-style? true whenever S3_PUBLIC_ENDPOINT is set.
- server/src/instant/util/aws_signature.clj (presign-s3-url, lines 288-337) builds url-path as "/" + bucket + path when path-style? is true.
- Result for R2 custom domain (files.example.com): https://files.example.com////?X-Amz-... → R2 looks up object //... → 404 (bucket is implicit at root on R2 custom domains).
- For R2 public dev URL (*.r2.dev), the same path-style URL also 404s, and additionally the host serves a certificate mismatch (SEC_E_WRONG_PRINCIPAL/ERR_CERT_COMMON_NAME_INVALID) on the current pub-.r2.dev hostname.
Workaround
- MinIO (self-hosted) works because MinIO supports path-style + presigned URLs (the official VPS guide). Set S3_ENDPOINT=http://minio:9000, S3_PUBLIC_ENDPOINT=https://files.example.com.
- Backblaze B2 also works (path-style + presigned supported), no custom-domain requirement for the API.
- R2 users can only use the S3 API domain (https://<ACCOUNT_ID>.r2.cloudflarestorage.com) as S3_ENDPOINT, but cannot get clean custom-domain URLs without a Cloudflare Worker (which must also handle presigning, since R2 custom domains reject SigV4).
Where They Break
- Any deployment that sets S3_PUBLIC_ENDPOINT to a host that does not accept path-style URLs (R2 custom domain, R2 .r2.dev dev URL, and any S3-compatible storage that only supports virtual-hosted style).
- There is no flag to opt out of path-style; it's hardcoded when S3_PUBLIC_ENDPOINT is present (s3.clj:156-158).
- Also affects PUT presigned uploads (browser uploads to $files), not just GET.
The Ask
- Add S3_VIRTUAL_HOST_STYLE opt-out env (path-style → virtual-hosted) di presign-creds + S3 client config.
- Document R2 caveat: custom domain + presigned URLs not supported (Cloudflare); supported path = S3 API domain.
Summary
When self-hosting InstantDB with Cloudflare R2 as the S3 backend, presigned URLs for Storage ($files) are always generated in path-style (https:////). Cloudflare R2 custom domains do not support presigned URLs at all (Cloudflare docs: "Presigned URLs work with the S3 API domain and cannot be used with custom domains"), and R2 custom domains serve the bucket at the root (/), so any URL that includes the bucket name in the path returns 404 NoSuchKey.
Current State
Workaround
Where They Break
The Ask