Diagnosing vm.max_map_count and mmap Exhaustion on a Small VPS

There is a class of VPS failure that produces no CPU spike, no disk pressure and no obvious OOM message: a process simply cannot create another memory mapping and dies with ENOMEM or Cannot allocate memory while free shows gigabytes available. The culprit is usually vm.max_map_count, the kernel limit on how many distinct memory-mapped regions a single process may hold. Elasticsearch, Redis with many keys, JVMs and modern databases all hit it on small hosts.

What the limit actually counts

Every mmap() call creates a virtual memory area (VMA). The kernel tracks them in a per-process red-black tree, and vm.max_map_count caps the total. Allocating heap is not the same as mapping: a process can have plenty of free RAM and still exhaust its VMA budget. Read the current limit and see how close a running process is to it:

cat /proc/sys/vm/max_map_count

# current VMA count for a given process
wc -l < /proc/$(pidof elasticsearch)/maps

If the maps line count is within a few percent of max_map_count, you are on the edge. The next allocation fails, and because the failure arrives as a generic ENOMEM, it is routinely misattributed to RAM exhaustion.

Recognising the symptom in logs

The error strings differ by runtime, but they cluster around mapping failures rather than allocation sizes. Grep for them before you start tuning memory:

journalctl -u elasticsearch --since "1 hour ago" | grep -iE 'max_map_count|mmap|Cannot allocate memory'

# JVM instances often surface it as a native memory tracking failure
grep -r 'OutOfMemoryError: Map failed' /var/log/*.log

A dead giveaway is that free -m shows available memory and swap in good shape while the process still refused to start. That combination points at a limit, not a shortage.

Raising the limit safely

The commonly recommended value for Elasticsearch — 262144 — is a reasonable ceiling for a small VPS too. The cost is a slightly larger per-process kernel bookkeeping footprint, which is negligible for the handful of processes a small host runs. Apply it persistently so it survives a reboot:

# apply now (non-persistent, good for testing)
sysctl -w vm.max_map_count=262144

# persist across reboots
echo 'vm.max_map_count=262144' > /etc/sysctl.d/99-max-map-count.conf
sysctl --system

Do not crank it to millions “just in case”. A process that maps unboundedly is usually leaking mappings, and hiding that behind a huge limit delays the day you find out.

Distinguish mmap exhaustion from real memory pressure

Before raising any limit, confirm which failure you actually have. The two look similar in logs but need opposite fixes:

  • Real memory pressure — MemAvailable is low, swap is being used heavily, and the OOM killer fires. Fix by shrinking the working set or the process, not by raising max_map_count.
  • VMA limit — MemAvailable is healthy, no swap storm, but maps is pinned at the limit. Fix by raising vm.max_map_count.

Watch both signals together during startup, when mapping counts climb fastest:

watch -n1 'echo "maps: $(wc -l < /proc/$(pidof mysqld)/maps)"; grep MemAvailable /proc/meminfo'

If maps freezes near the limit while MemAvailable stays high, the limit is your bottleneck and the fix is above.

Why this surfaces on small VPS specifically

Containers, cgroup accounting and modern runtimes all add mappings: glibc arenas, thread stacks, JIT code caches and per-shard file mappings each consume VMAs. On a large host the default limit of 65530 is invisible because the workload is spread thin. On a small instance running a memory-hungry daemon directly, the same default is tight. Being able to right-size these kernel limits is part of why a well-managed virtual server beats a host where you have no visibility into limits at all.

Prevention checklist

  • Set vm.max_map_count=262144 in /etc/sysctl.d/ before deploying Elasticsearch, a JVM service, or a sharded database.
  • Monitor wc -l /proc/<pid>/maps during startup, when it peaks.
  • Alert on the maps count approaching the limit, not just on RAM.
  • Treat repeated mapping growth over days as a leak and profile it, rather than raising the ceiling indefinitely.

None of this is exotic — it is one sysctl and one counter. The work is recognising that ENOMEM with free memory is a mapping-count problem, and that the fix belongs in the kernel limits of your virtual server rather than in the application.

Leave a Reply