All posts
CloudJuly 31, 20267 min read

S3 + CloudFront: Multi-Tenant File Storage for SaaS

How to architect secure multi-tenant file storage on AWS with S3 and CloudFront: signed URLs vs cookies, tenant isolation patterns, and real 2026 costs.

F
Fepiq Team
Fepiq

If you're searching for how to build multi-tenant file storage on AWS for a SaaS product, here is the short answer: keep every S3 bucket private, put Amazon CloudFront in front of it with Origin Access Control, and hand out short-lived signed URLs or signed cookies from your application backend after checking who the requester is and which tenant they belong to. Never rely on a public bucket policy, and never let the client decide which tenant's files it can see. The rest of this guide covers how those pieces fit together, when to reach for signed URLs versus signed cookies versus plain S3 presigned URLs, how to isolate tenants inside the bucket, and what the whole setup actually costs at real SaaS scale.

Why the tutorial-default S3 setup breaks down for SaaS

Most S3 tutorials assume a single application with one set of users, so a public bucket or a static presigned link is good enough. A multi-tenant SaaS product has a harder requirement: Tenant A's invoices, uploads, and exports must be mathematically impossible for Tenant B to reach, even if Tenant B guesses a URL pattern or an object key. Public buckets and long-lived shared links fail that test immediately, and they also make audit questions like "who accessed this file, and when" impossible to answer. The fix is not a bigger bucket policy — it's moving the access decision out of S3 entirely and into an authorization layer your backend controls.

The reference architecture: private S3 + CloudFront + signed access

The pattern that holds up in production is: a completely private S3 bucket, a CloudFront distribution in front of it using Origin Access Control (OAC) so CloudFront is the only path in, and a backend service that issues time-boxed signed URLs or signed cookies only after confirming the user's tenant and permissions. The building blocks look like this:

  • A private S3 bucket with Block Public Access enabled and no public-read bucket policy
  • A CloudFront distribution with Origin Access Control scoped to that one bucket
  • A backend authorization service that maps user → tenant → allowed key prefixes
  • CloudFront signed URLs for single downloads, signed cookies for a session with many assets
  • CloudWatch and S3 server access logs tagged with tenant ID for audit trails

Signed URLs vs signed cookies vs presigned S3 URLs

These three mechanisms get confused constantly because they all produce a URL with a signature in it, but they solve different problems and picking the wrong one is the most common architecture mistake we see in client codebases.

ApproachBest forTrade-off
Presigned S3 URLsDirect browser-to-S3 uploads or downloads from a single pageBypasses CloudFront's edge cache and edge-level protections entirely
CloudFront signed URLsOne-off downloads: invoices, reports, generated exportsMust be reissued per object, which is awkward when a tenant needs many files at once
CloudFront signed cookiesA tenant browsing a dashboard with dozens of assets in one sessionNeeds careful cookie-domain and short-TTL management to limit the blast radius of a leaked cookie

Isolating tenants inside the bucket

Once access is signed and authorized, the next decision is how tenants are separated inside storage itself. There are three patterns in practice, and the right one depends on your customer mix more than your engineering preference.

  • Prefix-per-tenant (tenants/{tenantId}/...) — cheapest to run, easiest to operate, and correct for the vast majority of SaaS products as long as IAM and your authorization layer enforce the boundary
  • Bucket-per-tenant — stronger isolation and easier per-tenant lifecycle policies or encryption keys, worth it once enterprise or regulated customers start asking for it in security questionnaires
  • Account-per-tenant — a full separate AWS account per customer, reserved for large enterprise or government contracts where the customer requires an account-level trust boundary, not a routine default
The bucket is not the security boundary — the IAM policy, the Origin Access Control, and the authorization check in front of every signed URL are.
AWS Well-Architected Framework, Security Pillar

What this actually costs

S3 storage itself is rarely the expensive line item — data transfer and request volume are. CloudFront helps because a cache hit at the edge never touches S3 at all, which cuts both request costs and origin egress. Here's a rough monthly estimate for a mid-size SaaS product storing 500 GB of tenant files and serving roughly 2 TB a month through CloudFront at a healthy cache-hit ratio.

Line itemApprox. monthly cost*
S3 Standard storage (500 GB)~$11.50
S3 GET/PUT requests (5M)~$2-4
CloudFront data transfer (2 TB, ~80% cache hit)~$85-110
Signed URL/cookie validationNo additional charge

*Public on-demand pricing in us-east-1, before Savings Plans or volume tiers. Actual bills shift with region, cache-hit ratio, and how aggressively you move cold exports to S3 Infrequent Access or Glacier through lifecycle rules — which is worth automating from day one rather than retrofitting once a bucket has millions of objects.

Implementation checklist

  1. Enable S3 Block Public Access and confirm no bucket policy grants public read
  2. Create a CloudFront distribution with Origin Access Control scoped to that bucket only
  3. Generate a CloudFront key pair and store the private key in AWS Secrets Manager, never in application code
  4. Add a tenant-aware authorization check before issuing any signed URL or cookie
  5. Set signed URL/cookie TTLs as short as your UX allows — minutes, not days
  6. Log every signed request to CloudWatch tagged with tenant ID for audit trails
  7. Add S3 lifecycle rules to move old exports to Infrequent Access or Glacier automatically

Generating signed URLs and thumbnails at scale is a compute decision as much as a storage one. If you haven't settled on Lambda or a container runtime for that layer yet, our cost breakdown compares both for SaaS workloads.

Compare Lambda vs Fargate for SaaS

Frequently asked questions

Should I use one S3 bucket for all tenants or one bucket per tenant?+

Start with a single bucket and a prefix-per-tenant layout enforced by IAM and your authorization layer — it's cheaper to operate and scales to thousands of tenants. Move to bucket-per-tenant only when a specific customer's compliance requirements or per-tenant encryption keys demand it.

Are S3 presigned URLs safe for multi-tenant SaaS?+

Yes, as long as your backend validates the requesting user's tenant and permissions before generating the URL and keeps the expiry short. The risk isn't the presigned URL itself — it's skipping that authorization check or setting a TTL of hours instead of minutes.

Do I still need CloudFront if I'm already using S3 presigned URLs?+

For serving media and downloads back out to users, yes — CloudFront caches at edge locations, which cuts both latency and S3 request costs. For one-off authenticated uploads, plain S3 presigned URLs are simpler and CloudFront adds little value there.

How much does S3 plus CloudFront cost for a SaaS product with heavy file usage?+

For roughly 500 GB stored and 2 TB served monthly at a healthy cache-hit ratio, expect somewhere around $100-130 a month in storage, requests, and CloudFront transfer combined, before any committed-use discounts.

Is Cloudflare R2 cheaper than S3 plus CloudFront for SaaS file storage?+

R2's zero-egress pricing can be materially cheaper for very high-egress workloads. But if the rest of your application already runs on AWS, keeping storage in the same region avoids cross-cloud transfer costs and operational complexity — run the numbers for your actual egress volume before deciding to split providers.

None of this requires exotic AWS services — it's a private bucket, an Origin Access Control, and a backend that takes authorization seriously. The teams that get burned are the ones who ship a public bucket to hit a launch date and only revisit the architecture after a customer's security review flags it, at which point migrating tenants safely is a much bigger project than building it right the first time.

Need an AWS architecture review before you onboard your next enterprise tenant?

Talk to Fepiq's AWS architects

Get new posts in your inbox

Occasional, no-fluff notes on shipping modern software — startups, automation, Laravel, Shopify and more. No spam, unsubscribe anytime.

Keep reading

Related posts

All posts

Let's build something

Ready to ship your next product with Fepiq?

Book a free discovery call. We'll listen, ask sharp questions, and send you a proposal within 3 business days.