Skip to content

Storage Configuration ​

Updated: 10/15/26, 5:44:21 PM

All platform files (site assets, reconciliation files, etc.) are stored through the S3-compatible protocol. MinIO, Aliyun OSS, Tencent COS, Huawei OBS, AWS S3 and other S3-compatible object storage services are supported. Configuration entry: Admin panel → System Config → Platform Config → Storage.

How Upload Works ​

Files are uploaded via presigned direct upload, in three steps:

  1. The admin frontend asks the backend for a presigned upload URL;
  2. The browser uploads the file directly to the object storage (the file never passes through the backend server);
  3. The backend confirms the file exists and registers the file record.

Therefore one action is mandatory before anything else: configure CORS rules on the bucket, otherwise "Test Connection" passes but real uploads still fail. Configuration and troubleshooting: see Storage CORS Configuration and Upload Troubleshooting.

Parameters ​

ParameterRequiredDescription
EndpointYesAPI address of the storage service. Must include https:// and must NOT include the bucket name
RegionDependsSee "Do I need the region" below
Access Key / Secret KeyYesCredentials of the storage service
Path Style AccessYesAddress style switch, see below
Public Bucket / Private BucketYesBucket names; the same bucket can be used for both, see "Single bucket"
Public Base URLRecommendedExternal address for public bucket files. Must start with http:// or https://; what to fill depends on the URL join mode, see below
URL Join ModeRecommendedHow public file URLs are composed: bucket-level domain (default) / with bucket, see below
Base Storage PathNoCommon prefix of object keys, e.g. daxpay
Upload presign expirationNoValidity of upload URLs in minutes, default 10
Download presign expirationNoValidity of private file URLs in hours, default 72

Do I need the region ​

The region only participates in request signing — it does not decide which data center is accessed; that is entirely determined by the endpoint. So in most cases it can be left empty:

Storage serviceRequiredExample
MinIO / self-hosted S3-compatibleNot required, leave empty—
Aliyun OSSLeave empty; fill it if a signature-region error occurscn-beijing
Huawei OBSRecommendedcn-north-4
Tencent COSRequired (validates the signing region)ap-shanghai, ap-guangzhou
AWS S3Requiredus-east-1, ap-northeast-1
Qiniu KodoRequiredcn-east-1
Cloudflare R2Fixed valueauto

When left empty, us-east-1 is used for signing. MinIO and other services that do not validate the signing region are not affected. Refer to each provider's S3-compatibility documentation for exact region codes.

Path Style Access ​

One bucket has two address styles:

  • Path style (on): https://endpoint/bucket/object — required for MinIO and most self-hosted storage
  • Virtual-hosted style (off): https://bucket.endpoint-host/object — the native style of cloud providers

How to choose

Always enable it for self-hosted MinIO. Cloud providers usually support both; follow the recommended values in the examples below.

About Public Base URL and URL Join Mode ​

The final public file URL is decided by "Public Base URL + URL Join Mode". Two join modes:

  • Bucket-level domain (default): URL = domain/object-key. For domains bound directly to a bucket — R2 bucket-level custom domains, a CDN domain bound to a single bucket. Fill in the bucket-level domain.
  • With bucket: URL = domain/bucket/object-key. For roots hosting multiple buckets — MinIO and the native domains of Aliyun OSS / Tencent COS. Fill in the root address; the bucket name is appended by the system.

The URL must start with http:// or https:// (validated by both the config page and the backend): an address without the protocol prefix is treated as a relative path by browsers during file-access redirects, making public files unreachable.

The config page previews the composed full URL in real time from the current form values, so you can verify the mode directly. When "Test Connection" gets a 404 on public access, it automatically retries with the other join mode and suggests switching if that one works.

Single bucket ​

The public and private bucket fields accept the same bucket name. In that case set the bucket ACL to public-read: public files are directly accessible and private files still work through presigned URLs.

The trade-off: "private" becomes nominal — anyone holding the full URL can read the file. For production, create two separate buckets so the permission semantics actually hold.

Examples ​

TIP

Endpoint addresses, region codes and other values in these examples are taken from each provider's official documentation. Providers may adjust their S3-compatible endpoint forms and configuration requirements over time — always follow the latest official docs when filling in the form.

Example 1: Self-hosted MinIO ​

ParameterValue
Endpointhttps://minio.example.com (the MinIO API port, not the console port)
RegionLeave empty
Path Style AccessOn
Public Base URLhttps://minio.example.com (or its public access domain)
URL Join ModeWith bucket (MinIO hosts multiple buckets under one root; the bucket name is appended by the system)

The keys are the MinIO AccessKey / SecretKey (created via mc admin).

Example 2: Aliyun OSS (Beijing) ​

ParameterValue
Endpointhttps://s3.oss-cn-beijing.aliyuncs.com (the S3-compatible endpoint with the s3. prefix, see "S3 Compatibility Notes" below)
RegionLeave empty; fill cn-beijing if a signature-region error occurs
Path Style AccessOn
Public / Private Buckete.g. daxpay-pub / daxpay-priv (or the same bucket name)
Public Base URLhttps://oss-cn-beijing.aliyuncs.com (the native domain, without the s3. prefix)
URL Join ModeWith bucket (multiple buckets under the native root; if the bucket has its own custom domain, switch to bucket-level domain and fill that domain)

The keys are the AccessKey of a RAM user. Minimal permissions: PutObject, GetObject, DeleteObject, ListObjects, GetBucketInfo on both buckets.

Note: even if the backend runs on Aliyun ECS, do not switch the endpoint to the internal address (ending with -internal) — uploads go from the user's browser directly, and browsers cannot reach internal addresses.

Example 3: Tencent COS (Shanghai) ​

ParameterValue
Endpointhttps://cos.ap-shanghai.myqcloud.com
Regionap-shanghai (required)
Path Style AccessOff
Public Base URLhttps://cos.ap-shanghai.myqcloud.com
URL Join ModeWith bucket (multiple buckets under the native root; the default CDN acceleration domain works the same way)

The keys are the SecretId / SecretKey of a Tencent Cloud sub-account.

Example 4: Cloudflare R2 (bucket-level custom domain) ​

ParameterValue
Endpointhttps://<account-id>.r2.cloudflarestorage.com (the S3-compatible endpoint; the account ID is on the R2 overview page)
Regionauto
Path Style AccessOn
Public Base URLhttps://cdn.example.com (the custom domain bound to the bucket in the R2 console)
URL Join ModeBucket-level domain (default) — the R2 custom domain points directly at the bucket, the URL contains no bucket segment

The keys are the R2 AccessKey / SecretKey (Object Storage → Manage API Tokens). Note that "bucket-level domain" exists exactly for the R2 shape: the custom domain is bound to the bucket and the address is simply domain/object-key; do not select "With bucket", otherwise the URL gains a bucket segment and always 404s. Without a custom domain, R2 also offers a public access domain of the form https://pub-<hash>.r2.dev (enable it in the bucket settings) — also fill it as a bucket-level domain.

S3 Compatibility Notes ​

Every provider has its own native storage API, but S3 is the de-facto standard and all mainstream providers offer a compatibility layer. The platform talks to every storage through the S3 protocol, so keep two distinct addresses apart:

  • API endpoint (Endpoint): where the platform sends S3 requests (test connection, presigned uploads). On most providers the native address is itself the S3-compatible endpoint — just fill it in.
  • File access domain (Public Base URL): the address browsers use to fetch files directly. It is independent of the API endpoint; use the provider's native domain or your CDN domain.

Why Aliyun OSS needs the s3. prefix ​

Aliyun OSS is the exception. In the early days it "incidentally" accepted S3 on its native address, and the behavioral gaps caused real problems — for example, the checksum and chunked transfer encoding that newer AWS SDK versions enable by default are rejected by the OSS compatibility layer. Aliyun later introduced a dedicated S3-compatible endpoint (https://s3.oss-<region>.aliyuncs.com); the official docs now require it and list the prefix-less native address as a typical misconfiguration.

Two pitfalls from practice are already handled by the platform, so normally you don't need to care:

  • The SDK-default checksum and chunked transfer encoding are disabled on the upload path, so the OSS error no longer occurs.
  • When Region is left empty, a default value is used for signing. If "Test Connection" reports SignatureDoesNotMatch, fill the region code (e.g. cn-beijing) in the Region field.

After switching the endpoint to the s3. prefixed address, keep the native address (e.g. https://oss-cn-beijing.aliyuncs.com) as the Public Base URL — the former is for API requests from the platform, the latter is for browsers accessing files; they serve different purposes.

For comparison: Tencent COS addresses (cos.ap-xxx.myqcloud.com) are already S3-compatible endpoints — fill them in directly, there is no separate prefixed form.

Testing and Troubleshooting ​

After saving, click "Test Connection". The system runs a full loop on both buckets: bucket exists → write probe file → read → delete. A pass means read/write permissions are ready. The test uses the current form values (unsaved edits are probed immediately); saving first is not required.

Common errors:

SymptomCauseFix
Test passes, upload failsBucket has no CORS rulesSee FAQ - Storage CORS Configuration and Upload Troubleshooting
Write failed, check upload permissionKey lacks write permission / bucket policy denies writes / bucket enforces KMS encryption without KMS permissionSearch 探测写入失败 (probe write failed) in backend logs for the real status code; temporarily grant full permissions to narrow it down
File uploaded but image not displayedURL Join Mode does not match the domain shape (bucket-level domain with "with bucket", or shared-root domain with "bucket-level"), or the public bucket is privateSwitch the join mode to match the domain (Test Connection auto-corrects and suggests); set the public bucket to public-read
Public file 404 after a 302Public Base URL lacks the http(s):// protocol prefix; the redirect is parsed as a relative path by browsersAdd the protocol prefix to the URL (enforced by the config page; watch out for legacy stored configs)
Upload succeeds but the record stays pendingKey can write but cannot readAdd read permission (GetObject)
"Test Connection" reports SignatureDoesNotMatchThe region used for signing does not match the region expected by the storageFill the region code in Region, e.g. cn-beijing

WARNING

"A record exists in the file list" does not mean the upload succeeded — the record is created as soon as the upload URL is requested. Only a truly accessible file counts.