In April 2026, Healthchecks.io — a profitable, famously lean cron-monitoring SaaS run by a tiny team — published a post titled "Healthchecks.io now uses self-hosted object storage." It reached 195 points and 79 comments on Hacker News, and it documents something the "never self-host state" crowd insists doesn't happen: a production service pulling 14 million objects (119GB) off managed object storage and onto a single dedicated server it now operates itself.
Here is the twist that makes it worth reading closely: the move made their bill go up. Founder Pēteris Caune says so plainly — "renting an additional dedicated server costs more than storing ~100GB at a managed object storage service." The egress-and-request-fee math in this story doesn't justify the migration; it explains something subtler and more useful. It explains why a hyperscaler bucket was never on the table in the first place, why the cheap managed tier they did use kept failing them in ways the invoice never showed, and why the "expensive" self-hosted option won anyway. Let's run the actual numbers.
One clarification before the math, because the shorthand misleads: Healthchecks.io was never on AWS S3. Since 2022 it stored ping bodies at S3-compatible managed providers — OVHcloud, then UpCloud. "S3" in this story means the API, not the Amazon product. AWS appears below only as the default everyone benchmarks against, and as the option Healthchecks.io explicitly rejected.
The Bill, Four Ways: Pricing the Actual Workload
First, the workload, straight from the write-up. Healthchecks.io stores the first 100kB of every HTTP POST ping body its clients send. That adds up to 14 million objects totaling 119GB — an average object size of about 8KB — written at 30 uploads per second on average, with regular spikes to 150 uploads per second.
That profile — tiny objects, relentless write rate, modest total volume — is exactly the shape that breaks hyperscaler pricing. Call the full exit-cost ledger of a cloud bucket "the egress math": per-request fees, data-transfer-out charges, and the switching costs they quietly create. For this workload, the load-bearing line is request fees, not egress — but both belong on the page. Priced four ways:
| Option | Storage | Requests | Egress | Monthly total |
|---|---|---|---|---|
| AWS S3 Standard (their real write-heavy workload) | 119GB × $0.023 ≈ $2.74 | ~78M PUTs × $0.005/1k ≈ $390 | negligible (writes) | ~$393 |
| AWS S3, read-heavy variant (serve the bucket out 10×/mo) | $2.74 | $390 + GET fees | ~1.2TB × $0.09/GB ≈ $107 | ~$500 |
| EU managed, no per-request fees (OVHcloud / UpCloud — what they used) | ~€1–5 | $0 | included/negligible | single-digit € |
| Self-hosted: dedicated NVMe server + backup server | — | $0 | $0 | ~€50–100 |
Walk through the rows. Thirty uploads per second is roughly 78 million PUT requests a month. On AWS S3, PUTs cost $0.005 per thousand, so the request line alone is about $390 — while storing the 119GB costs $2.74. The request fees outweigh the storage fees by a factor of about 140. Nobody storing 119GB expects a ~$400 bill, which is precisely why per-request pricing is the quiet killer for high-frequency, small-object workloads.
The egress trap is the mirror image, and it deserves its own number even though Healthchecks.io's workload is write-heavy. If the same bucket served its contents out ten times a month — a read-heavy monitoring dashboard, say, or an API replaying stored payloads — that's roughly 1.2TB of data transfer at $0.09/GB: another ~$107 every month, forever, for data you already paid to store. Egress is also the switching cost: the day you leave, pulling your own 119GB out is cheap, but pulling 119TB out at $0.09/GB is a $10,700 exit toll. The math that looks negligible at signup compounds into lock-in.
Row three is the honest middle: European providers like OVHcloud and UpCloud charge no per-request fees at all, and 119GB of storage runs single-digit euros per month. This is what Healthchecks.io actually paid for four years. On sticker price, it is unbeatable.
And row four — the option they chose — is the most expensive of the lot: a dedicated NVMe server plus a backup server lands somewhere around €50–100 a month depending on provider and specs. Self-hosting 119GB did not save Healthchecks.io money, and Caune never claims it did.
So why do it?
Why They Actually Left: The Line Items That Aren't on the Invoice
The answer is in the failure history, which the invoice never showed.
Healthchecks.io picked OVHcloud object storage in 2022 precisely because of the math above — no per-request fees, EU-based (AWS was rejected both for request pricing and because, being subject to the US CLOUD Act, it would have forced client-side encryption of every ping body before upload, "which would add complexity"). Then OVHcloud's performance and reliability deteriorated. In 2024 they migrated to UpCloud — same pricing logic, initially better quality. Then UpCloud degraded too: DeleteObjects operations "were getting slower and slower over time," and there were "periods where all operations would become slow and hit our timeout limits."
That is two managed providers in four years, both cheap, both fine at first, both drifting into latency and reliability problems the customer could measure but not fix. A managed service's sticker price hides a variable you cannot control: how the operator's aggregate load and priorities evolve underneath you. When the service degrades, your only lever is another migration.
The post-migration result validates the diagnosis. After moving to their own hardware, S3 operation latencies dropped sharply and the queue of pending uploads shrank. The performance problem wasn't their code or their workload. It was the shared platform.
This is the part of the egress math that never appears on a pricing page: the cost of not controlling the tail latency of your own storage, priced in engineering hours spent chasing another provider's slowdowns and in timeouts your users feel.
What Self-Hosting Adds to the Pager
The counter-argument writes itself, and the HN thread wrote it loudly: you just traded a monthly invoice for pager duty. It's worth itemizing exactly what Healthchecks.io took on, because the honest accounting is what makes this a useful datapoint rather than a hero story.
The stack is deliberately boring. Versity S3 Gateway — an Apache-2.0 Go project (~2.7k GitHub stars) — translates S3 API calls onto a plain local filesystem. One binary, one systemd service; the documented upgrade procedure is "replace a single binary and restart." It runs on a dedicated server with two NVMe drives in RAID 1, formatted with Btrfs — chosen over ext4 because 14 million small files is exactly the workload where a filesystem can exhaust inodes, and Btrfs allocates them dynamically. Caune evaluated MinIO, SeaweedFS, and Garage and rejected all three for operational complexity — a striking verdict given MinIO's ubiquity, and a reminder that for a single-node deployment, cluster-grade software is overhead, not safety.
What's not in the stack matters just as much. As one commenter (sigmonsays) pointed out, versitygw itself "does not include any erasure coding or replication." Durability is entirely homemade:
- RAID 1 NVMe absorbs a single-disk failure.
- Every two hours, rsync synchronizes added and deleted files to a second, backup server.
- Daily, that backup server takes a full backup, encrypts it, and ships it off-site, with 30 days of retention.
- Declared blast radius: up to two hours of not-yet-backed-up ping bodies. For payloads whose primary job is debugging context on recent cron runs, that RPO is a rational business decision — stated in public, which is more than most architectures can say.
The new pager items, named plainly: disk failures (mitigated, not eliminated, by RAID), filesystem risk (skeptics in the thread offered "Btrfs is not a strategy"), capacity planning (119GB on NVMe leaves years of headroom, but someone now owns watching it), backup verification (an untested restore is a wish), and single-node availability — if that server is down, uploads queue.
Also worth stealing: the migration itself ran with zero downtime. Uploads were double-written to both old and new storage, reads fell back to the old provider for objects not yet migrated, and a background process copied history over. Boring, reversible, correct.
And to a commenter asking why not "just use the filesystem" — Caune's answer is instructive for anyone designing a platform: multiple web servers need shared access to the same blobs, and keeping the S3 API meant zero application code changes across two provider migrations and now a self-hosting move. The S3 API isn't the product. It's the interface that makes storage swappable.
The Same Math on a Fleet You Already Run
Healthchecks.io's numbers have one property that doesn't generalize: they had to rent a new server, so self-hosting showed up as +€50–100 against a single-digit managed bill. For a team that already operates a fleet — say, Hetzner dedicated boxes under Cluster API running a self-hosted PaaS — the marginal hardware cost of 119GB of hot object storage is approximately zero. The NVMe drives are already spinning; versitygw is one more systemd unit (or one more Deployment) on capacity you've paid for.
That changes each row of the table. The hyperscaler rows stay absurd — the ~$390/month of PUT fees doesn't care where your compute lives, and if your compute is outside AWS, egress charges apply to every read, making an S3 bucket next to a Hetzner fleet the worst of both worlds. The managed-EU row stays cheap but keeps the risk Healthchecks.io hit twice: someone else's tail latency, on someone else's roadmap. The self-hosted row drops from "+€50–100 and a new pager duty" to "+€0 hardware and a pager duty you already carry," because a team running its own control plane already owns disks, backups, and capacity planning for the platform itself.
The software calculus shifts too. Caune rejected MinIO, SeaweedFS, and Garage as operationally complex for one node — the right call at his scale. On a three-plus-node fleet, Garage's design (replication-first, WAN-tolerant, praised in the same HN thread) turns the durability story from "rsync every two hours" into synchronous replication across machines you already own, erasing the 2-hour RPO without adding a vendor. Same math, different topology, different winner.
This is the context where the "never self-host state" rule quietly inverts. The rule exists because state is where self-hosting mistakes become unrecoverable — and it's a good default for a two-person team with no ops muscle. But if you already run the platform layer — provisioning machines with Cluster API, deploying from git, operating your own ingress and TLS — object storage for build artifacts, logs, and user blobs is not a new competency. It's one more workload on the fleet, with the egress meter unplugged. (That's the philosophy behind Bex.co, the open-source, AI-native Render alternative: the machines are yours, so the storage sitting beside your apps can be yours too — Bex deploys and operates the apps; what you run next to them, like a versitygw or Garage, is your call and your hardware.)
When to Self-Host State: A Decision Rule
Boil the Healthchecks.io datapoint down to an operator's rule of thumb:
Self-host object storage when the dataset is small and hot (tens to hundreds of GB), the request rate is high enough that per-request pricing dominates (Healthchecks.io: 140× the storage cost), latency is user-visible, and — the load-bearing condition — you already run hardware and carry a pager. Then self-hosting costs little more than attention, and buys you the tail latency no managed tier will promise you.
Stay managed when the data is large and cold (per-GB pricing is where managed providers genuinely win), you have no existing ops practice, or losing the dataset is existential and you can't yet prove your restores. But stay managed smartly: for most non-AWS-native workloads that means a no-per-request-fee provider, not the hyperscaler default — and it means writing down, the way Caune did, what your provider's degradation would cost you, because that line isn't on the invoice until the day it's the only line that matters.
The real lesson of Healthchecks.io's move isn't that self-hosting is cheaper. For them it wasn't, and they shipped it anyway, eyes open, with a two-hour RPO written in public. The lesson is that the egress-and-request-fee math tells you which options were never real (a hyperscaler bucket for 78 million tiny PUTs a month), while the numbers not on any invoice — tail latency, provider drift, the cost of your third forced migration — decide among the options that remain. A famously lean production SaaS just showed its work: state on your own metal is not reckless. Unexamined defaults are.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.
Sources: Healthchecks.io now uses self-hosted object storage (April 2026); Hacker News discussion (195 points, 79 comments); Versity S3 Gateway; AWS S3 public pricing (Standard storage $0.023/GB, PUT $0.005/1k requests, egress $0.09/GB).



