
If a site went down and came back after a few minutes with no changes on your end, the Linux OOM (out-of-memory) killer almost certainly stepped in and terminated a process to save the kernel from a full memory exhaustion. That single fact should redirect your whole investigation: you are not chasing a crash, you are chasing whichever process was using the most memory when the system ran out.
Start with a live snapshot of memory usage, then look backward at what the kernel actually killed:
The `dmesg` output above tells you exactly which process the kernel killed, its process ID, and how much resident memory it was holding. On CloudStick servers you don't need SSH access at all for the first pass — the Web Application Logs section on each website shows Nginx and PHP-FPM error output in the dashboard, which frequently shows the 502s and worker timeouts that line up with an OOM event on the log timeline.
A `Swap: 0B` line matters as much as the OOM kill itself — it means the server had zero cushion between "under memory pressure" and "kernel kills something." Keep that in mind; it comes back later in this article.
The single most common cause of a memory exhaustion on a PHP site is a `pm.max_children` value that was copied from a tutorial or a bigger server, without ever being checked against the RAM the box actually has. Every PHP-FPM worker can grow up to `memory_limit` in size, so `pm.max_children` × average worker size must fit comfortably under total RAM, with headroom left for Nginx/Apache, MySQL, and the OS itself.
Find the real average size of a worker process, then work the math backward from available RAM:
On a CloudStick server, the pool config lives at `/etc/php83cs/fpm-pools.d/<sitename>.conf` (CloudStick runs its own namespaced PHP-FPM builds like `php83cs-fpm`, separate from any system PHP) and the site connects to it over the Unix socket at `/run/<sitename>.sock`. Set `pm = dynamic`, `pm.max_children` to the value you calculated, and `pm.max_requests = 500` so any small per-request leak gets recycled instead of accumulating over the pool's lifetime.
Reload the PHP-FPM service after editing pool config, don't restart it mid-traffic on a production site with active requests — a reload drains existing workers gracefully while applying the new limits to new ones.
`innodb_buffer_pool_size` is a fixed allocation the moment MySQL starts — it doesn't grow and shrink with load, so an oversized value eats RAM permanently, whether or not the database is busy. This is exactly what happens after a downsize migration or a resize from a bigger droplet: the my.cnf still says 4G on a server that now only has 2G total.
On a server dedicated mostly to the database, the buffer pool should not exceed roughly 70% of total system RAM, leaving the remainder for connection buffers, the OS page cache, and everything else running on the box. On a shared web+DB server that also runs PHP-FPM, that number needs to be considerably lower — often 25-40% of RAM.
Restart the service after changing this value — it isn't dynamically resizable on a running instance in most configurations. Check `performance_schema` or `SHOW ENGINE INNODB STATUS` afterward to confirm the buffer pool is actually being used efficiently at the new size before moving on.
Swap does not solve an underlying memory problem — it converts a hard crash into a slow degradation, which is a strictly better failure mode. Without swap, a short-lived memory spike (a traffic burst, a big WP-CLI import, a slow query holding extra connections) goes straight to the OOM killer picking a victim process at random-ish priority. With swap, the kernel has somewhere to page out cold memory first, buying the spike time to pass.
Check whether swap exists at all, then create a swap file sized for the server if it doesn't (1-2x RAM is a reasonable default on small VPS instances):
Adding swap to a server that's already tight on RAM masks the real problem instead of fixing it — a database that constantly swaps will get dramatically slower query response times, since disk I/O is orders of magnitude slower than RAM. If you see steady swap usage under normal load rather than only during spikes, that's a signal to right-size the server or fix the PHP-FPM/MySQL config, not a signal to add more swap.
A long-running Node.js process or a Supervisor-managed background worker doesn't restart between requests the way a PHP-FPM worker does, so any object that never gets released — an ever-growing cache, an unclosed database connection, an event listener attached on every loop iteration — accumulates for as long as the process stays alive. The tell is memory usage that climbs steadily over hours or days and never comes back down, independent of traffic.
Confirm the pattern by sampling RSS for the specific process over time rather than trusting a single `free -h` snapshot:
A steadily climbing curve like that means the fix is in the application code, not the server configuration — but a practical mitigation while the leak gets tracked down is a scheduled restart of the worker (via Supervisor's process control, or a cron-triggered `supervisorctl restart`) before it reaches the point of triggering an OOM kill. CloudStick's Supervisor management panel lets you configure and restart background workers per site without needing raw SSH access to `supervisorctl` on the underlying process.
Every fix above treats a symptom that already happened — the real prevention is knowing your memory trend before it hits the ceiling, so you can right-size the server or tune a config days before an OOM kill, not minutes after a customer complains the site is down. A server that's been steadily climbing toward 90%+ RAM usage over three weeks is not a mystery crash, it's a predictable one you had time to catch.
CloudStick's Server Stats page gives you exactly that view — real-time CPU, RAM, and disk graphs powered by a lightweight Zabbix agent installed on the server, so you can see a memory trend creeping upward over days and act on it (bump `pm.max_children` down, resize the droplet, or move the database to its own box) well before the kernel makes that decision for you.
As a practical checklist: confirm swap exists as a cushion, recalculate `pm.max_children` against actual RAM whenever you resize a server, keep `innodb_buffer_pool_size` under roughly 70% of RAM on a dedicated DB box, watch for worker processes that never plateau in memory, and check the Server Stats trend line at least weekly rather than only when something breaks.

