What this covers
Tornado talks to any storage that speaks the S3 API. Beyond AWS S3 and Cloudflare R2, which have their own tiles in the dashboard, the generic S3 path is what you use for:MinIO
Self-hosted, on your own hostname and port
Wasabi
Hot cloud storage
Backblaze B2
Via the B2 S3-compatible API
DigitalOcean Spaces
Per-datacenter endpoints
Scaleway
Object Storage
OVHcloud
S3 Object Storage
Requests are always signed with AWS Signature Version 4. This is forced, not negotiated.
A provider or gateway that only supports SigV2 will not work.
Endpoint and region per provider
The examples below identify endpoint shapes; consult the linked provider documentation and console for your bucket’s current endpoint and actual region, since several providers assign it per bucket rather than per account.OVHcloud:
s3.{region}.io.cloud.ovh.net is the current endpoint, and OVHcloud calls it
“the preferred endpoint to access OVHcloud Object Storage”. The older
s3.{region}.perf.cloud.ovh.net is “maintained for backward compatibility purposes only, to
allow tools that don’t support AWS’s recent Express_One_Zone storage class to continue
operating”. Tornado is not one of those tools. Use the io form.DigitalOcean: some AWS SDKs insist on
us-east-1 when creating a bucket. Tornado never
creates buckets, it only writes objects into one you already own, so put your datacenter code
in the region field.What Tornado does to your endpoint string
The dashboard’s S3 probe normalises its endpoint before constructing the client:- Leading and trailing whitespace is stripped.
- An accidental doubled scheme is collapsed, so
https://https://hostbecomeshttps://host. - If no scheme is present,
https://is prepended. - Trailing slashes are trimmed.
- Use a public HTTPS endpoint. The dashboard validates the normalised URL and rejects plain HTTP, private/internal hosts and local addresses. The cleanup rules do not make an internal MinIO deployment reachable from Tornado. Save the complete, canonical HTTPS URL: the worker credential mapping does not repeat the probe’s scheme-repair steps.
- The bucket must not appear in the endpoint. Only the scheme, host and port are trimmed.
A path such as
https://s3.fr-par.scw.cloud/my-bucketis kept verbatim and the bucket ends up in the request twice.
Filling the fields in the dashboard
The onboarding wizard has a dedicated tile for this. In Destination, pick Any S3-compatible (the wide tile at the bottom of the grid, below Google Drive), then fill the Credentials step:1
Endpoint URL
The provider host from the table above, with scheme. The placeholder shows the expected
shape:
https://minio.internal.example.dev:9000. Despite the placeholder’s wording, your actual host must be publicly reachable over HTTPS.2
Bucket name
The bucket, on its own. It must already exist; Tornado does not create it.
3
Region
The region value from the table above. The field cannot be left empty.
4
Access key
Maps to
access_key_id.5
Secret key
Maps to
secret_access_key. Write-only in the UI, and read back masked afterwards.6
Test connection
Runs a live probe against the bucket before anything is saved. Read
what it proves before you trust the green tick.
Credentials are written to Azure Key Vault. The organisation document keeps routing metadata,
a masked display configuration and a vault reference. If Key Vault is unreachable the save is refused with a 503 rather
than falling back to storing the secret in the database.
Addressing style: path-style vs virtual-host
An S3 client can address a bucket in two ways:- Virtual-host style:
https://my-bucket.s3.example.com/key. The bucket becomes a subdomain, so the provider needs a wildcard DNS record and a matching TLS certificate. - Path-style:
https://s3.example.com/my-bucket/key. The bucket is the first path segment.
MINIO_DOMAIN, and its own documentation is explicit that “omitting this setting directs MinIO
AIStor to only accept the default path-style requests”
(core settings).
A stock MinIO therefore needs path-style, and enabling virtual-host on it means adding a
wildcard DNS entry and a certificate covering *.your-minio-host as well.
The current limitation
The worker and dashboard construct separate clients:
A path-style-only endpoint can therefore behave differently in the probe and dashboard
webhook signer. A missing
path_style flag does not by itself prove failure: the SDK can
choose path-style automatically. Test both the probe and the actual delivered link.
Supplying path-style through the API
The settings model acceptspath_style, and the probe honors it when supplied. However, the
current save path converts credentials into the worker’s flat format, which does not retain
this flag. Sending it on save therefore does not guarantee that later dashboard webhook
links use path-style. The worker already forces path-style independently.
path_style: true,
to POST /settings/storage/test. For example, save that JSON as storage.json and run:
Both routes require an Admin or Owner session token, not an
x-api-key.
Keep credential JSON outside version control. A successful explicit path-style probe is
not proof that the saved dashboard signer retains that option; verify its actual URL.What “Test connection” proves
The probe is a real network call against your real bucket, not a credential format check. It runs five steps and reports the round-trip latency:
No part is ever uploaded, so the whole probe moves 23 bytes regardless of how many steps it runs.
Each step names itself in the error string, so a failure tells you which permission is missing
rather than just
AccessDenied.
It proves:
- The endpoint resolves, the TLS handshake succeeds and the host speaks S3.
- The bucket exists and is reachable at that endpoint with the region you supplied.
- The credentials carry
s3:PutObject,s3:GetObject,s3:AbortMultipartUploadands3:DeleteObjecton that bucket. - The read works as the identity your download links are signed with, which is the reason the read leg exists at all.
- The addressing style currently in effect works for the endpoint.
- The provider actually implements the multipart API. Not every S3-compatible gateway does, and a
gateway that returns
NotImplementedat step 3 cannot handle the multipart delivery path (used above the default 50 MiB file threshold and by streaming uploads).
- That a real delivery will fit. The probe moves 23 bytes and uploads no parts.
- That your policy allows the delivery prefix. The probe writes to
tornado-connection-test/at the bucket root. A policy that permits that probe prefix but deniesvideos/*can pass the test and fail the job. A policy allowing onlyvideos/*instead rejects the probe. - That a recipient can fetch the link. Tornado reads from its own network; a bucket policy conditioned on source IP, or a private MinIO your customers cannot reach, behaves differently for them.
- That the presigned link is addressed correctly. On a stock MinIO the probe can pass while the
download_urlpoints at a hostname that does not resolve. See Addressing style.
Permissions
Minimum
Exactly the four actions the probe exercises and delivery uses, and nothing else:Minimum
Do not add
s3:CreateMultipartUpload, s3:UploadPart or
s3:CompleteMultipartUpload. They are not IAM actions. AWS’s multipart permission table maps
all three to s3:PutObject, For a compatible provider, check its supported policy actions and multipart behavior
rather than assuming complete AWS equivalence. AbortMultipartUpload is the only step of the lifecycle
with its own action name, which is exactly why it is the one people leave out.Recommended
The minimum plus one bucket-level action:Recommended
s3:ListBucket is not needed at all. Tornado never enumerates the bucket: it writes to a key it
computed itself, signs a URL for that key, and deletes by key.
Providers with their own dialect
Troubleshooting
Theerror string returned by the connection test is deliberately compact. For S3 failures it is
<ProviderErrorCode>: s3:<Operation>, where the operation is whichever of the probe’s five steps
failed first: PutObject, GetObject, CreateMultipartUpload, AbortMultipartUpload or
DeleteObject.
”internal error during storage test” means your endpoint is malformed
This is the least informative error and the most common one to hit while setting up a new provider. It says nothing about URLs, but that is usually exactly what it is. The S3 client is constructed before the probe’s own error handling begins. If the SDK rejects the endpoint string outright, for example because it contains a space, an illegal character, or an over-long hostname label left behind by a bad paste, the rejection happens during construction and is caught by the generic handler, which reportsinternal error during storage test with no
detail.
When you see it, check the endpoint before anything else:
1
No spaces, no stray characters
A trailing space inside the value survives the trim if it is in the middle. Retype the host
rather than pasting it.
2
Scheme written once
https://https://... is collapsed automatically, but a mangled paste such as
https://host/.provider.com/ is not repaired.3
No bucket, no path
Host and optional port only.
4
Not the console URL
Provider consoles live on different hostnames to the S3 API. Wasabi’s console is
console.wasabisys.com, which is not a storage endpoint.Test passes, downloads return 403
The probe reads, so a policy with nos3:GetObject at all now fails the test rather than passing
it. With a green probe and 403 links, check restrictions on the delivery prefix, recipient
network, link expiry and credential rotation. The probe exercises its own object and network
path; it cannot rule out all other access conditions.
Test passes on MinIO but webhook links do not resolve
Inspect whether the URL uses a bucket subdomain that your endpoint cannot resolve. An explicitpath_style: true can validate the probe path, but the current save path does not
preserve that flag for the dashboard signer. Verify the provider’s supported addressing mode
and DNS/certificate configuration; do not assume re-saving the flag fixes future webhook links.