PERFORMANCE
August 5, 2026

How to Choose Between More RAM and More CPU

7 min read
Author
CloudStick Team
Security Specialist
Share this article
How to Choose Between More RAM and More CPU
CloudStick
RAM or CPU?
Diagnose First

Why Guessing Wastes Money

A slow server is not evidence that either resource is short — it is evidence that something is short, and the two most common upgrade paths fix completely different problems. Doubling vCPUs on a server that is swapping does nothing, because the bottleneck was never processing speed; doubling RAM on a server pegged at load average 8 on a 2-core box does nothing either, because there was RAM to spare the whole time.

Providers price CPU and RAM separately for a reason: they are different physical resources with different failure signatures, and paying for the wrong one means the site stays slow while the bill goes up. The fix is always the same three steps — measure which resource is actually saturated, confirm it with a second metric, then upgrade that one. The rest of this article is that measurement process.

Diagnosing RAM Pressure

RAM pressure has three distinct, unambiguous signatures: swap usage that keeps climbing, OOM-killer entries in the kernel log, and PHP-FPM refusing new workers because it already hit pm.max_children. Any one of these confirms the server needs more memory, not more cores.

free -h
total used free shared buff/cache available
Mem: 7.8Gi 6.9Gi 180Mi 90Mi 740Mi 650Mi
Swap: 2.0Gi 1.4Gi 620Mi
vmstat 1 5
# watch the "si" and "so" columns — nonzero and rising means the kernel
# is actively paging memory to and from disk right now, not just once
dmesg -T | grep -i "out of memory"
dmesg -T | grep -i oom
# any "Killed process" line naming php-fpm or mysqld confirms an OOM kill

Swap climbing under normal traffic — not spiking once during a backup and settling back down — means physical RAM is genuinely exhausted and the kernel is using disk as an overflow, which is orders of magnitude slower and drags every process on the box down with it. dmesg entries naming the OOM killer are the most severe signal: the kernel decided a process had to die to keep the system alive, and if that process was mysqld or a PHP-FPM worker, the site went down because of memory, full stop.

The subtler signal is PHP-FPM hitting pm.max_children. Each FPM worker reserves a fixed chunk of RAM for the lifetime of the request — typically 40 to 80MB for a WordPress process with a handful of plugins — so the pool's child limit is really a memory budget divided by the per-worker footprint. Check /home/<username>/logs/<sitename>/php83cs-fpm/error.log for lines reading "server reached pm.max_children, consider raising it." That message means concurrent requests are queuing because there is no RAM headroom to spawn another worker — raising the limit without adding RAM just moves the crash from "requests wait" to "server swaps or OOM-kills."

Diagnosing CPU Pressure

CPU pressure shows up as a load average that outruns your core count, combined with request times that climb specifically under concurrency rather than staying flat. Unlike RAM pressure, there is no single log line to grep for — you have to compare load to cores directly.

nproc
4
uptime
14:22:03 up 41 days, 3:12, 1 user, load average: 7.84, 6.90, 5.15
# 3 numbers = 1/5/15 min avg. Sustained load well above nproc (4 here)
# means processes are queuing for CPU time, not just busy
top
# or: htop — sort by CPU%, check if %us (user) stays pegged near 100%
# across most cores, not just one runaway process

Load average is not a percentage — it is the number of processes wanting CPU time at once, averaged over the last 1, 5, and 15 minutes. A load average of 3 on a 4-core box means cores are mostly idle; a load average of 8 on that same 4-core box means, on average, twice as many processes wanted a core as there were cores available, and they were queued waiting their turn. That queuing is exactly what you feel as slow page loads even though nothing crashed.

The confirming signal is timing under load, not at rest. Time a PHP request with one visitor, then time the same request during a traffic spike — if execution time roughly doubles or triples as concurrent requests increase, but RAM and swap stay flat the whole time, that is CPU contention, not a memory problem. The same pattern in MySQL — query time creeping up under concurrent connections while free -h shows plenty of available memory — points at the same root cause: not enough cores to run everything that wants to run right now.

How Caching Trades RAM for CPU

Every caching layer in a typical PHP stack is fundamentally the same deal: spend RAM to hold a precomputed result so the CPU never has to compute it again. OPcache keeps compiled PHP bytecode in memory instead of re-parsing and re-compiling source files on every request — pure CPU work eliminated at the cost of a memory allocation. Redis caches database query results, session data, or full rendered fragments in RAM, so a request that would have meant a MySQL round trip and PHP processing instead becomes a key lookup in memory.

This is why, for the overwhelming majority of WordPress and general PHP workloads, more RAM for caching beats more CPU almost every time. A server that is CPU-bound because every request recompiles templates, re-runs the same expensive queries, and rebuilds the same HTML fragments from scratch is CPU-bound by choice — that work is redundant, and caching it removes the CPU cost entirely rather than just spreading it across more cores. Adding cores makes redundant work faster; adding cache makes it disappear.

TIP

Before touching a plan upgrade, open the CloudStick dashboard's Server panel and look at the CPU and RAM graphs side by side over the last 24 to 48 hours. Because they run off Zabbix Agent 2 metrics rather than a single snapshot, you can see exactly which line was actually saturated during the slow periods — no SSH session, no guessing — before you commit to paying for either resource.

The one caveat: caching is not free RAM out of nowhere. A Redis instance sized too large for the box will itself become the thing pushing you into swap, and an oversized OPcache pool is wasted allocation if the codebase is small. Size the cache to the working set of your actual traffic, then measure again — the goal is to trade a small, fixed amount of RAM for a much larger, recurring amount of CPU time, not to over-provision either one blindly.

Which Workloads Are Actually CPU-Bound

Genuinely CPU-bound workloads are the ones that perform real, non-repeatable computation on every request rather than fetching or assembling something that could be cached: image resizing and format conversion at upload time, video transcoding, PDF generation from large documents, machine learning inference, and any batch job that crunches large datasets. Each of those does new work every single time — there is nothing to cache because the input is different each call, so more cores genuinely speeds it up.

Almost everything else in a typical agency or SaaS stack is RAM-bound instead. Many concurrent PHP-FPM workers means many copies of the PHP runtime sitting in memory at once, not many CPU cycles being spent — the fix is more RAM to raise pm.max_children safely, not more cores. Many simultaneous MySQL connections consume memory per connection (buffers, sort space, temp tables) well before they consume meaningful CPU, unless the queries themselves are unindexed and scanning full tables. Redis cache size is, by definition, a RAM allocation with almost no CPU cost attached. If your traffic problem is "a lot of WordPress visitors at once" rather than "a few visitors doing something computationally heavy," you are looking at a RAM-bound workload no matter how busy the server feels.

The Upgrade Decision Checklist

Run through these checks in order before spending a dollar on either upgrade:

1. free -h -> swap "used" climbing under normal load? RAM
2. dmesg -T | grep -i oom -> any kill entries for mysqld/php-fpm? RAM
3. FPM error log -> "reached pm.max_children" warnings? RAM
4. uptime -> load average > nproc, sustained? CPU
5. request timing -> exec time rises only under concurrency? CPU
6. workload type -> transcoding/rendering/inference? CPU
many FPM workers / DB connections / cache size only? RAM

If checks 1 through 3 came back positive, buy RAM — and check whether OPcache and Redis are even enabled first, since expanding cache can remove the pressure without any upgrade at all. If checks 4 through 6 came back positive and the workload genuinely does new computation per request, buy CPU. If both sets of checks come back clean, the server is not actually the bottleneck; look at DNS, external API calls, or a single slow query instead.

Whichever way the evidence points, upgrading a server already connected to CloudStick does not mean reinstalling Nginx, PHP-FPM, MariaDB, or any of your sites — resize the underlying VPS with your provider, and CloudStick keeps managing the same server with the same configuration once it comes back online. That makes it cheap to be wrong in either direction the first time and correct the resource you actually needed on the next pass, rather than treating the upgrade as a one-shot bet.

Leave a comment
Full Name
Email Address
Message
Contents