How much RAM does WordPress actually need?
On our live site the whole stack — Caddy, php-fpm and MariaDB — uses 262 MiB, and the machine as a whole uses 753 MiB. The difference is Ubuntu and the Docker runtime, which cost 491 MiB — 1.9× the application. dockerd and containerd alone are 105 MB.
Key finding Summing per-process RSS overstates php-fpm by about 2×. Our three workers plus master read 191.4 MB in ps aux, but the container’s cgroup total is 97.34 MiB, because RSS counts every shared page once per worker. Any pm.max_children formula fed an RSS number inherits that error.
Our php-fpm container runs three workers and a master. ps aux reports them at 59.0, 58.7, 57.3 and 17.4 MB — 191.4 MB in total. The container’s actual memory usage is 97.34 MiB.
Both numbers are correct. The first one double-counts.
KEY FINDING: summing per-process RSS overstates php-fpm memory by roughly 2×, because RSS counts every shared page once per worker. If you sized a server by multiplying a per-worker figure by your worker count, you provisioned about twice the RAM that component needed.
Two more numbers from the same machine: the whole WordPress stack — web server, PHP and database — uses 262 MiB, and the operating system plus container runtime under it uses 491 MiB. The machinery costs 1.9× the application.
Measured 30 August 2026, 27 hours after container start.
What the existing guides answer, and where they stop
We read the first page of results for how much RAM does WordPress need. Most of it is recommendation writing from companies that sell hosting. SSD Nodes is representative: 512 MB to 1 GB per site, while stating plainly that “WordPress RAM requirements aren’t officially published.”
One is not like the others. HelpWithWeb publishes real measurements and says how it got them — ps aux RSS readings on live PHP 8.1–8.3 sites with 15 to 40 plugins, under real traffic:
| Site type | Per php-fpm worker |
|---|---|
| Plain blog, light plugins | 40–70 MB |
| Business site with a page builder | 80–130 MB |
| WooCommerce / membership | 100–200 MB |
That is a good measurement of a worker, taken under load across many sites, and this page does not improve on it. Our own ps readings land in the same range: 57–59 MB per worker for a plain blog.
The problem is what happens next. A per-worker figure is only useful if you multiply it by your worker count — and that multiplication is where sizing goes wrong, for a reason that has nothing to do with the measurement being wrong.
What is running
| Component | Image |
|---|---|
| Web server / TLS | caddy:2-alpine |
| WordPress | wordpress:6-php8.3-fpm-alpine |
| Database | mariadb:11 |
Host: Amazon Lightsail, $12/month — 2 GB RAM, 2 vCPU, 60 GB SSD, 3 TB transfer (official pricing). Ubuntu, Docker Compose, eleven published articles, near-zero traffic.
That last part matters, and we come back to it.
Measured memory, per container
docker stats --no-stream, cgroup memory:
| Container | Using | Cap set | % of cap |
|---|---|---|---|
| Caddy | 44.97 MiB | 96 MiB | 46.9% |
| WordPress (php-fpm) | 97.34 MiB | 320 MiB | 30.4% |
| MariaDB | 119.6 MiB | 384 MiB | 31.1% |
| Total | 261.9 MiB | 800 MiB | 32.7% |
Memory caps are set on each container. Every one is well under its cap, so the caps are not suppressing these numbers. The database is the largest single consumer, and it is the one that grows.
Where the 2× comes from
ps aux on the same machine, php-fpm processes only:
| Process | RSS |
|---|---|
| worker | 59.0 MB |
| worker | 58.7 MB |
| worker | 57.3 MB |
| master | 17.4 MB |
| Sum | 191.4 MB |
The container is using 97.34 MiB. The sum of the parts is 1.97× the whole.
Nothing is wrong with either tool. RSS is resident set size per process, and it includes shared pages in full for every process that maps them. php-fpm workers are forked from the master: the PHP binary, every loaded extension, the opcache, and any memory the master touched before forking are one physical copy that all four processes report as theirs. ps counts that copy four times. The cgroup accounting behind docker stats counts it once — which is how much RAM you actually have to buy.
On our workers, roughly half of each worker’s RSS is shared with its siblings.
What this does to your pm.max_children
Every php-fpm tuning guide gives the same formula:
pm.max_children = available RAM / average process size
The formula is fine. The input is usually an RSS reading, and RSS is inflated. That error propagates in both directions depending on which side you solve for:
- Sizing a server for a fixed worker count. Ten workers at a 60 MB RSS reading looks like 600 MB. If half of each worker’s RSS is shared, the real figure is closer to 300 MB. You buy twice the RAM.
- Setting max_children for a fixed server. The same inflation makes each worker look expensive, so you cap concurrency lower than the machine can actually support, and requests queue on a box with free memory.
The fix is to measure the process group rather than a process. Three ways, all of which count shared pages once:
# containers
docker stats --no-stream
# systemd services, no containers
systemd-cgtop
cat /sys/fs/cgroup/<slice>/memory.current
# per-process, shared pages split proportionally
smem -k -P php-fpm # read the PSS column, not RSS
PSS — proportional set size — divides each shared page by the number of processes mapping it. Summing PSS across a process group gives a total you can trust; summing RSS does not.
We did not measure the marginal cost of one additional worker, which is what you would want to plug into the formula. That needs readings at two different worker counts, and we have one. Treat “roughly half is shared” as an observation about our box, not a constant.
The whole machine
free -m, same moment:
| MiB | |
|---|---|
| Total | 1,906 |
| Used | 753 |
| Buffers / cache | 1,109 |
| Available | 1,152 |
Used is 753 MiB. The containers account for 262 MiB of it. The other 491 MiB is Ubuntu and the container runtime. The named processes, by RSS:
| Process | RSS |
|---|---|
dockerd |
67.2 MB |
systemd-journald |
48.8 MB |
containerd |
38.2 MB |
fwupd |
26.6 MB |
multipathd |
26.0 MB |
snapd |
20.0 MB |
networkd-dispatcher |
18.1 MB |
unattended-upgrade |
14.0 MB |
(These are RSS figures and carry the same inflation described above, so treat each as an upper bound.)
Docker’s own daemons — dockerd plus containerd — cost 105 MB. That is 40% of what the entire WordPress stack costs. You are paying it for isolation and reproducibility, which may well be worth it. It is simply not free, and no sizing guide counts it.
Three more entries — fwupd, multipathd, snapd — total 72.6 MB and handle firmware updates, multipath storage and snap packages. A single-purpose web server does not obviously need any of them.
| MiB | % of 2 GB | |
|---|---|---|
| OS + container runtime | 491 | 25.8% |
| WordPress stack | 262 | 13.7% |
| Available | 1,152 | 60.5% |
If you are sizing a box for WordPress, you are mostly sizing it for Linux and Docker. The advice that says “get 2 GB” is not wrong about the box. It is pointed at the smaller half of the bill.
This also dissolves a contradiction people run into: the measurements are small, the hosting advice is large, and both are correct. They describe different things, and the gap between them is the operating system.
What this implies for a 1 GB plan
Lightsail’s 1 GB tier is the cheapest step down. Our measured total at idle is 753 MiB, and the OS share of that would not shrink on a smaller instance. On 1 GB, after the kernel’s own reservation, that is roughly 79% of memory used before a single visitor arrives.
That is arithmetic on our numbers, not an experiment. We have not run this stack on 1 GB and we are not going to tell you it works. HelpWithWeb’s decision table says 1 GB is fine for a small blog with proper caching — and caching is precisely what stops workers from multiplying.
Two vCPUs, doing nothing
Load average over 1, 5 and 15 minutes:
load average: 0.00, 0.02, 0.00
Per container: 0.00%, 0.01%, 0.01%. Swap is 2 GB configured and 30 MB used — 1.5%. The machine has never been under memory pressure.
For a content site this size, vCPU is the specification you are least likely to need and the one every plan raises alongside RAM. Our price-per-GB-of-RAM comparison shows how tightly providers bundle the two.
The disk line item nobody warns you about
docker system df:
| Type | Size | Reclaimable |
|---|---|---|
| Images | 1.75 GB | 405 MB (23%) |
| Build cache | 896.6 MB | 450.4 MB |
| Local volumes | 309.1 MB | 0 B |
| Containers | 57.34 kB | 0 B |
Build cache is 897 MB — half the size of every image on the machine, and nearly three times the site’s actual data.
Disk in use is 10 GB of 58 GB, so it is not urgent here. On a 40 GB plan with a few rebuilds behind you it stops being a footnote. docker builder prune reclaims 450 MB.
The site’s real data — WordPress files, the database, TLS certificates — is the 309 MB of volumes. Everything else is machinery.
WP_MEMORY_LIMIT is not your memory usage
Our config sets:
define('WP_MEMORY_LIMIT', '256M');
This gets read as “the site needs 256 MB” far more often than it should. It is a per-PHP-process ceiling — the most any single request may allocate before PHP kills it. It is not an allocation, not a reservation, and not a measurement.
The entire php-fpm container, master and workers together, is using 97 MiB. The 256 MB limit has never been approached.
Raising it does not make your site use more memory and does not require a bigger server. It changes when a runaway request fails.
Method and limits
What we measured. One site, one configuration, 30 August 2026, 27 hours after container start. docker stats --no-stream for per-container cgroup memory; ps aux --sort=-rss for per-process RSS; free -m and /proc/meminfo for the host; docker system df for disk; uptime for load. Instance specification confirmed against AWS’s published pricing page. We opened two of the nine first-page results to check their method and did not read all nine in full, so statements about what is unpublished describe our search, not a proof of absence.
The server runs nothing else. We checked, because the instance is named for an earlier project. The only Python processes are networkd-dispatcher and unattended-upgrade, both Ubuntu defaults. Nothing else competes for the 491 MiB.
Traffic. This site gets almost no traffic, and that single fact limits everything above. php-fpm allocates per concurrent request; the container grows with pm.max_children and with how many visitors arrive at once. MariaDB’s InnoDB buffer pool grows with the working set. 262 MiB is a floor for this stack, not a ceiling. For behaviour under load, the per-worker figures cited above are the better source.
Uptime. 27 hours. MariaDB’s memory rises over days as the buffer pool fills. Even the idle number is probably not final.
One configuration. Alpine images, php-fpm rather than Apache with mod_php, no object cache, no page-cache plugin, eleven articles, one theme. Any of those changes the number, and mod_php in particular is heavier.
We did not test 1 GB, and we did not measure the marginal cost of an additional worker. Both of those sections are arithmetic on 2 GB measurements.
What we can say is narrow: on this running WordPress site, summed RSS overstates php-fpm by about 2×, and the operating system costs more memory than WordPress does. If either holds on your machine too, the sizing question has been aimed at the wrong number.
Related
- What a GB of RAM Actually Costs: Every Major VPS Provider
- Four Things That Broke in Three Days of Self-Hosting WordPress on Docker and Caddy
Used is 753 MiB. The containers account for 262 MiB of it. The other 491 MiB is Ubuntu and the container runtime. The named processes, by RSS:
| Process | RSS |
|---|---|
dockerd |
67.2 MB |
systemd-journald |
48.8 MB |
containerd |
38.2 MB |
fwupd |
26.6 MB |
multipathd |
26.0 MB |
snapd |
20.0 MB |
networkd-dispatcher |
18.1 MB |
unattended-upgrade |
14.0 MB |
(These are RSS figures and carry the same inflation described above, so treat each as an upper bound.)
Docker’s own daemons — dockerd plus containerd — cost 105 MB. That is 40% of what the entire WordPress stack costs. You are paying it for isolation and reproducibility, which may well be worth it. It is simply not free, and no sizing guide counts it.
Three more entries — fwupd, multipathd, snapd — total 72.6 MB and handle firmware updates, multipath storage and snap packages. A single-purpose web server does not obviously need any of them.
| MiB | % of 2 GB | |
|---|---|---|
| OS + container runtime | 491 | 25.8% |
| WordPress stack | 262 | 13.7% |
| Available | 1,152 | 60.5% |
If you are sizing a box for WordPress, you are mostly sizing it for Linux and Docker. The advice that says “get 2 GB” is not wrong about the box. It is pointed at the smaller half of the bill.
This also dissolves a contradiction people run into: the measurements are small, the hosting advice is large, and both are correct. They describe different things, and the gap between them is the operating system.
What this implies for a 1 GB plan
Lightsail’s 1 GB tier is the cheapest step down. Our measured total at idle is 753 MiB, and the OS share of that would not shrink on a smaller instance. On 1 GB, after the kernel’s own reservation, that is roughly 79% of memory used before a single visitor arrives.
That is arithmetic on our numbers, not an experiment. We have not run this stack on 1 GB and we are not going to tell you it works. HelpWithWeb’s decision table says 1 GB is fine for a small blog with proper caching — and caching is precisely what stops workers from multiplying.
Two vCPUs, doing nothing
Load average over 1, 5 and 15 minutes:
load average: 0.00, 0.02, 0.00
Per container: 0.00%, 0.01%, 0.01%. Swap is 2 GB configured and 30 MB used — 1.5%. The machine has never been under memory pressure.
For a content site this size, vCPU is the specification you are least likely to need and the one every plan raises alongside RAM. Our price-per-GB-of-RAM comparison shows how tightly providers bundle the two.
The disk line item nobody warns you about
docker system df:
| Type | Size | Reclaimable |
|---|---|---|
| Images | 1.75 GB | 405 MB (23%) |
| Build cache | 896.6 MB | 450.4 MB |
| Local volumes | 309.1 MB | 0 B |
| Containers | 57.34 kB | 0 B |
Build cache is 897 MB — half the size of every image on the machine, and nearly three times the site’s actual data.
Disk in use is 10 GB of 58 GB, so it is not urgent here. On a 40 GB plan with a few rebuilds behind you it stops being a footnote. docker builder prune reclaims 450 MB.
The site’s real data — WordPress files, the database, TLS certificates — is the 309 MB of volumes. Everything else is machinery.
WP_MEMORY_LIMIT is not your memory usage
Our config sets:
define('WP_MEMORY_LIMIT', '256M');
This gets read as “the site needs 256 MB” far more often than it should. It is a per-PHP-process ceiling — the most any single request may allocate before PHP kills it. It is not an allocation, not a reservation, and not a measurement.
The entire php-fpm container, master and workers together, is using 97 MiB. The 256 MB limit has never been approached.
Raising it does not make your site use more memory and does not require a bigger server. It changes when a runaway request fails.
Method and limits
What we measured. One site, one configuration, 30 August 2026, 27 hours after container start. docker stats --no-stream for per-container cgroup memory; ps aux --sort=-rss for per-process RSS; free -m and /proc/meminfo for the host; docker system df for disk; uptime for load. Instance specification confirmed against AWS’s published pricing page. We opened two of the nine first-page results to check their method and did not read all nine in full, so statements about what is unpublished describe our search, not a proof of absence.
The server runs nothing else. We checked, because the instance is named for an earlier project. The only Python processes are networkd-dispatcher and unattended-upgrade, both Ubuntu defaults. Nothing else competes for the 491 MiB.
Traffic. This site gets almost no traffic, and that single fact limits everything above. php-fpm allocates per concurrent request; the container grows with pm.max_children and with how many visitors arrive at once. MariaDB’s InnoDB buffer pool grows with the working set. 262 MiB is a floor for this stack, not a ceiling. For behaviour under load, the per-worker figures cited above are the better source.
Uptime. 27 hours. MariaDB’s memory rises over days as the buffer pool fills. Even the idle number is probably not final.
One configuration. Alpine images, php-fpm rather than Apache with mod_php, no object cache, no page-cache plugin, eleven articles, one theme. Any of those changes the number, and mod_php in particular is heavier.
We did not test 1 GB, and we did not measure the marginal cost of an additional worker. Both of those sections are arithmetic on 2 GB measurements.
What we can say is narrow: on this running WordPress site, summed RSS overstates php-fpm by about 2×, and the operating system costs more memory than WordPress does. If either holds on your machine too, the sizing question has been aimed at the wrong number.
Related
- What a GB of RAM Actually Costs: Every Major VPS Provider
- Four Things That Broke in Three Days of Self-Hosting WordPress on Docker and Caddy
- Managed Hosting vs a VPS You Run Yourself
- Is Self-Hosting Actually Cheaper? Six Apps, Real Numbers
- What a Terabyte of Bandwidth Actually Costs
- The Cheapest VPS That Actually Runs n8n 2.x
Sources
- Amazon Lightsail pricing — instance specification and monthly price
- HelpWithWeb — How Much RAM Does a WordPress Site Actually Need? — per-worker RSS measurements under real traffic
- SSD Nodes — How Much RAM Does My VPS Need? — representative recommendation guidance
- Docker:
docker stats— reports cgroup memory, not summed RSS - Docker:
docker system df - WordPress:
WP_MEMORY_LIMIT
This page links to each vendor’s own pricing page. No affiliate links are used at this time.