"It's just a $5 VPS" is the most expensive sentence in database operations. I've said it myself. The VM really is cheap. The database running on it is not the product you think you're buying. It's a kit. What you're actually pricing is everything you have to bolt on before you'd trust it with customer data, plus the hours you'll spend bolting.
This is the comparison I wish someone had written down for me ten years ago: what self-hosting actually takes, what it costs in time, when it's still the right call, and how to grade a managed provider so you don't trade one set of problems for a glossier set.
The checklist nobody prices in#
A bare VM with Postgres installed is maybe 20% of a production database. Here's the other 80%, in the order it will hurt you.
Backups you have actually restored. Not backups you configured. Backups you restored, last month, on purpose. The standard self-hosted stack is pgBackRest shipping to object storage with WAL archiving for point-in-time recovery, and the part everyone skips is the scheduled restore drill:
pgbackrest --stanza=main --delta --type=time \
"--target=2026-06-02 03:12:00" restoreThe first time you run that command should not be during an incident. A backup that has never been restored is a hope, not a backup, and the failure modes are silent: an archive command that started erroring three months ago, a retention policy that expired the only good full, a cron job backing up the wrong cluster. pgbackrest info saying ok is necessary, not sufficient.
Failover. One node means your availability is the VM's availability, and host migrations, hypervisor reboots, and disk failures are not hypothetical. Real HA means a streaming standby, something like Patroni plus etcd to manage promotion, and a connection layer (HAProxy, a load balancer, or DNS you trust) that follows the new primary. Then you have to actually test a failover, because an untested failover is just a more elaborate outage.
Upgrades. Minor Postgres releases land roughly quarterly and you should apply them within weeks, because some are security fixes. Major versions are an annual project: pg_upgrade with a rehearsed rollback, or logical replication if you can't take the window. Extensions move too. If you run pgvector, ALTER EXTENSION vector UPDATE is the easy part; deciding whether to rebuild indexes to pick up improvements is the part that needs a human who's been paying attention.
Monitoring. Prometheus, postgres_exporter, Grafana, and alert rules. The glamorous dashboards are query latency and cache hit ratio. The alert that will actually save you is the boring one: disk above 80%. After that: replication lag, connection saturation, autovacuum falling behind, and transaction ID age if you want to sleep well.
Security patching. The OS underneath the database is yours now. OpenSSL CVEs, kernel updates that want a reboot (which on a single node means an outage window), pg_hba.conf hygiene, TLS certificates that expire on a Saturday, and the port 5432 you exposed "temporarily" during debugging that's still open to the internet eight months later.
Disk management. Postgres has a special relationship with full disks: when it can't write WAL, it doesn't degrade gracefully. It PANICs. The classic trap is a replication slot left behind by a decommissioned standby, quietly retaining WAL forever:
SELECT slot_name,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn))
AS retained_wal
FROM pg_replication_slots
WHERE NOT active;
slot_name | retained_wal
----------------+--------------
old_standby_eu | 41 GBSet max_slot_wal_keep_size so a dead slot can't eat the disk, and alert on inactive slots. Most people learn about this setting at 2 AM.
None of these tasks is hard in isolation. The problem is that they're all ongoing, they all fail silently, and they all assume someone is looking.
The real monthly time cost#
Here's my honest accounting for a single production Postgres, run properly by someone competent who is not a full-time DBA:
- Patching and minor upgrades: 1 to 2 hours/month
- Reviewing alerts, tuning thresholds, dashboard sanity checks: 1 hour/month
- A quarterly restore drill, amortized: ~1 hour/month
- A yearly major-version upgrade, amortized: ~1 hour/month
- Capacity decisions, disk cleanup, vacuum babysitting: 30 to 60 min/month
Call it 4 to 6 hours a month in the steady state. At a loaded engineering cost of $80 to $120/hour, your "$25 VM" carries $350 to $700/month of attention before anything goes wrong.
And the steady state isn't the cost that kills you. The variance is. One real incident, whether a corrupted backup chain, a failed failover, or a disk that filled during a holiday, eats a week and burns the trust of whoever was waiting on the features you didn't ship. Self-hosting doesn't cost five hours a month; it costs five hours a month most months.
Every self-hosted disaster is one of two stories#
I've been collecting these for years, secondhand and firsthand, and the pattern is embarrassing in its consistency.
Story one: the backup that wasn't. The cron job that stopped after an OS upgrade changed a path. The pg_dump of a database that had been renamed. The retention policy that kept seven dailies of an archive job that had been writing zero-byte files since March. Nobody notices, because backups are write-only, right up until the day they're read, which is by definition the worst possible day.
Story two: the disk that filled. WAL retained by a forgotten slot, logs with no rotation, a table that doubled after a product launch. Disk hits 100%, Postgres panics, and now you're trying to free space on a machine with no space, under pressure, while the app is down. Deleting the wrong file in pg_wal/ at this moment converts an outage into data loss.
Both stories have the same root cause: nobody's job was to look at this database every week. Not lack of skill, but lack of an owner.
When self-hosting genuinely wins#
I run managed infrastructure for a living and I'll still tell you there are three cases where self-hosting is the right answer.
You already employ the team. If you have platform engineers or DBAs running a fleet of Postgres instances, the marginal cost of one more is near zero, and a good in-house team will beat any vendor's defaults for your workload. The checklist above is already someone's staffed job. Don't pay twice.
Compliance forces your hand. Some regulatory and contractual regimes genuinely require on-prem or your-own-cloud deployment. If your auditor says the data cannot leave hardware you control, that's the end of the discussion. (Be precise about what's actually required, though: "data must stay in the EU under GDPR without US CLOUD Act exposure" is a hosting-region requirement, not a self-hosting requirement.)
You're learning. Running your own Postgres with real, low-stakes data is the best Postgres education there is. Break the replication, fill the disk on purpose, restore from backup with a timer running. Six months of that and you'll understand exactly what you're paying any vendor to do, which makes you a much sharper customer.
What's not on this list: saving money at small scale. Let's do that math.
The small-scale math, done honestly#
The comparison people make is a $25 VPS against a managed plan, and the VPS "wins." But a $25 VPS isn't the self-hosted equivalent of a managed database. The honest bill of materials for the DIY version of production-grade looks like:
| Line item | DIY, done properly |
|---|---|
| Primary VM (2 vCPU / 4 GB, NVMe) | ~$25/mo |
| Standby VM for failover | ~$25/mo |
| Object storage for backups + WAL | ~$5/mo |
| Monitoring stack (small VM or hosted) | ~$10/mo |
| Your time, 4 to 6 h/mo at $80/h | $320 to $480/mo |
| Total | ~$385 to $545/mo |
Against that, managed Postgres with pgvector on a dedicated 2 vCPU / 4 GB NVMe node with daily backups and 14-day point-in-time recovery runs $49/month flat on Rivestack as the Starter tier, the entry point for HA-ready clusters. (If you don't need HA, a single-node Solo instance with 1 vCPU / 2 GB / 30 GB NVMe and 7-day PITR runs $29/month.) Flat matters: no usage-based billing means no surprise line item when your app gets traffic.
Even if you think my hours estimate is double the truth, the managed option wins on cash and time at small scale. The crossover where self-hosting gets cheaper exists, but it's measured in fleets of databases, not one or two, because the labor cost amortizes across instances, not within one.
One more thing the cash math hides: the standby in the DIY column only helps if you've built and tested the failover machinery around it. A standby you've never promoted is decoration.
What to demand from any managed provider#
Outsourcing the checklist doesn't mean you stop caring about it. It means you interrogate the vendor with it. Here's what I'd ask anyone, including us.
The restore story. Not "do you have backups," because everyone says yes. Ask: "Walk me through restoring my database to 3:12 AM last Tuesday. What's the retention? Who's tested it?" PITR with a stated window, or walk away.
The failover story, honestly told. Ask what the standby actually does. A lot of products imply that adding nodes scales your reads. Often it doesn't. On Rivestack, for instance, added nodes are streaming-replication standbys for automatic failover only; every query, reads included, routes to the primary, and read-serving replicas are roadmap, not reality. A vendor that volunteers the unflattering version of its own architecture is telling you something useful about everything else it claims.
Benchmark methodology, not benchmark marketing. A throughput number without its recall and concurrency is decoration. An honest pgvector figure looks like: ~980 QPS at recall@10 0.90, p50 3.8 ms, 4 clients, 250k 1536-dim vectors on a 1 vCPU Solo node. If a provider publishes peak QPS next to minimum latency as if they happen simultaneously, they're hoping you won't ask. The NVMe vs cloud-SSD benchmark writeup covers what honest methodology looks like, with the embarrassing numbers left in.
Sizing honesty. Memory limits are real and vendors should state them. I've watched a 500k-vector HNSW build grind for over four hours on a 4 GB node, while the same node builds 250k in about five minutes. Builds are memory-bound, and a provider that lets you discover the cliff yourself isn't doing you a favor. Ask for the working-set ceiling per plan, in vectors or gigabytes.
Pooling details, in writing. Connection pooling is where managed databases hide their sharpest edge. Under PgBouncer transaction pooling, a session-level SET hnsw.ef_search = 100; is silently dropped before your next query, so your recall tuning does nothing and nothing errors. The fix is SET LOCAL inside the query's transaction, or ALTER DATABASE ... SET for a persistent default; the HNSW tuning guide walks through it. If a provider's docs don't mention this class of gotcha, they haven't run serious workloads on their own product.
The exit. Plain pg_dump/pg_restore and logical replication should work, because it should be plain Postgres. Any proprietary layer between you and your data is a tax you'll pay at the worst time.
What I'd actually pick#
If you're a solo developer or a small team shipping product: managed, without agonizing. Your scarce resource is attention, and a database is the worst possible place to spend it, because database work is invisible when it succeeds and catastrophic when it fails.
If you have a platform team already running Postgres at scale: keep self-hosting. You've staffed the job; the economics flipped long ago.
If you're somewhere in between, a team of five with one infrastructure-curious engineer, be honest about what you're signing that person up for. "Ahmed kind of knows Postgres" is not an on-call rotation. Either make it a real, resourced responsibility with restore drills on the calendar, or pay someone whose whole business is the checklist.
And if you're self-hosting to learn: genuinely, do it. Just do it on data you can afford to lose, and run the restore drill before you need it. That habit alone puts you ahead of half the production databases on the internet.
Frequently asked questions#
How much time does self-hosting PostgreSQL really take per month?
For a single production instance run properly: 4 to 6 hours a month in the steady state, covering patching, alert review, a quarterly restore drill, and amortized upgrade work. The catch is variance. A single incident (failed backup chain, full disk, botched failover) can consume a week, and incidents cluster around exactly the moments you're busiest. Budget for the bad month, not the average one.
Is a cheap VPS ever enough for a production database?
For a hobby project where losing the data is annoying rather than fatal, yes. Take a nightly pg_dump to object storage, test a restore once, set a disk alert, and you're in reasonable shape. The line is customer data. The moment someone else's business depends on your database, you need restore-tested PITR, failover, and monitoring, and at that point the VPS price is a small fraction of the true cost.
What should I check before trusting a managed PostgreSQL provider?
Five things: point-in-time recovery with a stated retention window and a concrete restore procedure; automatic failover with an honest account of what standbys do (many route all traffic, reads included, to the primary); published benchmark methodology where every throughput number carries its recall and client count; documented connection-pooling behavior, including which session settings survive transaction pooling; and a clean exit via pg_dump or logical replication. A vendor who answers all five without flinching is rare and worth keeping.
Do standby nodes give me read scaling?
Usually not, and this is the most common false assumption about managed HA. In many architectures, including some that market "add nodes for scale," standbys exist purely for automatic failover, and the load balancer sends every query to the primary. Read-serving replicas are a separate feature with its own consistency tradeoffs (replication lag means a read replica can serve stale data). Ask the question explicitly and get the answer in writing.
How do I migrate off a self-hosted PostgreSQL without downtime?
Small databases: pg_dump/pg_restore during a short maintenance window, since it's the same Postgres on both sides, so schema, extensions, and queries move unchanged. Always-on systems: logical replication, so you replicate to the new primary, let it catch up, then cut over with seconds of write pause. Some managed providers will run the migration for you at no cost; it's worth asking before you script it yourself.
# keep reading