vCPU or RAM? How to Size a VPS for Static Sites, Databases and Traffic Spikes
Cloud

vCPU or RAM? How to Size a VPS for Static Sites, Databases and Traffic Spikes

Four readers asked four versions of the same question, and each one has a different honest answer. Static files want RAM for page cache, Postgres wants RAM for shared_buffers, and "slow under traffic" is usually neither CPU nor RAM.

Published August 19, 202612 min readUpdated Sep 6, 2026

Written by · Full-Stack Agentic AI Software Engineer — AI Agents, Automation & Revenue Systems for GTM/RevOps teams

In brief

Should I size my VPS for CPU or for RAM, and how do I know when to upgrade?

It depends on which resource your workload exhausts first, and that's a different resource for different workloads. Static sites are bound by disk I/O and how much of your file set the OS page cache can hold in RAM, not by CPU. Postgres on the same box wants RAM for shared_buffers and the OS cache so the working set doesn't fall back to disk. "Slow under traffic" is very often neither CPU nor RAM — it's a connection limit, a missing index, or a single-threaded bottleneck that more of either resource won't fix.

  • Static file serving is I/O and page-cache bound, not CPU bound — the OS caches hot files in RAM automatically, no config needed
  • Postgres on a shared box needs RAM sized around shared_buffers (roughly 25% of total RAM) plus the OS cache holding the rest of the working set
  • A slow app under traffic is usually a concurrency ceiling (max_connections, an event loop, a thread pool) or a missing index, not raw CPU or RAM starvation
  • Watch swap usage and page cache hit rate, not just CPU percentage, to know when to actually upgrade

Evidence notes

shared_buffers guidance

PostgreSQL's own resource-consumption docs and the community wiki both converge on roughly 25% of system RAM as the starting point, with diminishing or negative returns above ~40% because the OS page cache also needs room.

Next.js memory under load

Standalone Next.js builds and servers have documented memory-growth issues on low-RAM boxes; the practical fix on a small VPS is a swap file plus a hard memory ceiling on the process, not just adding vCPUs.

Contabo tier specs

Cloud VPS 4: 4 vCPU / 8 GB RAM / 100 GB SSD at €5.50/month, promotional for the first 24 months, verified on Contabo's own pricing page, checked September 2026 — the baseline this whole cluster runs on.

"Should I get more vCPU or more RAM" is really four different questions wearing one sentence, and I get some version of it every time someone's about to rent a box. The honest answer changes depending on what's running on it, so I'm going to answer the four versions separately instead of giving you one number that's wrong for three of them.

The short version, if you want it before the mechanism: static files are bound by disk I/O and the OS page cache, not CPU. Postgres on the same box wants RAM specifically for shared_buffers and the page cache, not for query math. And "it got slow when traffic spiked" is, in my experience, almost never a CPU or RAM problem at all — it's something with a ceiling that more of either resource doesn't raise. The revenue-side version of VPS sizing is something XenGrowth's marketing operations practice writes about in more operational detail than I go into here.

My site is mostly static files and pages — is that CPU or RAM?

Neither, mostly, and that surprises people. Serving a flat file — an HTML page, a bundled JS chunk, an image — is not compute work. Nginx or Caddy reads bytes off disk and writes them to a socket. There's a little CPU for TLS handshakes and gzip/brotli compression, but on a modern 4-vCPU box that ceiling is high enough that you'll hit something else first.

What actually decides how fast a static site feels is whether the file being requested is sitting in the Linux page cache or has to come from disk. The kernel caches recently-read files in whatever RAM isn't already spoken for, automatically, with zero configuration from you. Request the same asset twice and the second read comes from memory in microseconds. The moment your total file set — images, fonts, prerendered HTML, cached API responses — stops fitting in free RAM, some fraction of requests start hitting the SSD instead, and that's where latency shows up first, not in CPU graphs.

So for a mostly-static workload, RAM matters, but not because the app needs it — because the more RAM sits idle, the more of your file set the kernel can cache for you for free. On a 100 GB-SSD box like Contabo's Cloud VPS 4, disk throughput is rarely the bottleneck either; static sites this size fit in the page cache with room to spare. You'd need either a genuinely huge media library or a traffic volume that saturates NVMe-class I/O before storage speed becomes the story.

Prices here are the ones Contabo advertises for the first 24 months, in euros. That matters when you are sizing for a system you expect to still be running in three years — size on the specs you need, then check what the renewal costs.

My database runs on the same box as my app — CPU or RAM?

RAM, and here's the actual mechanism instead of a hand-wave. Postgres keeps its own cache — shared_buffers — sized at startup, and the accepted starting point is around a quarter of total system RAM. That's not an arbitrary number: go much higher and you're taking memory away from the OS page cache, which is also caching your data files (Postgres relies on both layers, not just its own buffer). Go much lower and Postgres keeps evicting pages it needs again ten seconds later. The XenGrowth resource library works through the operations side of this in more operational detail.

The number that actually predicts your latency is whether your "working set" — the rows and indexes your queries actually touch during a typical hour, not your whole database — fits inside shared_buffers plus the OS cache combined. When it does, reads are memory speed. When it doesn't, every query that reaches past the cached portion pays a disk seek, and on a shared box that disk is also serving your app's own file reads and writes. That's the failure mode people mean when they say "Postgres got slow": not CPU contention, a working set that quietly outgrew the RAM that was caching it.

There's a second RAM cost people forget: work_mem. It's allocated per sort or hash operation, per connection, not once globally — so a query with a few sorts, multiplied by 40 concurrent connections, can eat far more RAM than shared_buffers alone would suggest. On the box I actually run — Postgres in Docker, behind Coolify, same 8 GB as the app — that's the number I watch, not total free memory, because it's the one that spikes under concurrency and doesn't show up until it does.

Traffic spiked and the app got slow — is that CPU, RAM, or something else?

This is the case where I've seen people burn money upgrading the wrong thing. "Slow under traffic" feels like it should mean "not enough compute," so the reflex is to double the vCPU count. Half the time that changes nothing, because the actual ceiling was never compute.

The two culprits I hit most often: a concurrency limit and a missing index. Postgres has a max_connections setting — a hard ceiling — and every framework's connection pool has one too. Once concurrent requests exceed that ceiling, new requests queue behind old ones instead of running in parallel, and queueing latency looks exactly like "the server got slow" from the outside even though CPU usage might sit at 20%. The fix there is a pooler (PgBouncer, or your ORM's own pool) sized correctly, not more silicon. Next.js's standalone server has its own version of this: documented memory growth under sustained load that eventually triggers the OOM killer on small boxes — that reads as "traffic broke it" too, and it's a memory ceiling on one process, not the VPS running out of RAM overall. If AI agents and marketing automation is the part you are stuck on, XenGrowth on governed AI marketing workflows is the better reference.

The missing-index case is the one that fools people longest, because it doesn't show up until the table crosses some row-count threshold. A query that scans a 5,000-row table sequentially is fast enough that nobody notices. The same query against 500,000 rows does the same sequential scan and now it's the slowest thing on the page — and it gets slower in direct proportion to how many of those queries traffic is now running concurrently, which makes it look exactly like a traffic problem. It isn't; it's an O(n) scan that finally has enough n to hurt. Check pg_stat_statements before you check the resource graphs. I've fixed "the server can't handle our launch traffic" with a single `CREATE INDEX` more times than I've fixed it by upgrading the plan.

None of that means CPU never matters. It genuinely does for a narrower set of workloads: report generation with heavy aggregation, image or video transcoding, anything doing real per-request compute rather than I/O and lookups. The tell is different too — a CPU-bound problem shows up as sustained high utilization across all cores while the app is otherwise responsive, not the queueing-and-timeout pattern you get from a saturated connection pool. If your metrics actually show that, more vCPU is the correct fix, not a workaround for something else.

So how do I actually choose CPU, RAM and storage — and when do I upgrade?

Start from the workload, not a spec sheet. The table below is the shortcut I wish someone had handed me — match your situation to a row, and that tells you which resource actually binds first and why.

Workload shape

Resource that binds first

Why

Static site / mostly prerendered pages

RAM (as page cache), not CPU

The kernel caches hot files for free; CPU only matters for TLS and compression, which rarely saturate first

Postgres co-located with the app

RAM

shared_buffers plus OS cache need to hold your working set, or every query past that boundary pays a disk seek

Bursty API traffic, request-response app

Concurrency ceiling (pool size, max_connections), not CPU/RAM

Requests queue behind a fixed limit long before the box itself is out of resources

Analytics / reporting queries, big aggregations

CPU and work_mem together

Sorts and hashes are genuinely compute- and memory-heavy per query, unlike simple row lookups

Background jobs, image processing, builds

CPU, briefly, in bursts

Actual compute work, but usually short-lived — size for the burst, not the steady state

Everything slow at once, all resources look fine

Almost always an index or a pool size

Real resource exhaustion shows up in exactly one graph; "everything is slow" is a code-path problem

For a box that runs a small app plus its own Postgres — which is the setup this whole cluster is built around — that points at RAM-heavy tiers over CPU-heavy ones, because the page cache and shared_buffers both compete for the same pool. Here's how Contabo's own tiers map onto that, checked against current pricing in September 2026:

Tier

Specs

Price

Suits

Cloud VPS 4

4 vCPU / 8 GB RAM / 100 GB SSD

€5.50/mo (first 24 mo.)

One app + co-located Postgres, low-to-moderate traffic — what this cluster runs on

Cloud VPS 6

6 vCPU / 12 GB RAM / 200 GB SSD

€7.50/mo (first 24 mo.)

A modest step up in RAM and vCPU for headroom, still SATA SSD — Contabo doesn't offer a separate NVMe tier at this size

Cloud VPS 8

8 vCPU / 24 GB RAM / 300 GB SSD

€14.00/mo (first 24 mo.)

Working set has outgrown shared_buffers on Cloud VPS 4 or 6, or you're running two apps plus a DB

Cloud VPS 12

12 vCPU / 48 GB RAM / 400 GB SSD

€25.00/mo (first 24 mo.)

Several apps, a DB with a genuinely large working set, or headroom for a traffic spike you can't predict

On upgrade timing: don't watch CPU percentage alone, it lies. Watch swap usage (any sustained swap on a database box is a sign shared_buffers or work_mem is oversized for what's left, or RAM itself is too small), page cache hit rate if your monitoring exposes it, and CPU steal time specifically if you're on a shared vCPU plan, since that's contention with other tenants rather than your own load. Cloudflare's cache sitting in front of the origin changes what "traffic spike" even means for the box itself — worth reading what its free tier actually absorbs before you assume every spike reaches Postgres at all. But on the origin's own metrics, the pattern that actually predicts an upgrade is swap creeping up over days, not a CPU spike during one bad afternoon. XenGrowth on building one SEO and GEO content system approaches this from the AI search, GEO and discovery side.

A CPU graph pinned at 90% for ten minutes during a traffic spike is a story. A CPU graph that's fine but swap keeps climbing every week is the one that means upgrade now, before it becomes an outage.

When does storage actually matter, separately from RAM?

Storage gets folded into "RAM vs CPU" less often than it should. It matters on its own in two cases: total capacity (backups, media, WAL for Postgres all consume it independently of RAM or CPU, and running out of disk space is a much uglier failure than running out of either) and raw I/O throughput, which only becomes the bottleneck once your working set genuinely can't fit in RAM at any price you're willing to pay — a large media library, or a database bigger than any tier's RAM makes sensible. Below that threshold, more storage speed just makes the eventual cache miss cheaper; it doesn't remove the miss.

Postgres has a storage wrinkle worth naming: WAL (write-ahead log) writes go to disk on every commit, ahead of the actual data files, so a database with a heavy write rate cares about disk latency even when its read working set fits entirely in RAM. That's the specific case where NVMe over SATA SSD earns its cost — not because your data is bigger, but because commits are more frequent and each one is waiting on a fsync. some providers sell a separate NVMe-optimized line for exactly this tradeoff — less RAM for faster commits versus more RAM on SATA SSD; Contabo's own current lineup doesn't split it that way, so check whichever provider's actual spec sheet you're comparing rather than assuming the option exists. Which one wins for you depends on whether your bottleneck is cache size or commit latency, and the only way to know is to look at which one you're actually hitting, not guess from the spec sheet.

If you're deciding this for a SaaS that's still finding its shape rather than one that's already outgrown a single box, the sizing question folds into a bigger one: what should even be self-hosted in the first place, versus what's cheaper to keep paying someone else for. I've written up the rule I actually use for that decision separately, because it's a different question from how big the box should be once you've decided to run it yourself.

The pattern underneath all four cases is the same one: figure out which resource your specific workload actually exhausts first, then size for that, watching the metric that predicts it rather than the one that's easiest to graph. CPU percentage is easy to graph and rarely the answer. Swap, cache hit rate, and connection queue depth are less convenient and almost always closer to the truth.

Further reading from XenGrowth

Where this work meets go-to-market

the team at XenGrowth covers the go-to-market side of VPS sizing, which this piece deliberately leaves alone.

Size your own box

Five questions, then a starting point. It follows the same reasoning as the post — what binds first for your workload shape.

1 / 5
What does the app mostly do?

Pick what the majority of requests hit.

Apply this article

How to turn insights into execution

A practical sequence for teams turning concepts into production outcomes.

VPS sizingContaboPostgreSQLself-hostingcapacity planningserver tuningcloud

Audit your current state

Map the bottlenecks and constraints connected to the article’s core problem.

Choose one bounded change

Test the most useful recommendation on one workflow before widening the scope.

Measure what changed

Keep the parts that improve the work, document what failed, and make the next decision from evidence.

Next step

Need help applying this in your stack?

I can translate these patterns into a concrete implementation plan for your team.

Discuss implementationBack to blog

Replies usually within 24 hours.

Next Steps

Continue reading

One Big VPS or Several Small Ones? How Many Apps Fit on One Box

The question isn't how many containers fit in the RAM. It's what happens the day one of them takes the box down with it, and whether you'd rather patch one server or four.

Navigate

My Complete Self-Hosted Stack for SaaS in 2026

Contabo, Coolify, Docker, Cloudflare, Postgres, R2, Resend, Uptime Kuma, Turborepo and Docker Hub. Here's every piece of the stack I actually run, what each one replaced, and why I picked it over the alternatives.

Navigate

Should Your Database Live on the Same VPS as Your App?

The pitch for co-location is real: no network hop, no egress bill, one box to back up. So is the failure mode — one OOM event takes the app and the database down together, because they were never separate to begin with.

Navigate

How I Self-Host PostgreSQL for My SaaS (and When I Wouldn't)

Running Postgres in a container is easy. Running it in a way that survives a redeploy, a full disk, and an eventual major-version upgrade is the actual job. Here's the setup, tuned against Postgres's own defaults, and the honest list of where managed wins outright.

Navigate

The Backup Strategy Every Self-Hosted SaaS Needs (3-2-1, Applied)

3-2-1 is easy to nod along to and easy to get wrong in the specific way that only shows up on the day you need it. Here's what it actually means for one VPS running Postgres and Docker volumes, not the generic version you've already skimmed past twice.

Navigate

The Simplest Production Architecture for a Bootstrapped SaaS

One box, a handful of managed pieces around the edges, and a very short list of things you're not allowed to build yet. Here's the architecture, priced out to $20 a month, and the exact signal that tells you when to add each thing you skipped.

Navigate

Moving a SaaS Off Vercel to a €5.50 VPS: What the Numbers Actually Look Like

Nobody publishes their real Vercel invoice, so most migration posts trade in vibes instead of arithmetic. This one builds the comparison from Vercel's and Contabo's own published rates, states every assumption out loud, and shows exactly where the two lines cross.

Navigate

What "Unlimited Bandwidth" Really Means on a VPS

Nobody actually gets an unbounded pipe to the internet for $7 a month. What you get is a port speed, a fair-use clause, and a provider's word that they'll throttle you before they bill you.

Navigate

Best VPS Providers for Coolify: Contabo, Hetzner and the 2026 Price Shift

Every 'best VPS for Coolify' post still says Hetzner is the cheap option. That stopped being reliably true in June 2026 for two of its most popular tiers, and nobody's updated the recommendation yet.

Navigate

$5 vs $10 vs $20 VPS: What Each Tier Actually Buys You

The spec sheets look close enough to round to the same box. They aren't — the gap between tiers is mostly in what you don't see printed: whether your vCPU is shared, and with how many strangers.

Navigate