AWS S3 Alternatives Without Egress Fees: What to Check Before You Switch
Zero-egress storage moves the meter, it doesn't remove it. What providers bill instead of transfer, why the saving only appears when bytes leave from the bucket, and which workloads actually get cheaper by leaving S3.
You've worked out where the object storage bill actually comes from, discovered it's mostly transfer rather than storage, and gone looking for an S3 alternative that doesn't charge for it. There's no shortage of candidates. The problem is the shape of the answer you get back: every comparison ranks providers on the headline price per gigabyte and the phrase "no egress fees," and those are the two numbers that tell you the least about what the invoice will say.
A provider that doesn't meter transfer still has to make money somewhere, and the meter it uses instead is the one to go looking for. So this is about the meters: what replaces egress on the bill, why the saving sometimes fails to appear at all, which workloads genuinely get cheaper by leaving S3, and what stops working when the bucket moves. What "S3-compatible" guarantees at the protocol level is a separate question, answered in how S3-compatible object storage works.
"No egress fees" is a pricing shape, not a gift
Bandwidth costs the provider something. Dropping the egress line is a decision to recover that cost differently, or a bet that most customers store far more than they serve. Either way the invoice still has to add up, and four meters do the adding. Any one of them can be the reason a cheaper provider produces a bigger bill:
- Per-operation charges. Requests are billed by the million, usually split into a write-and-list class and a read class. Cloudflare R2 calls these Class A and Class B; Backblaze B2 uses transaction classes. The important property is that this meter runs on the *number* of objects you touch, not on how big they are, which makes it invisible in any comparison done per gigabyte.
- Minimum storage duration. An object deleted before it has been stored for N days is still billed for the remaining days. This is standard on cold and archive tiers everywhere, and it also applies to some flat-rate providers on their normal tier: Wasabi has applied a 90-day minimum on standard storage as of writing. Store something for a week under a 90-day minimum and you paid for twelve.
- Minimum billable object size. An object below the floor is billed as if it were the floor size. Half a million 3 KB records can be billed as if they were something much larger, and again nothing about the per-gigabyte price predicts it.
- Free transfer with conditions attached. Free can mean unconditional, or capped at a multiple of what you store, or governed by an acceptable-use policy, or available only over a particular path such as a partner CDN or a peering arrangement. Backblaze B2 offers free egress up to roughly three times your average stored data as of writing, metered above that. Wasabi ties free transfer to your stored volume through its use policy.
The fourth one deserves a specific question: is free transfer a term you can point at, or a policy someone can reinterpret? A cap expressed as a multiple of stored data is arithmetic, and you can check today whether your traffic fits under it. A fair-use clause is a judgement about your account made by someone else, at a moment you don't choose. Both are legitimate ways to run a business, and neither is a reason to distrust a provider. They're a reason to know which one you're buying before your traffic triples.
The saving only exists if the bytes leave from the bucket
Before comparing anyone's prices, find out where your data physically exits to the internet, because that is the only place an egress charge can land. This one check reorders most migration plans, and it's the part the comparison articles skip.
On AWS, moving data between S3 and EC2 in the same region isn't billed as data transfer out at the time of writing. The meter runs when bytes head for the public internet. So picture the common architecture: a request arrives at your application, the application reads the object from the bucket, and the application streams it back to the user. The S3-to-application hop was already free. The application-to-user hop is the one on the invoice. Now move that bucket to a provider that charges nothing for transfer. The hop that just became free is the hop that was free already, and the metered hop hasn't moved an inch. You did a migration and saved nothing.
The saving turns up when the storage provider hands bytes to the user directly, with your application out of the byte path: a presigned URL so the browser fetches the file from the bucket itself, or a public bucket served through the provider's CDN. Then the transfer leaves from storage that doesn't meter transfer, and the line genuinely disappears. Same provider, same prices, completely different outcome, decided entirely by which machine is holding the socket.
Follow the bytes before you compare prices
Draw the path a downloaded file takes from disk to browser and mark the point where it crosses onto the public internet. Whoever owns that point is the one billing you for transfer. Zero-egress storage pays off when that point is the bucket, and changes nothing when it's your application server.
There's a second consequence for the reads your application still has to do itself: thumbnailing, virus scanning, zipping an export, serving a private file it has to authorise first. If the bucket now sits with a different provider than your compute, that read crosses a network boundary. Most clouds don't charge for data coming in, so as of writing this usually costs latency rather than money. Keeping storage and the service that reads from it close together still matters, because a few extra milliseconds on every object read has a way of showing up in page load times rather than on the invoice.
Which meter dominates depends on the shape of your data
No provider is cheapest in general. One is cheapest for the way your particular data behaves. Work out which meter your data runs hottest and the shortlist mostly writes itself:
| Workload shape | Meter that dominates | What to check on the pricing page |
|---|---|---|
| Video and large media: few objects, many bytes, served repeatedly | Transfer, then storage per GB | Whether free transfer is unconditional or capped, and whether the CDN in front is included or billed separately |
| Millions of small objects: thumbnails, map tiles, telemetry, per-user documents | Operations, plus any minimum billable object size | Price per million reads and per million writes, and the floor size an object is billed at |
| High churn: CI artefacts, temporary renders, staging data written and deleted within days | Minimum storage duration | Whether early deletion is charged, for how many days, and whether it applies to the standard tier |
| Backups and archives: written once, read rarely, kept for years | Storage per GB, plus retrieval | Restore charges, retrieval latency, and whether the archive tier carries its own minimum duration |
The second row is where the assumption breaks. A bucket holding fifty million thumbnails might be only a couple of terabytes, so the storage line looks trivial and the transfer line looks like the thing to optimise. But every page view reads dozens of those objects, and on a provider that bills per operation, that read meter runs on all of them. For workloads like that, free transfer can still land above metered S3, and the only way to find out is to count requests rather than gigabytes. Media is the opposite case, and the arithmetic that makes it so painful is worked through in S3 egress fees.
What the zero-egress providers actually bill for
With that in hand, here are the main options sorted by billing model rather than by price. Prices change and free tiers get restructured; the model each provider chose is the part that stays put long enough to compare.
| Provider | Transfer out | What else is metered | The line to read |
|---|---|---|---|
| Amazon S3 | Metered to the internet past a small monthly allowance; free to AWS compute in the same region | Storage per GB, requests, storage-class transitions and retrievals | How much of your traffic actually leaves the region |
| Cloudflare R2 | Not metered to the internet | Storage per GB, Class A (write and list) and Class B (read) operations; the infrequent-access class adds a minimum duration and a retrieval fee | Your monthly operation counts, not your gigabytes |
| Backblaze B2 | Free up to a multiple of the data you store, metered above it | Storage per GB, transaction classes | Where that free allowance sits relative to your actual traffic |
| Wasabi | No separate transfer charge, bounded by an acceptable-use policy tied to stored volume | Storage per GB, with a minimum storage duration and a minimum billable object size | The minimum-duration clause and the wording of the use policy |
| EU providers (Hetzner, Scaleway, OVHcloud) | Usually an included allowance, metered above it | Storage per GB, sometimes requests | Size of the included allowance and which regions are on offer |
| Self-hosted MinIO or Ceph | No provider charge; you pay your own bandwidth | Servers, disks, replication, and your team's time | The operational cost you just moved in-house |
| Runsite | Not metered in either direction, with no allowance to exceed | Storage per GB after the free tier | Nothing further: operations aren't billed |
Pricing pages move faster than articles do
Every figure and policy above is current at the time of writing and none of it is a substitute for the provider's own pricing page on the day you decide. Use this to know which questions to ask, then get the numbers from the source, and read the terms rather than the marketing headline for anything described as free.
What you leave behind isn't the API, it's the identity model
"Will my code still run?" is the question everyone asks about compatibility, and for the core read-write path the answer is usually yes: change the endpoint, change the keys, and the SDK calls are the same calls. The things that don't come with you live outside the S3 API, which is exactly why a feature comparison of storage products never mentions them.
Static keys instead of IAM roles
Inside AWS, a well-configured application holds no storage credentials at all. It runs under an IAM role, and the SDK collects temporary credentials from the instance or task metadata and refreshes them on its own. Nothing is written to a config file and nothing needs rotating by hand. Point that same SDK at an S3-compatible endpoint and there's no role for it to assume: authentication is Signature Version 4 with a long-lived access key and secret that you now store, scope, and rotate yourself.
import os
import boto3
# Inside AWS: no credentials in the code at all. The SDK picks up
# temporary ones from the instance role and refreshes them itself.
s3 = boto3.client("s3")
# Anywhere else: a long-lived key pair that is now your responsibility.
s3 = boto3.client(
"s3",
endpoint_url=os.environ["S3_ENDPOINT"],
aws_access_key_id=os.environ["S3_ACCESS_KEY"],
aws_secret_access_key=os.environ["S3_SECRET_KEY"],
)None of that blocks a migration, but it does change your threat model, and the answer is worth setting up on day one: a separate key per application, scoped to the narrowest permission that application needs, kept in your platform's secret storage rather than the repository, and rotated on a schedule you'll actually keep. A key that can only write to one bucket is a small incident when it leaks; an admin key pasted into every environment is a large one. Decide it during the migration, because the version of this decision that gets made under time pressure is one admin key everywhere, and that is difficult to unpick later.
The integrations that don't travel
Bucket events triggering a Lambda, Athena querying objects where they sit, CloudFront with origin access control, Glacier tiers for cold data: these are integrations with the rest of AWS rather than with the S3 API. A provider can implement `PutObject` flawlessly and still have nothing to offer here, because the missing piece was never storage. Where those wires exist, moving the bucket means rebuilding them, and that work belongs in the migration estimate rather than in the surprise column. The gaps inside the API itself, meaning versioning, lifecycle rules, object lock and bucket policies, vary by provider and are better tested on a live bucket than read about; there's a checklist for that in how S3-compatible object storage works.
When staying on S3 is the right answer
Sometimes the honest conclusion is that the migration isn't worth doing. A few situations where the answer is to stay put:
- Your compute lives in AWS and the bytes leave from there. Per the byte-path argument above, moving the bucket relocates a charge you weren't paying. Moving the delivery path is the change that would save money, and that's a different project.
- You depend on something that only exists inside AWS. Object Lock to satisfy a retention regime, Glacier economics for a genuinely cold archive, query-in-place over your own data, an event pipeline that other systems consume. Each of those has to be rebuilt or replaced, and the replacement is rarely free.
- Your transfer volume is small. An internal tool, a private bucket written far more often than it's read, an archive nobody touches. If the egress line is a few dollars, the entire prize is a few dollars.
- Nobody wants to own key rotation. If static credentials would sit unrotated in a config file for two years, the security cost of leaving may outrun the billing saving.
A workable rule: if transfer isn't among the three largest lines on your bill, migrating is work without a payoff. Open last month's invoice and rank the lines before you rank the providers. If transfer is up there, moving to object storage that doesn't meter it is the change that pays for itself.
Running both buckets while you move
Copy everything across, flip the endpoint, delete the old bucket. That plan works fine for a bucket nobody is writing to. For a live one, the copy is out of date the moment it starts, so the sequence needs a window where both buckets are real: backfill what already exists, write new objects to both providers while the backfill runs, keep reads on the old bucket until the new one is provably complete, then move reads over and hold the old bucket read-only for as long as you'd want to roll back. Standard tools such as `aws s3 sync` and `rclone` speak both endpoints, and the step-by-step is in the Runsite docs rather than here.
Two things reliably go wrong in that window. The first is verification: comparing ETags across providers doesn't prove anything, because the ETag of a multipart object is a hash over its part hashes and depends on the part size the copying tool happened to pick, so the same bytes yield different ETags on each side. Verify with object size plus a checksum you compute yourself, for the reasons set out in uploading files to S3. The second is metadata: content type, cache control and content disposition are properties of each object, and a careless copy resets them to defaults. The symptom arrives later as browsers downloading your images instead of displaying them.
One piece of good news on the way out. The cost of extracting your data used to be its own obstacle, and it's easing: AWS has offered free data transfer out to customers leaving the platform entirely since 2024, subject to a support request and its conditions, and the other large clouds have made comparable moves under regulatory pressure. The background to that shift, and why exit pricing is worth checking before you arrive rather than when you leave, is covered in S3 egress fees.
How Runsite reads on these axes
For a concrete answer to the questions above: Runsite's S3-compatible object storage doesn't meter transfer in either direction, with no allowance to exceed and no use policy converting free into conditional. Operations aren't billed either, and the free plan says so in as many words: unlimited operations. That leaves stored gigabytes as the only variable, at €0.025 per GB per month after 5 GB free as of writing, with one storage class and no minimum duration to price around. The dashboard reports storage, bandwidth and request counts in real time with alerts on thresholds, so the meters are visible whether or not they're billed.
On the rest: the API is S3-compatible, so the AWS SDK, boto3 or aws-cli work with the endpoint and keys swapped and no other change. Objects reach 5 TB and total storage is uncapped. Access keys carry read, write, delete or admin scope and rotate without downtime, which is the answer to the static-key problem above. Presigned URLs and per-bucket CORS get the browser talking to storage directly, and public buckets are served through a global CDN, so the byte path can leave your application out of it. Objects sit in a Frankfurt, Germany region encrypted with AES-256 at rest and TLS in transit, with no replication outside the EU and a signed GDPR Data Processing Agreement on every plan, which is the residency axis covered in full in where to store EU user data.
The short version
Zero-egress pricing moves the meter rather than removing it, so read for operations, minimum storage duration, minimum billable object size, and the conditions attached to the word free. Check where your bytes actually cross onto the internet first, because if that point is your application server, changing storage providers saves nothing until the browser fetches from the bucket instead. Match the provider to how your data behaves, since a request-heavy workload can cost more on free transfer than on metered S3. Budget for the identity change, since an IAM role becomes a static key you own, and for the AWS-only integrations that stay behind. If transfer isn't in your top three billing lines, staying put is a legitimate answer. And if the shortlist is down to a couple of names, the quickest way to settle it is 5 GB of free storage, your own SDK, and a weekend of real traffic.