A 1 GB or 2 GB instance running nginx, PHP-FPM and MariaDB has a hard ceiling, and the default configuration of every one of those services assumes it is the only thing on the machine. Install them from distro packages, start them, and free -m shows a few megabytes of available memory with the disk already swapping. Nothing is broken. The defaults were written for servers with eight times the RAM.
Fixing it is arithmetic rather than guesswork. You decide how many bytes each component may claim, write those numbers into config files, and leave one deliberate safety margin. The commands below target Debian/Ubuntu paths; on RHEL-family systems the same values live in /etc/my.cnf.d and /etc/php-fpm.d.
Start From a Measured Baseline, Not a Guess
Before changing anything, capture what the kernel says the services actually use. The baseline matters because it separates page cache (reclaimable) from anonymous memory (not reclaimable without swap), and only the second number counts against your budget.
free -m
ps_mem -p # sudo apt install ps_mem
smem -t -k -P 'nginx|php-fpm|mysqld|mariadbd'
ps_mem reports private and shared memory separately, which ps RSS does not: twenty PHP-FPM workers each showing 60 MB of RSS may share 40 MB of that with each other. If you plan to move to a larger plan later, the same methodology applies to cloud VPS capacity guarantees and how they are metered.
Split the Budget Arithmetic Before Touching Configs
On a 2048 MB instance, reserve 300 MB for the kernel, page tables and network buffers, and 128 MB for systemd, journald, sshd and cron. That leaves roughly 1600 MB to divide. A workable split for a database-backed web app is 600 MB for innodb_buffer_pool_size, 512 MB for PHP-FPM, 150 MB for nginx and the remainder for Redis or spare page cache.
Write the split down in a comment block at the top of each config file. The value of that note is auditability: six months later, when a deploy adds Memcached and the instance starts OOM-killing MySQL, you can see which numbers were intentional and which were a leftover default.
Cap MariaDB’s Buffer Pool First
The buffer pool is the single largest controllable allocation on a small instance. The default is often 128 MB, which is too small for a real workload and also competes unpredictably with PHP. Set it explicitly, and cap per-connection buffers too, because those multiply by connection count.
# /etc/mysql/mariadb.conf.d/99-small.cnf
[mysqld]
innodb_buffer_pool_size = 600M
innodb_buffer_pool_instances = 1
max_connections = 40
tmp_table_size = 32M
max_heap_table_size = 32M
sort_buffer_size = 1M
join_buffer_size = 1M
performance_schema = OFF
Forty connections at 1 MB per sort and join buffer is a worst case of 80 MB, not 40 GB. Check the result with SHOW ENGINE INNODB STATUS and the actual Buffer pool hit rate line. If the hit rate stays above 99% you are not memory-starved on the pool; if it sits in the low 90s, raise the pool before you touch anything else.
Size PHP-FPM Workers in Real Megabytes
PHP-FPM’s pm.max_children is a memory figure wearing a process count. If a typical WordPress request peaks at 50 MB, then 512 MB of pool space means ten children, not fifty. Set the count from measurement and let the pool queue, not swap.
; /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
; log the real high-water mark
request_terminate_timeout = 30s
Watch pm.max_children reached warnings in the error log and the max children reached counter in the FPM status page. Persistent saturation means either raise the pool and lower the database pool, or fix the slow plugin that holds each worker for two seconds. The latter is usually cheaper than the former.
Trim Nginx Buffers and Stop Writing Access Logs to Disk
nginx itself is lean, but defaults allocate per-connection buffers that add up at concurrency. On a small instance, lowering fastcgi_buffers and disabling access_log for static assets removes both memory pressure and a surprising amount of disk I/O.
worker_processes 1; # or 2, never more than vCPUs
worker_connections 1024;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
client_body_buffer_size 64k;
location ~* \.(jpg|png|css|js|woff2)$ {
access_log off;
expires 30d;
}
One worker is generally right for a 1-2 vCPU instance: extra workers cost memory and cause cache-line contention without adding throughput. Verify with nginx -t before reloading, and reload rather than restart so established connections drain normally.
Keep a Small Swap File as a Tripwire, Not a Crutch
After the caps are in place, add 1 GB of swap on NVMe or SSD storage. Its purpose is to absorb a rare spike — a backup job, a cache stampede — not to run the steady state. Sustained swap-in rates above a few hundred kilobytes per second mean the budget is wrong somewhere.
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
vm.swappiness = 10 # /etc/sysctl.d/99-memory.conf
vm.vfs_cache_pressure = 50
If swap I/O latency becomes visible in application response times, the answer is a smaller footprint, not a bigger swap file. Memory accounting on Linux is subtle enough that free‘s second line is often misleading; the comparison of dedicated hardware versus virtualised memory is worth reading before you conclude the instance needs more RAM rather than better limits.
Re-Measure After Twenty-Four Hours
Config changes look good in the first ten minutes, when caches are cold and nothing has accumulated. Re-check after a full day of real traffic, including the overnight backup window, which is when small instances usually OOM.
free -m
vmstat 1 10
journalctl -k | grep -i 'oom\|killed process'
cat /proc/pressure/memory
The pressure stall information file is the most useful of these: a full value consistently above zero means processes are stalling on memory even if free looks comfortable. Stable numbers across a week mean the budget is set correctly and you can stop tuning.

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