How much RAM does a server need? The honest answer: less than most sizing tables claim — and you cannot calculate it without measuring. Because the number almost everyone treats as “memory usage” is the wrong one. We measured this in 2026 across three of our own production machines: a 24 GB development server, an 8 GB VPS, and a 64 GB Shopware production box.
The finding that overshadows everything else: on the Shopware server, 43 PHP workers add up to 11,245 MB of RSS. Their actual memory consumption is 893 MB. Size your machine with the usual “add up RSS” method and you buy twelve times what the workload needs.
How much RAM does a server need: the short answer
If you take away one line, take this one: read available, not free, and sum PSS, not RSS. Everything else in this article is the justification.
As rough starting points, each backed by numbers we prove below:
| Use case | RAM | What we base this on |
|---|---|---|
| Pure reverse proxy / static sites | 1 GB | Caddy with 30+ domains: 104 MB including TLS |
| Small VPS, 2–3 Node services + SQLite | 2 GB | Our services measure 33–112 MB each |
| Typical web server with database | 4 GB | Helsinki runs on 8 GB with 4.5 GB free |
| Node/Next.js apps, several services | 8 GB | One Next.js process here: 205–287 MB |
| Shop under real load (Shopware, Magento) | 16 GB and up | The database alone wants 24 GB |
| Local AI models | 32 GB+ | See our article on running AI locally |
This table is a starting point, not a result. The real point: after two weeks of operation you can answer the question yourself — better than any table, because your load is your load.
Mistake one: “free” is the wrong column
The classic. Someone looks at free -m, sees little free memory, and orders more RAM. Here is the actual state of our development server at measurement time:
total used free shared buff/cache available
Mem: 23456 6125 1380 78 16428 17331
1,380 MB free out of 23,456 MB. That looks like 94 % utilization. Panic would be the obvious reaction — and completely wrong. The correct column is on the far right: 17,331 MB available. That is 74 % of the machine.
The difference is those 16,428 MB under buff/cache. That is the page cache: files the kernel keeps in memory because otherwise it would leave that memory idle. This memory is not consumed, it is lent out. The moment a program needs it, it gets it.
The proof: drop the cache and watch
We did not assume this, we measured it. Created a 1.5 GB file, dropped caches, read it twice:
| Moment | free | buff/cache | available |
|---|---|---|---|
| Before | 1,205 MB | 16,612 MB | 17,340 MB |
After drop_caches | 17,243 MB | 844 MB | 17,610 MB |
| After 1st read (from disk) | 15,608 MB | 2,344 MB | 17,476 MB |
| After 2nd read (from cache) | 15,560 MB | 2,345 MB | 17,428 MB |
Two things live in that table:
First: free jumped from 1,205 to 17,243 MB — a fourteenfold increase. Meanwhile available moved from 17,340 to 17,610 MB, about 1.5 %. The machine had the same usable memory the entire time. Only one of the two columns knew it.
Second — and this is the price: the first read took 1.18 seconds, the second 0.24 seconds. The cache was nearly five times faster. Anyone “freeing memory” by dropping caches buys a prettier number in free and pays for it in disk reads.

Rule of thumb: a Linux server with lots of free memory is not using its memory. free near zero is not an alarm, it is a working system.
Mistake two: VSZ is not a memory measurement
ps shows two columns that look like memory: VSZ (virtual size) and RSS (resident, actually in RAM). How far apart they can drift is shown by our MariaDB instance on the development server:
| Metric | Value |
|---|---|
| VSZ | 8,592,218,816 kB = 8.00 TiB |
| RSS | 24,368 kB = 23.7 MiB |
| Ratio | 352,602 × |
Eight terabytes of virtual address space on a machine with 24 gigabytes of RAM. That is not a bug and not a memory leak — it is how modern programs handle addresses. The cross-check via smaps_rollup confirms the real portion:
Rss: 24368 kB
Pss: 24364 kB
Private_Dirty: 15508 kB
Private_Clean: 8856 kB
Actually in RAM: 23.7 MiB, of which 15 MiB genuinely belongs to this database alone.
Why this happens — reproduced in four steps
So this does not stay theoretical, we wrote a small program that requests 2 GiB and then uses it piece by piece:
| Step | VSZ | RSS |
|---|---|---|
| Start | 19.1 MiB | 11.4 MiB |
After malloc(2 GiB) | 2,067.1 MiB | 11.6 MiB |
| After writing 256 MiB | 2,067.1 MiB | 267.6 MiB |
| After writing 1 GiB | 2,067.1 MiB | 1,035.6 MiB |
After free() | 19.1 MiB | 11.6 MiB |
The second row is the entire point: requesting 2 GiB raised RSS by 0.2 MiB. The kernel did nothing but make a promise. Only writing to a page costs real memory — and then exactly as much as was written.

This also explains why Committed_AS on our server sits at 20.2 GiB while CommitLimit is 15.5 GiB. Linux promised more than it has, because it knows nobody claims everything at once. This is called overcommit, and ours runs at the default vm.overcommit_memory = 0.
Mistake three — the expensive one: adding up RSS
Here is where most sizing exercises fall apart. The obvious method: add the RSS of all processes, add headroom, done. On our development server that yields:
| Metric | Value |
|---|---|
| Sum of all RSS | 8,385 MiB |
| Sum of all PSS | 5,406 MiB |
free -m reports “used” | 6,081 MiB |
| Double-counted | 2,978 MiB |
Nearly three gigabytes of air in a calculation that looks perfectly correct. The reason: RSS counts shared pages in full for every process. A library used by 40 processes appears forty times. PSS (Proportional Set Size) divides such pages among their users and is therefore the only sum you are allowed to build.
On the production server this becomes a factor of 12.6
On the development server it is a 55 % overshoot. On the Shopware server, with its many identical PHP processes, it becomes something else entirely:
| Metric | Value |
|---|---|
| php-fpm workers | 43 |
| Sum of RSS | 11,245 MiB |
| Average per worker (RSS) | 261 MiB |
| Sum of PSS (real) | 893 MiB |
| Overestimation | 12.6 × |
43 workers share the same PHP interpreter, the same extensions, the same OPcache. Each one reports the entire shared foundation as “its” memory. Apply the rule of thumb “261 MB per worker × planned workers” and you size a machine for 11 GB where 0.9 GB is needed.
How to measure PSS yourself:
# Real consumption of a process family (php-fpm here)
T=0; for p in $(pgrep -f php-fpm); do
v=$(awk '/^Pss:/{print $2; exit}' /proc/$p/smaps_rollup 2>/dev/null)
T=$((T+${v:-0}))
done; echo "Total PSS: $((T/1024)) MiB"
And to cross-check the grand total:
ps -eo rss --no-headers | awk '{s+=$1} END {print "RSS total: "int(s/1024)" MiB"}'
If the two numbers diverge sharply, you have many identical processes — and the RSS figure is worthless for sizing.

What individual services actually need — our numbers
Instead of copying rules of thumb, here are the systemd-measured values of our running services. This is the cgroup figure, so it includes all child processes and associated cache:
| Service | Memory | Note |
|---|---|---|
fail2ban | 23 MB | Python, still does its job |
postgresql@16 | 23 MB | without notable load |
podcast-studio (Node) | 33 MB | small service |
nyx-analytics (Node) | 42 MB | analytics + SQLite |
nyxvault (Node) | 68 MB | encryption + uploads |
shellgames (Node) | 104 MB | WebSockets, several games |
caddy | 104 MB | 30+ domains, TLS, reverse proxy |
veganmaps-api | 112 MB | Express + SQLite, real users |
docker | 179 MB | daemon alone, no containers |
signal-cli | 214 MB | JVM |
Two readings from this:
A reverse proxy is cheap. Caddy serves over thirty domains with automatic TLS for 104 MB. If all you need is static sites and forwarding, 1 GB total RAM will do. Details in our article on Nginx as a reverse proxy and the introduction What is a reverse proxy?.
The JVM is the most expensive line in the list. signal-cli alone needs more than six of our Node services combined. Language runtime beats feature set.
And the containers alongside, measured with docker stats:
| Container | Memory |
|---|---|
forkcart-db | 5.9 MB |
cobalt-api | 7.7 MB |
woo-proxy-db | 22.7 MB |
pasta-stage-67 | 40.8 MB |
shopware-test | 165.6 MB |
woo-proxy-wp | 221.9 MB |
Six containers together: 465 MB. The Docker daemon itself costs more at 179 MB than the four smallest containers combined. If you run Docker, budget the daemon as its own line item — it is there even when nothing runs. How we operate containers is covered in our Docker Compose guide.
The one service where rules of thumb hold: the database
On the Shopware production server the ranking looks completely different:
| Process | RSS |
|---|---|
mariadbd | 34,237 MB |
redis-server | 342 MB |
php-fpm (single) | ~285 MB |
34 GB for the database on a 64 GB machine. And unlike php-fpm, this number is real — the InnoDB buffer pool is configured to 24 GiB, and that is precisely a database’s purpose: hold data in RAM instead of reading it from disk.
This is the most important distinction in the whole article: for application processes, high memory is often just a measurement illusion. For databases, it is the entire point. The buffer pool is not waste to be optimized away — it is why the shop is fast. Redis reports 263 MB used against a maxmemory of 8 GB, so it still has plenty of headroom.
Swap: not a last resort, but offloading
Swap has a bad reputation rooted in a false idea: “swap is what happens when RAM runs out.” Our measurement says otherwise. The development server:
Swap: 4095 3949 146
3,949 of 4,095 MB of swap in use — while 17,331 MB of RAM is available. Under the last-resort theory this should not exist. The reason sits in vm.swappiness = 60: the kernel pages out memory that has not been touched in a long time to make room for page cache — even when RAM is free. That is not a shortage, that is housekeeping.
Who is in swap on our machine?
| Process | Swapped out |
|---|---|
node | 748 MB |
mysqld | 405 MB |
mysqld | 348 MB |
next-server | 247 MB |
java | 231 MB |
dockerd | 213 MB |
gpg-agent | 117 MB |
These are mostly processes that initialized a lot at startup and have used only a fraction since. Parking those pages on disk is the right call.

The number that actually matters: memory pressure
Whether swap is a problem is told neither by its size nor its fill level. It is told by PSI (Pressure Stall Information), available since kernel 4.20:
cat /proc/pressure/memory
Our three machines compared:
| Machine | RAM | some avg60 | Verdict |
|---|---|---|---|
| Development server | 24 GB | 0.23 | completely relaxed |
| Helsinki (VPS) | 8 GB | 0.00 | no pressure at all |
| Shopware production | 64 GB | 0.00 | no pressure at all |
The some avg60 value states what share of the last 60 seconds at least one process had to wait for memory. The rule of thumb from our operations:
- below 1 — fine, no action needed
- 1 to 10 — it is starting to stutter, watch it
- above 10 — real shortage, add RAM now
fullpersistently above 0 — the system can no longer keep up
The development server is the loudest of the three — at 0.23. Meaning: even the machine whose swap is 96 % full has virtually no memory pressure. A full swap is not an alarm. A high PSI value is.
The OOM killer: what happens when it really does run out
When Linux genuinely cannot find memory anymore, the out-of-memory killer steps in. It terminates a process — not necessarily the guilty one, but the one with the highest score derived from memory usage and oom_score_adj. In practice it often hits the database, because it holds the most memory.
On our machines this has not happened once in 82 days of uptime:
journalctl -k | grep -ci "out of memory\|oom-kill"
# → 0
How to check whether it hit you:
# Search the kernel log for OOM events
journalctl -k --no-pager | grep -i "oom-kill\|Out of memory: Killed"
# How exposed is a given process?
cat /proc/$(pgrep -x mariadbd)/oom_score
You can protect an important service by giving it a lower score via systemd — then someone else gets hit instead:
# /etc/systemd/system/myservice.service.d/oom.conf
[Service]
OOMScoreAdjust=-500
A detail many overlook: systemd-oomd is inactive on our system. So there is no second instance intervening ahead of the kernel. Check yours with systemctl is-active systemd-oomd — if it is active, it can terminate services before the kernel is even under pressure, based on PSI values.
The line item almost every calculation forgets: the kernel itself
Before the first application starts, the operating system has already helped itself. On our development server:
| Kernel structure | Memory |
|---|---|
| Slab (kernel objects) | 533 MB |
| PageTables | 174 MB |
| VmallocUsed | 51 MB |
| KernelStack | 28 MB |
| Total | 767 MiB |
Three quarters of a gigabyte before anything useful runs. Of that, 2,568 MB of the slab portion is reclaimable as SReclaimable — but the rest is fixed. On top of that our journal occupies 1,008.9 MB on disk, which costs no RAM but is happily forgotten during sizing.
Practical consequence: on a 1 GB VPS, after kernel and base services you realistically have 500–600 MB left for your application. That is why 1 GB only makes sense for genuinely lean workloads.
The three machines side by side
All values collected on the same day:
| Development | Helsinki (VPS) | Shopware production | |
|---|---|---|---|
| Total RAM | 23,456 MB | 7,745 MB | 62,792 MB |
used | 6,125 MB | 3,205 MB | 36,428 MB |
free | 1,380 MB | 853 MB | 4,239 MB |
buff/cache | 16,428 MB | 4,004 MB | 23,109 MB |
available | 17,331 MB | 4,540 MB | 26,363 MB |
| Utilization (real) | 26 % | 41 % | 58 % |
| Swap in use | 3,949/4,095 MB | 1,203/2,047 MB | 8/16,383 MB |
PSI some avg60 | 0.23 | 0.00 | 0.00 |
| CPU cores | 12 | – | 16 |
The most interesting row is the swap row. The production server has 16 GB of swap configured and uses 8 MB of it — 0.05 %. The development server uses 96 % of its swap. Both report PSI values near zero. This shows very clearly: swap usage does not correlate with memory shortage. The production server simply has so much RAM that the kernel never had a reason to page anything out.
And one more thing: the production server is the most heavily loaded machine at 58 % — and still reports zero memory pressure. 58 % is a very healthy operating point. Running servers at 20 % utilization means paying for capacity you never call on.
How to answer the question for your own server
Here is the procedure we use ourselves. It needs two weeks of operation and five commands.
Step 1 — read the right column:
free -m
# Only 'available' counts. Ignore 'free'.
Step 2 — observe memory pressure over time:
cat /proc/pressure/memory
# 'some avg300' below 1 = all good
Step 3 — determine real consumption per service:
systemctl status SERVICENAME | grep Memory
# or for all at once:
systemd-cgtop -m --iterations=1
Step 4 — check the process-family trap:
# RSS total (overestimates) against PSS total (real)
ps -eo rss --no-headers | awk '{s+=$1} END {print "RSS: "int(s/1024)" MiB"}'
T=0; for f in /proc/[0-9]*/smaps_rollup; do
v=$(awk '/^Pss:/{print $2; exit}' "$f" 2>/dev/null); T=$((T+${v:-0}))
done; echo "PSS: $((T/1024)) MiB"
Step 5 — peaks instead of snapshots:
A single free -m shows you one second. What matters is the worst moment of the last two weeks. For that you need a recording — either sar from the sysstat package or a simple cron line:
*/5 * * * * echo "$(date +\%F\ \%H:\%M) $(awk '/MemAvailable/{print $2}' /proc/meminfo)" >> /var/log/mem-history.txt
After two weeks you know how low available actually dropped. That number, not the table above, answers your question.
The rule we use for upgrading
We add RAM when one of these conditions holds for several days:
availabledrops below 15 % of total memory- PSI
some avg300stays above 1 - The OOM killer fired — even once
- The database cannot size its buffer pool to fit the hot data
What is not a reason to upgrade: a low free value, a full buff/cache, used swap, or a high RSS total.
What we deliberately do not claim
Honesty includes saying where our measurements stop.
We did not measure desktop or VM scenarios. Our machines lack /dev/kvm; we cannot run virtual machines and therefore cannot measure how ballooning or KSM deduplication behave across several VMs. If you are considering virtualization, our Proxmox vs ESXi comparison is a starting point — but the RAM figures there are not ours.
We do not run zram. None of the three machines uses compressed swap in RAM. So we cannot say from experience how much it helps on small VPS instances. The technique is real and frequently recommended — you will get no number for it from us, because we have none.
We did not touch Windows Server. All values come from Linux systems.
A copied number looks identical in a table to a measured one. That is why there is a gap here rather than an estimate.
Conclusion: how much RAM does a server need?

The question of how much RAM a server needs cannot be read off a table, because the common measurement methods are systematically wrong in one direction: they overestimate. RSS counts shared pages multiple times, VSZ measures promises rather than consumption, and free treats useful cache as lost memory.
The three numbers that stick from our measurements:
- 12.6 × — how badly the RSS total overestimates 43 PHP workers
- 352,602 × — how far VSZ and RSS diverge on our MariaDB
- 0.23 — the highest memory pressure across three production machines, meaning practically none
In practice: buy small, measure for two weeks, upgrade when available and PSI tell you to. A 4 GB server running at 58 % utilization with zero memory pressure is correctly sized. A 32 GB server at 20 % utilization is a standing order to your hosting provider.
The one exception remains the database. There, memory is not consumption but performance: the buffer pool should be large enough that the actively used data fits inside. Anything beyond that is wasted, anything below costs you disk reads on every request.
If you are setting up a new server, our guide on setting up a Linux server will help; for choosing a distribution, see our overview of Linux server distributions. And if you are considering running AI models yourself — that is the one case where the memory question is genuinely decided before purchase: running AI locally.
Frequently asked questions
What is the minimum RAM a server needs?
A pure reverse proxy or web server for static sites runs stably on 1 GB. Our Caddy serves over 30 domains with TLS for 104 MB. But budget for the fact that kernel and base services already occupy 767 MB of fixed structures on our system — on a 1 GB VPS you realistically have 500 to 600 MB left for your own application.
Why does my server show almost no free memory?
Because Linux uses unused memory as page cache. Our development server reported 1,380 MB free but 17,331 MB available — the difference is 16,428 MB of file cache that is returned at any time. Always look at the available column, not free. A server with lots of free memory simply is not using its memory.
What is the difference between VSZ and RSS?
VSZ is the virtual address space, meaning everything a process has requested. RSS is the portion actually resident in RAM. On our MariaDB, 8.00 TiB of VSZ stands against 23.7 MiB of RSS — a ratio of 352,602. For sizing purposes VSZ is completely useless.
Can I just add up the RSS values of all processes?
No, and this is the most expensive sizing mistake there is. RSS counts shared memory pages in full for every process. With 43 php-fpm workers on our production server the RSS total came to 11,245 MB while actual consumption was 893 MB — an overestimation by a factor of 12.6. Sum PSS from /proc/PID/smaps_rollup instead.
Is used swap a sign of insufficient RAM?
No. Our development server has 96 % of its swap in use while simultaneously having 17 GB of available RAM. At vm.swappiness = 60 the kernel pages out rarely used memory to make room for cache. Whether there is a real shortage is told by /proc/pressure/memory — not by the swap fill level.
How do I know my server really needs more RAM?
Three signals: available drops persistently below 15 % of total memory, the PSI value some avg300 in /proc/pressure/memory exceeds 1, or the OOM killer fired. Across our three machines the highest PSI value was 0.23, and in 82 days of uptime there were zero OOM events.
How much RAM does a server need for an online shop?
Budget from 16 GB upward, and the largest line item is the database. On our Shopware production server, MariaDB occupies 34 GB, including a configured InnoDB buffer pool of 24 GiB. Unlike application processes, this memory is not a measurement error but the purpose: data in RAM instead of on disk. The 43 PHP workers alongside need only 893 MB combined.
What happens when a server runs out of memory?
The kernel invokes the OOM killer and terminates a process — not necessarily the culprit, but the one with the highest score from memory usage and oom_score_adj. It often hits the database. Protect important services via systemd with OOMScoreAdjust=-500. Also check whether systemd-oomd is active — it intervenes ahead of the kernel.
How much RAM does Docker use?
The Docker daemon alone occupies 179 MB on our server before a single container runs. Our six production containers total 465 MB, ranging from 5.9 MB for a small database to 221.9 MB for a WordPress instance. Plan the daemon as its own line item.
Should I clear the cache to free up memory?
No. We measured it: after drop_caches, free jumped from 1,205 to 17,243 MB, but available changed by only 1.5 % — so no usable memory was gained. You paid for it anyway: reading the same file afterwards took 1.18 instead of 0.24 seconds, nearly five times slower.
