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.
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.
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.
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 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:
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.
| Approach | Best for | Trade-off |
|---|---|---|
| Presigned S3 URLs | Direct browser-to-S3 uploads or downloads from a single page | Bypasses CloudFront's edge cache and edge-level protections entirely |
| CloudFront signed URLs | One-off downloads: invoices, reports, generated exports | Must be reissued per object, which is awkward when a tenant needs many files at once |
| CloudFront signed cookies | A tenant browsing a dashboard with dozens of assets in one session | Needs careful cookie-domain and short-TTL management to limit the blast radius of a leaked cookie |
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.
“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.”
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 item | Approx. 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 validation | No 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.
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 SaaSStart 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.
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.
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.
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.
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 architectsOccasional, no-fluff notes on shipping modern software — startups, automation, Laravel, Shopify and more. No spam, unsubscribe anytime.
Keep reading
Node.js event loop vs Laravel Octane for SaaS backends: a technical concurrency comparison, real AWS cost math, and a clear framework to pick the right one.
Turborepo vs. Nx for SaaS in 2026: compare caching, code generators, and cost so your React and Node.js monorepo scales without CI pain.
Let's build something
Book a free discovery call. We'll listen, ask sharp questions, and send you a proposal within 3 business days.