Docs

Private S3 buckets

Create a private bucket, connect your app, use scoped S3 keys, and understand temporary links and shared storage limits.

Updated November 1, 2026
On this page
  1. Choose the right storage
  2. Create and connect
  3. Use an external app
  4. Browse and upload
  5. Share a temporary download
  6. Manage access
  7. Understand shared capacity
  8. Requests and transferred data
  9. Deliver files to your users
  10. Browser access
  11. Compatibility
  12. Download returns a Cloudflare error
  13. Use an AI agent
  14. Delete a bucket

Private buckets store files behind bucket-scoped S3 credentials. Uploads in the console and objects written through the S3 API are the same objects. They have no public /_files/ URL.

Private buckets are available on hosted Fortly within your account’s shared storage allowance. New buckets are private, and creating one does not publish any files. Self-hosted installations have separate data-plane and provisioning switches; follow the operator rollout guide.

Choose the right storage

StorageAccessUse it for
Private bucketScoped S3 key, authorized console user, or temporary signed linkDocuments, user uploads and app exports
Project filesPublic /_files/ URLPublic images and downloads
Worker R2 bindingPrivate: only the Worker, through its bindingR2's object API from a Worker
Key-valueProject-scoped console, app API or Worker bindingSmall values and expiring cache entries

Creating a private bucket does not migrate or change existing Project files or Worker bindings. PostgreSQL restores do not restore object storage.

Create and connect

Storage opens Buckets by default. Choose Create bucket and enter a display name. Project files is the next tab for public assets; Key-value holds project-scoped values. The bucket is private. Fortly generates a stable S3 bucket name with a unique suffix. Renaming the display name does not change that S3 name, move objects or invalidate credentials.

Opening a bucket gives you one resource header and one navigation row: Files, Connect, Access keys, and Settings. Use the Storage breadcrumb to return to all buckets, Project files, or Key-value storage. Account-wide storage usage appears beneath the workspace; the bucket header shows only this bucket’s size and object count.

After creation, Files offers upload and app connection actions. Connect creates a dedicated key and adds five runtime-scoped variables to the project’s app:

S3_ENDPOINT
S3_REGION
S3_BUCKET
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY

Choose Read only or Read and write. Write permission includes replacing and deleting files. Use a variable prefix, such as PHOTOS, when connecting multiple buckets: PHOTOS_S3_BUCKET, PHOTOS_AWS_ACCESS_KEY_ID, and so on.

Existing variables are never overwritten. If names conflict, choose another prefix or deliberately remove the existing values before retrying. Retrying a connection with its original idempotency key returns the existing connection rather than creating extra credentials.

After saving, Connect shows the configured app and a View code example action. Examples use that connection’s variable prefix and offer file upload or temporary download-link generation. Read-only connections show a download example rather than an upload action. Additional connections are under Add another app connection; an existing prefix cannot be reused.

The connection saves runtime variables and refreshes the runtime. Redeploy an app that caches configuration at startup. Bucket credentials are not supplied to builds; use them from server-side code at runtime, not public browser bundles or static build output.

Fortly currently has one app per project and a Default bucket environment. These buckets do not introduce a production/preview environment-cloning feature. Credentials are never automatically copied into another project or environment.

Use an external app

Open Connect, expand Connect an external app, copy the endpoint and stable bucket name, then create a key in Access keys. Save its secret immediately; ordinary key listings cannot retrieve it. Keep credentials in server-side environment variables or a secret manager.

Use path-style addressing with the gateway region fortly-1:

https://s3.fortly.com/<stable-bucket-name>/<object-key>

Install @aws-sdk/client-s3 and use the connection’s runtime variables:

TypeScript
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: process.env.S3_ENDPOINT,
  region: process.env.S3_REGION,
  forcePathStyle: true,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

await s3.send(new PutObjectCommand({
  Bucket: process.env.S3_BUCKET,
  Key: "exports/hello.txt",
  Body: "Hello from Fortly",
  ContentType: "text/plain",
}));

const file = await s3.send(new GetObjectCommand({
  Bucket: process.env.S3_BUCKET,
  Key: "exports/hello.txt",
}));
console.log(await file.Body?.transformToString());

For the AWS CLI, set your generated AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, then use:

Terminal
aws configure set default.s3.addressing_style path
aws --endpoint-url https://s3.fortly.com --region fortly-1 \
  s3api list-objects-v2 --bucket "$S3_BUCKET"

Do not use your Fortly account token as an S3 access key. Bucket creation, settings, keys and deletion belong to the control-plane API, not S3 CreateBucket or DeleteBucket.

Browse and upload

Files lists up to 100 items per page. The filter matches loaded items in the current folder; it does not search the entire bucket. Breadcrumbs and folder rows change the listing prefix. Refresh files reloads the current folder and storage usage, including writes made through an SDK. Choose folder selects an existing or new prefix; it becomes visible in listings after an object is uploaded there.

The console uploads files of up to 64 MiB and shows transfer progress, saving status and per-file failures. Larger objects use an S3 multipart client. Cancel stops the browser transfer; a request that already reached storage may finish, so refresh before retrying. Retrying a file replaces the object at the same key.

Object names are exact UTF-8 keys. Repeated slashes, Unicode and literal percent signs are preserved. Empty keys, keys over 1,024 UTF-8 bytes, control characters, backslashes and . or .. path segments are rejected. Folders are prefixes, not filesystem directories. There is no atomic folder move or bulk rename.

Share a temporary download

Choose Create temporary link from a file’s menu. The default duration is 15 minutes; the console also offers one hour and 24 hours. The server rejects durations outside one minute to 24 hours.

Anyone holding an unexpired link can download that object. The signature binds the method and exact key. A link follows the key: replacing the file before expiry makes the replacement available through the link. Links point to Fortly’s S3 origin, not the backing provider.

Console links use a separate hidden read-only signer. Revoking an ordinary app key does not revoke console-generated links. Settings → Revoke console download links invalidates all existing console links for this bucket. Individual-link revocation is unavailable. URLs signed by your SDK stop working when their own signing key expires or is revoked. Bucket or owning-account deletion denies new requests. An already streaming response may finish.

Downloads use attachment disposition and nosniff on the separate S3 origin. Do not log signed query strings or place temporary URLs in analytics events.

Manage access

The account owner and admins can browse objects, upload, create or revoke keys, connect apps, change settings and delete buckets. Members who can see the project can view bucket metadata and aggregate usage; project visibility alone does not grant private object access.

Every S3 key belongs to exactly one bucket. Read-only keys can discover their bucket, list and download objects. They cannot upload, delete, copy into a bucket, modify multipart uploads, or manage credentials. Even a read-and-write key cannot administer buckets or reach another bucket in the same account.

Key revocation is read from PostgreSQL on each request; there is no gateway authentication cache. Finishing an upload checks the key again before publishing. Key rotation creates a replacement while leaving the old key active: update clients, verify access, then explicitly revoke the old key. Use Connect → App connections → Rotate app credentials to update a Fortly app transactionally. Verify the app, then retire its previous key in Access keys. Disconnect revokes all keys belonging to the connection, including retained replacements.

Disconnect revokes the connection’s key and removes variables only when their values and runtime scope still match what the connection installed. Customer-edited variables are preserved and named in the result.

Understand shared capacity

Private bucket objects, incomplete multipart bytes, reserved transfers, public Project files and Worker R2 buckets share the account’s existing object-storage allowance. Database storage, key-value entries, deployment artifacts and platform backups are separate. No additional bucket charge is introduced by this feature.

Capacity is reserved atomically before reading a private upload request body or writing to storage. Overwriting an object temporarily requires space for both the current and new copies. The old object remains charged until cleanup confirms removal. Interrupted or uncertain uploads retain their reservations until a durable cleanup job settles them; restarting a node does not free space prematurely.

Provider inventory is reconciled against the durable ledger. Drift pauses growth until reconciled; it never causes automatic deletion of unknown objects. Accounts over their allowance can still read and delete authorized objects. Deleting a file removes it from listings immediately; capacity becomes available after stored bytes are removed. Compute CPU suspension is separate from bucket access; Fortly currently has no independent bucket-specific financial-suspension state.

Plan changes and Storage Pack removals cannot reduce object-storage capacity below stored bytes plus outstanding reservations. Free space and wait for cleanup, or retain enough Storage Packs before requesting a smaller plan. A rejected reduction leaves the current plan and packs unchanged; it does not delete data or silently grant extra storage. An upgrade raises capacity when the server saves the new entitlement.

Requests and transferred data

The bucket’s Files section shows Activity this month: read requests, write requests, data received and data sent for requests started since the beginning of the current UTC calendar month. Refresh usage reloads these totals. Metadata-only project members and approved MCP agents can read these aggregates without access to file names or contents.

Usage records persist in PostgreSQL across node restarts. They cover authorized, supported S3 operations and console file listing, upload and deletion. SDK retries are separate requests; multipart parts are separate writes. The traffic meter records HTTP body bytes processed by Fortly, including listing and error response bodies, rather than assuming every download transferred the full object. HEAD has no response body. Interrupted requests remain identifiable; a frame passed to the HTTP transport does not prove the browser received it. Failed authorization and requests for another customer’s bucket are not attributed to that customer.

Storage capacity and traffic are separate measurements. Copies stored as separate objects consume capacity; downloading an object does not store it again. These usage totals do not create per-request or bandwidth charges. Payments and a prepaid balance are not connected in this release, and no automated balance-based suspension or usage invoicing is offered. Storage remains bounded by the account’s saved plan and packs.

LimitValue
Buckets per project32 active or deleting buckets
Active named keys per bucket32, including app keys; hidden download signer excluded
Object key1–1,024 UTF-8 bytes
Single PUT / console upload64 MiB
Multipart partUp to 64 MiB; at least 5 MiB except the final part
Multipart objectUp to 5 GiB
Part numbersConsecutive, starting at 1; at most 10,000
Incomplete multipart uploads32 per account
Active transfer reservationsFour per account
Object metadata32 text entries, 2 KiB total
Inactive multipart cleanupAfter 24 hours, plus cleanup scheduling and transfer grace periods
Transfer deadlineFive minutes per upload or part

Deliver files to your users

Your server can check a user’s permission and generate a temporary S3 GET URL. The browser then downloads from the Fortly S3 endpoint. In Connect → Use it in your app, choose Create a download link for a server-side SDK example. Install @aws-sdk/s3-request-presigner alongside the S3 client. Do not log signed links.

Private S3 responses use Cache-Control: private, no-store. Separately enabled CDN delivery caches bodies at cdn.fortly.com with a current authorization/budget check on every hit. Choose private signed links or explicitly publish one folder; the S3 origin stays private. Existing Project files retain their separate public delivery model. Enabling a bucket alone does not publish any files or activate the CDN.

Browser access

Server-side SDKs do not need CORS. For direct browser requests, configure explicit origins under Settings → Browser access. HTTPS is required except for local development. Select allowed methods and explicit request headers. There are no wildcard origins, credentialed cookies or public-access switches.

CORS is a browser permission layer, not authentication. A signed request or temporary URL is still required. A browser preflight that receives no allow-origin response may indicate an unlisted origin, method or header. Do not weaken a rule to a wildcard to work around missing credentials.

The console keeps each CORS rule separate. In Settings, expand Browser access (CORS) to add up to four rules, edit their origins, methods, request headers and preflight cache duration, or remove them. Removing every rule disables browser access. Saving a display-name change preserves existing rules.

Compatibility

This is an S3-compatible subset, not all of Amazon S3. The implementation uses maintained s3s parsing and SigV4 verification, with Fortly authorization and pooled quota admission.

OperationBehavior
ListBucketsOnly the key’s bucket
HeadBucket, GetBucketLocationScoped access; region fortly-1
GetObjectStreaming, single byte range and conditional reads
HeadObjectMetadata
ListObjectsV2Prefix, / delimiter, common prefixes, query-bound encrypted cursors
PutObjectUp to 64 MiB, overwrite, content type and text metadata
DeleteObject, DeleteObjectsScoped deletion; bulk results include per-object failures
CopyObjectSame bucket only; preserves source metadata
MultipartStart, upload/replace part, list parts, complete, abort, list own incomplete uploads
Presigned GET / PUTSigV4; the signing key’s current permissions still apply
Upload integrityPayload signatures and supplied supported checksum headers/trailers are verified
Multipart checksum aggregationUnsupported; omit the algorithm when starting a multipart upload
Conditional writes/copiesUnsupported; explicit failure
Virtual-hosted bucket addressingUnsupported; use path style
ACLs, policies, public buckets, website hosting, versioning, tags, object lock, customer encryptionUnsupported; explicit failure
Cross-bucket copy / multipart copyUnsupported
Bucket creation/deletion through S3Unsupported; use Fortly control plane

Multipart clients that automatically request checksum aggregation must be configured to request checksums only when required. Read the release’s compatibility evidence before migrating a production client; an emulator test does not prove behavior through the real provider or ingress. rclone compatibility is not advertised.

Download returns a Cloudflare error

A plain Cloudflare 1010 response is a browser-integrity rejection before the request reaches Fortly. It is different from an S3 XML authentication error, and rotating a valid bucket key does not fix it. It may affect a non-browser client such as Python’s default HTTP opener even when the same signed link works in a browser or curl.

For hosted Fortly, contact support with the HTTP status and CF-Ray identifier; never send the full signed URL or secret. A standard user-agent header can identify the client while the edge configuration is corrected. Self-hosted operators should apply the hostname-specific Browser Integrity exception in the rollout guide, preserving S3 signatures and authorization. Requests rejected before admission do not appear in bucket request totals.

Use an AI agent

The remote MCP tool list_storage_buckets requires projects:read and a grant covering the project. It rechecks current membership and returns bucket identities, state, usage and redacted app connection status. It does not list object names, read contents, generate download URLs, reveal keys or change storage. Disabled installations return enabled: false.

Ask your agent to inspect bucket usage or help write an SDK integration, then use the console to create credentials. Never paste a generated secret into a chat. See Connect an agent.

Delete a bucket

Type the displayed bucket name to confirm permanent deletion. The console returns to the bucket list and refreshes deleting rows while cleanup runs. Fortly first marks it deleting and revokes keys and connections. Durable jobs then abort incomplete uploads, remove objects and settle usage. The stable identity is never reused. Project and account deletion revoke their buckets too.

There is no bucket recovery window. PostgreSQL point-in-time recovery does not restore deleted objects. Keep separate copies when your retention policy requires them.

Something missing or unclear? Tell us and we will fix the page.© 2026 Fortly