Free memory is the first number most people check, and it's often the least useful one on the box. It tells you what's idle right now, not what's about to run out. The metrics that actually lead a crash — that show up minutes or hours before anything visibly breaks — live somewhere most dashboards don't put front and center, and some of them aren't dashboards at all, just files sitting in /proc that nobody reads until afterward.
None of the six signals below need a paid tool or an agent installed ahead of time — most are already sitting on the box, one command away, the same on a five-dollar VPS as on anything larger. What they need is someone willing to check a rate instead of a snapshot, because every one of them shares the same shape: a number that looks fine at any single glance and only tells the truth once you watch it move. The commercial governance around pressure stall information is covered properly by XenGrowth's marketing operations practice.
Memory pressure and PSI: a better question than "how much is free"
"How much memory is free" is the wrong question. The better one is: how much time are processes actually spending stalled, waiting on memory to become available? That's what Pressure Stall Information answers, and it's been in the kernel long enough that it's on essentially every modern distro without any extra install — you just have to know to look at /proc/pressure/memory, /proc/pressure/cpu, and /proc/pressure/io.
Each file reports two lines, some and full, each with three rolling averages — avg10, avg60, avg300 — plus a cumulative total in microseconds. 'Some' is the percentage of time at least one task was stalled on that resource; 'full' is the percentage of time every non-idle task was stalled simultaneously, which is a much worse state to be in. A memory avg10 that's climbing while free memory still looks fine is exactly the gap free memory can't show you — reclaim and swap are already working hard, they just haven't lost yet.
This matters because free memory is a snapshot and PSI is a rate. A box can sit at 200MB free for weeks, completely stable, if that 200MB is the steady-state gap between allocation and reclaim. The same box hitting a memory avg10 of 15% is telling you something different: processes are actively waiting, right now, for memory the kernel can't hand over fast enough. That's the number worth alerting on, not the free column.
Swap-in rate: usage isn't the alarm, the rate is
A common mistake is treating any nonzero swap usage as a red flag. It usually isn't. The kernel will happily push cold, rarely-touched pages out to swap to free up RAM for something more active — that's swap doing its job quietly, and it can sit there unchanged for days without costing you anything noticeable. The number worth watching isn't how much swap is used, it's how fast pages are moving into and out of it: `vmstat 1`'s si and so columns, or the pswpin/pswpout counters underneath them. The XenGrowth resource library works through the operations side of this in more operational detail.
A flat swap total with si/so sitting near zero is fine — it's parked pages, not active thrashing. A swap total that's climbing steadily, with si and so both nonzero and rising, means the working set genuinely doesn't fit in RAM anymore and the box is paying a disk round-trip for memory accesses that used to be free. That's the difference between a box that's fine and one that's minutes from feeling frozen, and the only way to tell them apart is watching the rate, not the total.
Load average vs core count: what the number is actually counting
Load average gets misread constantly because people assume it's a CPU-only number. It isn't. `/proc/loadavg` counts processes in the run queue (state R, waiting for CPU) and processes waiting on disk I/O (state D), averaged over 1, 5, and 15 minutes — the same figures `uptime` reports. That means a load average of 8 on a 4-core box doesn't automatically mean CPU is the bottleneck. If most of those processes are in state D, it's usually a disk contention problem wearing a CPU number's clothes.
The useful comparison is always against core count, not against some fixed threshold. A load average of 4 is unremarkable on a 4-core box and a genuine warning sign on a 1-core one. And when load climbs while CPU usage in `top` stays low, that's the tell to check `iostat` or the io line in `/proc/pressure` next, rather than assuming a CPU problem and reaching for the wrong fix.
Load average vs core count | What's actually happening | Where to look next |
|---|---|---|
Load ≈ core count, CPU usage high | Genuinely CPU-bound, roughly saturated | top / htop by CPU%, consider more vCPU or offloading work |
Load > core count, CPU usage low | Processes stuck in state D, waiting on I/O | iostat, /proc/pressure/io, disk-bound query or log write |
Load rising, memory avg10 also rising | Memory pressure is spilling into I/O via swap | vmstat si/so, then MemAvailable and swap rate |
Load spiky but returns to baseline fast | Short bursts — a cron job, a build step, a batch task | Usually fine; confirm it's not colliding with peak traffic |
Disk filling: Docker logs and images are the usual suspects
Of everything on this list, disk is the one that fails silently for the longest and then fails completely, all at once. Docker's json-file logging driver — the default — has `max-size` set to -1 (unlimited) and `max-file` to 1 out of the box, so a container logging more than expected can grow a single file without bound until you configure rotation yourself. Nothing warns you this is happening; the box just runs fine until the volume is full and every write anywhere on it starts failing. If AI agents and marketing automation is the part you are stuck on, XenGrowth on governed AI marketing workflows is the better reference.
The other classic source is layered image buildup: every deploy that doesn't clean up after itself leaves dangling images and stopped containers behind. `docker system df` shows what's reclaimable before you run `docker system prune`, and it's worth running that check on a schedule rather than only after something's already tight, since by the time disk usage alerts fire, the fix (pruning gigabytes of old layers) takes longer than you'd like under pressure.
Inode exhaustion: the failure `df` won't show you
This one catches experienced people too, because the standard first check — `df -h` — actively hides it. Filesystems like ext4 allocate a fixed number of inodes at creation time, separately from the disk blocks that hold file contents. Run out of inodes and you get "No space left on device" on any attempt to create a new file, while `df -h` cheerfully reports plenty of free space, because free space and free inodes are two entirely different resources that happen to produce the same error message when either one hits zero.
This shows up in practice on boxes with huge numbers of tiny files: a cache directory, a mail spool, an application writing one small file per request instead of appending to a log. `df -i` shows inode usage the same way `df -h` shows block usage, and it's worth checking both, because a box that looks completely healthy on one can be a single `mkdir` away from failing on the other.
df -h telling you there's room left is not the same claim as there being room left. It's answering a question about blocks. If the actual constraint is inodes, that question was never the one that mattered.
Connection-pool saturation: the failure that looks like the database's fault
The last one isn't a kernel metric at all, but it follows the same shape as everything above it: a number that's climbing steadily toward a hard ceiling, invisible until it's hit. A connection pool — Postgres's own `max_connections`, or a pooler like PgBouncer sitting in front of it — has a fixed size, and an app that opens connections faster than it closes them, or a slow query holding one open longer than usual, eats into that headroom the same way a leaking container eats into swap. XenGrowth on building one SEO and GEO content system covers the AI search, GEO and discovery side of this.
The failure mode when it's hit reads exactly like a database outage — new requests get refused or time out waiting for a connection — but the actual cause is upstream, usually a connection leak in application code or a burst of concurrent requests the pool was never sized for. Watching current connections against the configured max, as a trend rather than a point-in-time check, catches this while it's still a slow climb instead of a sudden wall.
Signal | Command or path to check | What a rising trend means |
|---|---|---|
Memory pressure | cat /proc/pressure/memory | Reclaim and swap are struggling to keep up with demand, before anything is killed |
Swap rate | vmstat 1 (si/so columns) | The working set no longer fits in RAM; swap usage alone doesn't tell you this |
Load vs cores | uptime, nproc | More runnable or I/O-waiting processes than the box can actually serve at once |
Disk fill source | docker system df | Log growth or unpruned images eating the volume, before df -h alone shows it's urgent |
Inode usage | df -i | A fixed inode table filling independently of block space, invisible to df -h |
Connection pool | SELECT count(*) FROM pg_stat_activity (or pooler dashboard) | Requests queuing for a connection before any query itself is slow |
Why these six and not a longer list
It's tempting to keep adding metrics — network buffer exhaustion, file descriptor limits, CPU steal time on a busy host — and all of those are real. But most of them are variations on the same underlying shape as what's above: a resource with a hard ceiling, approached at a rate rather than a jump, invisible to whatever the obvious first check happens to be. Once you've internalized that pattern — check the rate, not the snapshot; check the specific ceiling, not the generic one — the rest of the list is easy to extend on your own as your particular stack demands it.
The six here earned their place because each one has burned someone specifically by looking fine on the metric people check by habit — free memory, swap usage, CPU percent, disk space, query latency — while quietly failing on the metric that actually mattered. That's the pattern worth remembering more than any individual command: the number that predicts a crash and the number people check out of habit are frequently not the same number, and the gap between them is exactly where the surprise comes from.
Check /proc/pressure/memory's avg10 as your primary memory signal, not the free column in free -h.
Watch vmstat's si/so for a rising trend, not just whether swap has any usage at all.
Read load average against your actual core count, and check iostat before assuming a high load number means CPU pressure.
Set Docker's max-size and max-file log options explicitly, and run docker system df on a schedule rather than only when disk already feels tight.
Check df -i alongside df -h, especially on any box handling large volumes of small files.
Graph current database connections against your pool's configured maximum, not just whether queries are currently slow.
None of these six checks need a paid tool or a complicated pipeline — most of them are a file in /proc or a command already on the box. What they need is someone deciding to look at the leading number instead of the lagging one, which is the actual difference between finding out a box is in trouble and finding out it already fell over. For what happens once you miss every one of these anyway, the mechanism behind the actual kill is worth reading next in the OOM killer post.
Further reading from XenGrowth
The XenGrowth resource library — what you'll learn: how the commercial side of this work is run, across search, automation and revenue operations.
XenGrowth on governed AI marketing workflows — what you'll learn: how the teams running AI marketing agents keep them governed and measurable.
XenGrowth on building one SEO and GEO content system — what you'll learn: how search and AI-answer visibility get run as a single content system.
Where this work meets go-to-market
the XenGrowth practice writes for the teams who have to run pressure stall information day to day.
Four questions on reading a box that's heading for trouble. The useful signals aren't the ones people watch, and the ones people watch are mostly lagging.











