A 1 GB or 2 GB VPS is plenty for a real website — until you install the usual stack and watch free -m drop to double digits while the server starts swapping on every request. The problem is rarely the plan size. It’s that the default installs of Nginx, PHP-FPM, and MySQL each assume they own the whole machine, and together they quietly eat 3–4 GB. The fix is a deliberate memory budget: decide up front how much RAM each service may use, enforce it in the config, and monitor the totals. This guide walks through a realistic budget for a 1–2 GB VPS running a typical web stack, with the exact settings that keep every service inside its allowance.
If your workload needs more headroom than a small plan can offer, our VPS comparison table shows which providers offer 4–8 GB plans with dedicated vCPUs — but start with the budget below; it often removes the need to upgrade at all.
1. Set the Budget: Where the RAM Goes
On a 2 GB VPS running a LEMP stack, aim for roughly this split:
- MySQL/MariaDB: 25–35% (512–700 MB) — mostly the InnoDB buffer pool.
- PHP-FPM: 20–30% (400–600 MB) — worker processes at ~30–40 MB each.
- Nginx: 5–10% (100–200 MB) — static file serving and buffers.
- OS + caches: 20–25% (400–500 MB) — kernel, page cache, systemd, sshd.
- Headroom: 10–15% (200–300 MB) — burst room so the OOM killer never fires during traffic spikes.
Write these numbers down before touching a config file. Every setting below is just a way of enforcing this table.
2. MySQL/MariaDB: Cap the Buffer Pool First
The single biggest RAM consumer on any LEMP stack is the InnoDB buffer pool, because its default is sized for a dedicated database server. On a shared web/db VPS, set it to roughly 20–25% of total RAM and cap the rest of the cache structures to match:
# /etc/mysql/mariadb.conf.d/99-small-vps.cnf
[mysqld]
innodb_buffer_pool_size = 384M # ~20% of a 2 GB VPS
innodb_log_buffer_size = 8M
key_buffer_size = 16M
max_connections = 40
tmp_table_size = 32M
max_heap_table_size = 32M
performance_schema = OFF # saves ~100 MB on MariaDB 10.5+
If you run a second web server on the same box (e.g. a small admin panel), drop the buffer pool to 256M. After applying, verify with mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';" and check actual usage with mysqltuner or SHOW ENGINE INNODB STATUS.
3. PHP-FPM: Count Workers in MB, Not Processes
PHP-FPM workers are where small VPSes die: the default pm.max_children of 30+ lets the pool balloon past a gigabyte under load. Budget by memory instead. Each PHP worker uses roughly 30–40 MB for WordPress, so on a 2 GB VPS you can afford ~10–12 workers:
# /etc/php/*/fpm/pool.d/www.conf
pm = ondemand # spawn only on demand; best for small VPS
pm.max_children = 10 # 10 x 35 MB = ~350 MB worst case
pm.process_idle_timeout = 30s
pm.max_requests = 500
# Hard memory ceiling per worker (PHP 8.2+)
php_admin_value[memory_limit] = 128M
pm = ondemand is the right choice for most small VPS workloads because idle workers are killed instead of parked. If your traffic is steady rather than bursty, pm = dynamic with pm.min_spare_servers = 2 and pm.max_spare_servers = 5 behaves similarly with slightly less process churn.
4. Nginx: Trim the Buffers and Logs
Nginx itself is lean, but its buffers and access logs can accumulate. Keep it minimal:
# /etc/nginx/nginx.conf
worker_processes auto; # one per vCPU
worker_connections 1024;
sendfile on;
tcp_nopush on;
keepalive_timeout 15;
client_body_buffer_size 8k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# Disable access logging for static assets to cut disk I/O
location ~* \.(js|css|png|jpg|svg|woff2?)$ {
access_log off;
expires 30d;
add_header Cache-Control "public";
}
Also add a page cache if you serve WordPress or any dynamic CMS — a 10 MB fastcgi_cache_path can absorb 90% of requests and let you run fewer PHP workers, which is the cheapest RAM you’ll ever buy.
5. Add zram or a Small Swapfile as a Safety Net
Even with a tight budget, peaks happen. A compressed swap layer keeps the OOM killer at bay without hammering disk I/O:
# zram (compressed RAM swap) — Debian/Ubuntu
sudo apt install zram-tools
# /etc/default/zramswap
PERCENT=50
PRIORITY=100
# or a plain swapfile as backup for cold pages
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Keep vm.swappiness low (10–20) so the kernel prefers cache eviction over swapping, but leave zram enabled — it’s the difference between a slow request and a dead process during a spike.
6. Measure, Then Tune Again
Apply the budget, then verify the totals match your plan:
# Total memory picture
free -m
# Per-process usage, sorted
ps aux --sort=-%mem | head -15
# PHP-FPM pool status (set pm.status_path in nginx first)
curl http://localhost/status?full | grep -E "processes|max_children"
# Swap pressure over time
vmstat 5 6
If free -m shows 150–300 MB free with minimal swap after a traffic test, the budget is working. If the OOM killer fired, reduce pm.max_children or the buffer pool by 20% and re-test. The goal is a stack that survives a traffic spike without a plan upgrade — and when you genuinely outgrow it, you’ll know exactly which number to scale. For that decision, check out our VPS provider comparison table to see what the next size up costs across hosts.
Memory Budget Cheat Sheet
- InnoDB buffer pool ≈ 20–25% of RAM on a shared web/db VPS.
- PHP-FPM
pm.max_children= (RAM budget for PHP) ÷ 35 MB per worker. - Use
pm = ondemandon small VPSes; it reclaims idle workers automatically. - Nginx buffers stay small; add a FastCGI page cache to cut PHP load.
- zram + a swapfile + low swappiness = graceful degradation under spikes.
- Verify with
ps aux --sort=-%memandfree -m, not with guesses.
A small VPS is only “too small” when its software is configured for a bigger machine. With a written budget and configs that enforce it, a 1–2 GB plan comfortably runs a production WordPress or PHP app — and the money you save on the plan can go toward backups, monitoring, or a CDN instead.


Leave a Reply
You must be logged in to post a comment.