How much RAM a VPS needs depends not on the "size of the site" but on which programs run on the server all the time: the web server, the PHP interpreter, Node.js or Python, the database, Docker. Below is how much memory each of them used in our measurements, ready-made configurations for typical workloads, and the commands to measure usage on a server that is already running.
The measurements were taken in Docker containers on an arm64 CPU; on x86 servers the numbers may differ slightly. Versions: nginx 1.31.6, PostgreSQL 18.6, MySQL 8.4.11, MariaDB 12.3.3, PHP 8.3 with WordPress 7.1.3, Node.js 24.21, Python 3.14.8, Docker 29. Tested on Ubuntu 24.04 and 26.04 (the commands for inspecting a server).
Short answer: VPS sizing table
| Workload | vCPU | RAM | Disk | Plan |
|---|---|---|---|---|
| Static site or landing page on nginx | 1 | 1 GB | 10 GB | VPS-1 |
| Telegram bot or small API without a database | 1 | 1 GB | 10 GB | VPS-1 |
| Bot or API with PostgreSQL | 1–2 | 2 GB | 20–30 GB | VPS-2 |
| WordPress: blog, company website | 2 | 2 GB | 30 GB | VPS-2 |
| WooCommerce, WordPress with heavy plugins | 2 | 4 GB | 40 GB | VPS-3 |
| Docker Compose: app, PostgreSQL, Redis, nginx | 2 | 4 GB | 40 GB | VPS-3 |
| Building projects (CI runner) | 2–4 | 4–8 GB | 40–80 GB | VPS-3, VPS-4 |
| Several projects, a database of tens of gigabytes | 4 | 8 GB | 80 GB | VPS-4 |
The table is for small projects with up to a few thousand visitors a day. Where the numbers come from is explained below.
At Frogus Cloud you don't have to pick a ready-made plan: you can order the exact number of vCPUs, the amount of RAM and disk you need — for example, 1 vCPU, 2 GB of RAM and 20 GB of disk. Resources can be added later but not reduced, so start with a smaller configuration and scale up based on measurements.
What uses memory on a VPS
Operating system: 200–400 MB
The base services of Ubuntu 24.04 after initial server setup — systemd, the journal, SSH, cron, rsyslog, fail2ban and automatic updates — took 59 MB in our measurement. The largest were fail2ban (20 MB) and the automatic updates service (12 MB).
On a virtual machine, add the kernel with its data structures and services that a container doesn't have. In the Ubuntu Server 24.04 and 26.04 cloud images, according to the image package list, these are snapd and multipathd. So budget 200–400 MB for the system: on a server with 1 GB of RAM, 600–800 MB is left for your programs.
Nginx web server: a few megabytes
nginx barely uses memory: the master and worker processes took 1.3–1.9 MB each and the whole container 6–9 MB, including after 20,000 requests at a concurrency of 50. By default the number of nginx worker processes equals the number of cores. A static site fits even on the smallest configuration.
Node.js and Python: 25 to 160 MB per process
| App | Memory after start | After 20,000 requests |
|---|---|---|
| Node.js 24 + Express 5.3 | 25 MB | 79 MB |
| Python 3.14 + FastAPI 0.143 + uvicorn | 37 MB | 39 MB |
Node.js grows the JavaScript heap under load and is in no hurry to give it back: the process tripled in size and stayed that way after the test. If the server is short on memory, cap the heap with node --max-old-space-size=256 app.js (the value is in megabytes).
Every additional process multiplies usage: uvicorn --workers 4, gunicorn -w 4 or pm2 in cluster mode start several copies of the app, and each copy takes the same amount of memory.
Telegram bots: depends on the library
We ran a bot that replies to messages on three popular libraries:
| Library | Process memory |
|---|---|
| grammY 1.46 (Node.js) | 27 MB |
| python-telegram-bot 22.8 | 41 MB |
| aiogram 3.31 (Python) | 163 MB |
With aiogram, the 163 MB is taken by importing the library alone, before the first message arrives. One bot fits on a 1 GB server with any of these libraries, but five aiogram bots already take 800 MB.
PHP and WordPress: count PHP-FPM workers
WordPress runs on PHP-FPM — a pool of worker processes, each handling one request at a time. We set up WordPress 7.1.3 with nginx, PHP-FPM and MariaDB, filled it with 30 posts and loaded it with 10 concurrent requests:
| Site | Memory per worker | All of PHP-FPM, 5 workers |
|---|---|---|
| Plain WordPress | 12–14 MB own + 23 MB shared | 67 MB |
| WordPress + WooCommerce 11.2 | 21 MB own + 48 MB shared | 148 MB |
The shared part is the compiled PHP code cache (OPcache); there is one for all workers. So the estimate is: number of workers (pm.max_children) × memory per worker + the shared cache. Heavy themes and page builders increase per-worker memory several times over; the limit for a single request is set by memory_limit in the PHP settings, 128 MB by default.
Five workers — the default in the official image — are enough for a small site. If you set 20 workers on a server with 1 GB of RAM, under load they will take all the memory and the kernel will start killing processes.
Databases: the biggest memory consumer
| Database | Memory |
|---|---|
| PostgreSQL 18, right after start | 25 MB |
| PostgreSQL 18, 307 MB database, 20 connections under load | 191 MB |
| MariaDB 12.3, idle | 60–100 MB |
| MySQL 8.4, right after start, default settings | 456 MB |
PostgreSQL takes memory for its shared data cache (shared_buffers, 128 MB by default) gradually, as tables are read, plus about 2–3 MB per connection. For complex queries with sorts, each connection can take another work_mem (4 MB by default) per operation — noticeable with a hundred connections.
MySQL 8.4 took 456 MB even without any data. When our test machine ran out of memory, the kernel picked it as the process to kill. If the server has 1–2 GB of RAM and MySQL is not a hard requirement, choose MariaDB or PostgreSQL. If you do need MySQL, it can be fit into 170–300 MB — see installing MySQL on Ubuntu.
The main rule for databases: the data cache must be smaller than the server's memory minus everything else. If you increased shared_buffers or innodb_buffer_pool_size, recalculate the total. The ClickHouse analytics database is allowed to use up to 90% of memory by default — how to limit it on a 4 GB VPS is covered in the article on installing ClickHouse.
Docker
The Docker daemon (dockerd) took 35–50 MB in our measurement, plus containerd. The containers themselves use as much memory as the same programs without Docker. Add up the usage of every service in compose.yaml using the tables above and add the system. A Node.js app (79 MB), PostgreSQL (191 MB), Redis 7.4 with 63,000 keys (34 MB), nginx and Docker itself came to about 400 MB under load in our measurements, or 0.6–0.8 GB including the system. So for a Docker Compose stack take at least 2 GB, and 4 GB if you build images on the server itself. How to limit the memory of Redis itself is covered in Redis on Ubuntu.
Builds and CI
A build uses more memory than the running app. Installing the dependencies of a new Next.js 16.4 project (npm install) took up to 0.5 GB, and the build (next build) up to 0.9 GB of memory and 41 seconds on 6 cores. While the machine had enough memory, the build succeeded; once other processes took the memory, the kernel killed the build with code 9 (SIGKILL), even though the container had a 2 GB limit: a limit is a ceiling, not a guarantee.
For a server that builds projects (GitHub Actions runner, GitLab Runner), take at least 4 GB of RAM and 2 cores. If you build on a production server with 1–2 GB of RAM, enable a swap file or build the image in CI and ship it to the server ready to run.
When CPU matters more than RAM
Running out of memory and running out of CPU look different.
- Out of memory — the kernel kills processes (OOM killer), the database or the site goes down, and the log shows
Out of memory. How to recognize it is covered in how to tell that your server is running out of memory. - Out of CPU — everything works, but slowly: response times grow, and
load averageintopis higher than the number of cores. How to find out what exactly is loading the CPU, disk or network is covered in what is loading your Linux server.
Cores matter more than memory when the server does a lot of computation: PHP without a page cache (in our test each worker spent about 0.4 seconds on the WooCommerce home page), building projects, compression, image and video processing. A bot, a static site and a small database are fine with one core. A Minecraft server needs both: about 100 MB of Java heap for each player who has gone off into their own part of the world, and a fast core — see the measurements in the article on running a Minecraft server on a VPS.
Keep in mind that Node.js runs application code in a single thread, and Python runs it under the global interpreter lock (GIL). A single such app won't use a second core; a second core helps the database, nginx or a second copy of the app — and each copy multiplies memory again.
How much disk space a VPS needs
What takes up space:
- System. The packages of the Ubuntu Server cloud image, per its package list, take about 1.4 GB in 24.04 and 1.9 GB in 26.04 (we summed the package sizes from the repository). With the apt cache, a second kernel after an update, and logs, budget 3–5 GB for the system.
- Logs. systemd-journald uses up to 10% of the file system by default, but no more than 4 GB (
SystemMaxUsein journald.conf). Docker container logs are not limited by default — see below. - Docker images. On our disk:
nginx— 183 MB,node:24-alpine— 166 MB,python:3-slim— 145 MB,postgres:18— 480 MB,wordpress:fpm— 777 MB,mysql:8.4— 812 MB. Every app update leaves the old image behind until you remove it. - Database data. A table with 2 million rows and an index (the
pgbenchtest database) took 307 MB. On top of that, PostgreSQL keeps a write-ahead log (WAL) — up to 1 GB by default (max_wal_size) — andpg_dumpbackups, if you keep them on the same server, take as much again as the database itself.
Keep at least 20% of the disk free. When space runs out, the database stops accepting writes, and a system upgrade can fail halfway through.
To limit logs for all new containers, configure /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
sudo dockerd --validate --config-file /etc/docker/daemon.json # "configuration OK"
sudo systemctl restart docker
The setting applies only to containers created after Docker restarts: existing ones have to be recreated (docker compose up -d --force-recreate).
To find what is taking up space:
df -h / # used and free space
sudo du -xh --max-depth=1 / | sort -rh | head # largest directories
journalctl --disk-usage # systemd journal size
docker system df # Docker images, containers, volumes
The most convenient tool is ncdu: it shows the directory tree sorted by size, and you can navigate it with the arrow keys.
sudo apt install -y ncdu
sudo ncdu -x /
To free up space:
sudo journalctl --vacuum-size=200M # keep 200 MB of journal
sudo apt-get clean # cache of downloaded packages
docker image prune -a # images not used by any container
How to check memory usage on your server
If the server is already running, don't guess — measure. The main command:
free -h
Look at the available column: how much memory programs can get without pushing each other out. The free column is almost always small: Linux uses free memory for the file cache and gives it back on demand. The output of free is explained in detail in the article on running out of memory.
Which processes use the most:
ps -eo pid,rss,comm --sort=-rss | head -15 # RSS in kilobytes
top -o %MEM # real time, press q to quit
If you prefer an interactive view, use htop (sudo apt install -y htop); press M to sort by memory.
RSS is the part of a process's memory that is currently in RAM, including shared memory. That is why the sum of RSS values can be larger than the actual usage. In our test ps showed 66 MB for each PostgreSQL connection, although only 2.3 MB belonged to the connection itself and 61 MB was the shared cache. It is more accurate to measure per service:
systemd-cgtop -m # memory per systemd service
docker stats --no-stream # memory per container
Measure at peak hours, not right after startup: the database fills its cache, Node.js grows its heap, and PHP-FPM starts new workers under load. A good practice is to check free -h and docker stats several times over a week and add 30–50% headroom above the peak.
If you run out of memory, CPU or disk
| Symptom | What to do |
|---|---|
Processes restart unexpectedly, journalctl -k shows Out of memory |
Out of memory: find the culprit using the OOM killer article, limit it or add memory |
available in free -h is close to zero, the server is sluggish |
Enable a swap file as a safety net and add memory |
Memory is free, but responses are slow and load average is above the number of cores |
Out of CPU: enable a page cache or add cores |
df -h shows more than 80% |
Clean up logs and Docker images with the commands above, or increase the disk |
| MySQL takes half the memory of a small server | Reduce innodb_buffer_pool_size or switch to MariaDB or PostgreSQL |
What's next
- How to tell that your server is running out of memory: OOM killer, free, vmstat, dmesg
- Initial server setup on Ubuntu 24.04 and 26.04, including a swap file
- How to log in to a server with an SSH key
- How to size PHP-FPM workers to the available memory — WordPress on a VPS; for Python apps — FastAPI and Django on a VPS.