Cloud Explainers
Home › Linux › Linux for Beginners, episode 7

Why Your Linux Server Runs Out of Memory

Linux9 min videoPublished Oct 29, 2026

Why a Linux server runs out of memory, shown on a real 1 GB Ubuntu 24.04 server: too many workers, a memory leak and one big job, each killed by the real OOM killer. How to read free -h (available, not free), what the OOM killer does, and the fix for each reason, including a systemd memory limit.

Why Your Linux Server Runs Out of Memory: the full diagram
The whole picture, from the video's recap.

The shop is down

Ana's shop is down, and Linux stopped it on purpose. Why does a Linux server run out of memory, and how do you stop it happening? Last night, she doubled her shop's workers for a sale. This morning, the status says failed, and the reason is oom-kill.

ana@ubuntu · the shop is down
systemctl status shop -n 0× shop.service - Ana's shop
     Loaded: loaded (/etc/systemd/system/shop.service; disabled; preset: enabled)
     Active: failed (Result: oom-kill) since Thu 2026-10-29 18:26:45 UTC; 5s ago
   Duration: 1.356s
    Process: 3418 ExecStart=/usr/local/bin/shop (code=exited, status=0/SUCCESS)
   Main PID: 3418 (code=exited, status=0/SUCCESS)
        CPU: 840ms

The log tells the story. Workers died with signal nine, a kill that can't be refused. systemd says a process was killed by the OOM killer. OOM means out of memory. systemd restarted the shop fifteen times, the killer struck every time, and then systemd gave up.

ana@ubuntu · journalctl
journalctl -u shop -o cat | tail -n 8shop: worker 3423 died (signal 9), starting a new one
shop: worker 3422 died (signal 9), starting a new one
shop.service: A process of this unit has been killed by the OOM killer.
shop.service: Failed with result 'oom-kill'.
shop.service: Scheduled restart job, restart counter is at 15.
shop.service: Start request repeated too quickly.
shop.service: Failed with result 'oom-kill'.
Failed to start shop.service - Ana's shop.

In this video, we'll see what memory is, and how to read it. Then the three usual reasons a server runs out: too many workers, a memory leak, and one big job. We'll see what the OOM killer does, and then the fix for each reason, all on Ana's real server.

What memory is

First, what memory is. Your server has two places to keep things. The disk is big and keeps files, even when the power is off. Memory, or RAM, is much smaller, but far faster, and it's empty when the power goes off. A program runs from memory: Linux loads it there, and the program keeps its data there while it works.

What memory is: diagram from the video

Reading free

To see memory, run free, with dash h for human sizes. With the shop stopped, total is one gigabyte, and used is only ninety-three megabytes. Free is memory nothing is using at all. Buff and cache is memory Linux borrowed to keep recent files close. And the column to watch is the last one, available: how much a new program could get. The swap line says zero: no swap. More on that in a moment.

ana@ubuntu · free
free -h               total        used        free      shared  buff/cache   available
Mem:           1.0Gi        93Mi       918Mi        26Mi        37Mi       930Mi
Swap:             0B          0B          0B

Every running program, every process, takes some of that memory. The server Ana runs has one gigabyte in total, which is common for a small cloud server. Her shop's workers, her database and Linux's own helpers all share it. And there's no swap: no space on the disk to spill over into. When the gigabyte is full, something has to give.

Reading free: diagram from the video

Which processes use it? ps can list them sorted by RSS: the memory each one really holds, in kilobytes. The database is about twenty-six megabytes. The rest are small helpers that Ubuntu runs. So with the shop stopped, the server is nearly empty.

ana@ubuntu · ps, biggest first
ps -eo rss,comm --sort=-rss | head -n 6  RSS COMMAND
34668 python3
26988 postgres
23220 unattended-upgr
21148 packagekitd
15272 systemd-journal

Reason 1: Too many workers

So what filled it? systemctl cat shows the shop's settings. It runs eight workers, eight copies of the shop that answer customers at the same time. And each one loads the whole product catalog into memory: a hundred and fifty megabytes.

ana@ubuntu · the shop's service
systemctl cat shop# /etc/systemd/system/shop.service
[Unit]
Description=Ana's shop
After=postgresql.service

[Service]
User=shop
Environment=WORKERS=8 CATALOG_MB=150
ExecStart=/usr/local/bin/shop
Restart=on-failure

[Install]
WantedBy=multi-user.target

With its own code added, each worker holds about one hundred and sixty-four megabytes. Eight of them need about one thousand three hundred and twelve megabytes. The server has one thousand and twenty-four. It never fit, not even for a minute. Four workers need about six hundred and fifty-six, which leaves room for the database and Linux.

Reason 1: Too many workers: diagram from the video

Here's the fix for reason one. Ana changes eight workers to four with sed, a find and replace for files. She reloads systemd so it reads the change, and restarts the shop. ps shows four workers at about a hundred and sixty-seven thousand kilobytes: one hundred and sixty-four megabytes each. free shows three hundred and fifteen megabytes still available. And the shop answers: ok.

ana@ubuntu · four workers
sudo sed -i 's/WORKERS=8/WORKERS=4/' /etc/systemd/system/shop.service[sudo] password for ana:
ana@ubuntu · four workers
sudo systemctl daemon-reload
ana@ubuntu · four workers
sudo systemctl restart shop
ana@ubuntu · four workers
ps -eo rss,comm --sort=-rss | head -n 6  RSS COMMAND
167740 shop-worker
167740 shop-worker
167712 shop-worker
167708 shop-worker
34668 python3
ana@ubuntu · four workers
free -h               total        used        free      shared  buff/cache   available
Mem:           1.0Gi       708Mi       302Mi        26Mi        38Mi       315Mi
Swap:             0B          0B          0B
ana@ubuntu · four workers
curl localhost:8080ok

The OOM killer

So what happens when memory runs out? First, Linux takes back what it can: cached files it can read again later. With swap, it could move some memory to the disk. Ana has none. When nothing is left, the kernel, the core of Linux, calls the OOM killer. It gives every process a score, mostly by how much memory it uses, and kills the one with the highest score, so the rest of the system survives.

The OOM killer: diagram from the video

On Ana's server, the biggest processes were always shop workers, so the killer chose them. The database survived. That's good luck, not a plan: on another server, the biggest process could be the database itself. And when a worker died, the shop restarted it, ran out again, and lost again.

The OOM killer: diagram from the video

Cache is not the problem

Now, a trap that scares a lot of beginners. Ana's nightly backup copies three hundred megabytes of product photos. Run free again: free is now zero! But look at buff and cache: it grew to three hundred and forty megabytes. And available barely moved.

ana@ubuntu · after the backup
tar -cf backup.tar /var/lib/shop/photos 2>/dev/null; ls -lh backup.tar-rw-rw-r-- 1 ana ana 301M Oct 29 18:27 backup.tar
ana@ubuntu · after the backup
free -h               total        used        free      shared  buff/cache   available
Mem:           1.0Gi       709Mi          0B        26Mi       340Mi       314Mi
Swap:             0B          0B          0B

Linux uses spare memory to cache files it just read or wrote, because memory is faster than the disk. It gives that memory back the moment a program needs it. So low free memory is normal, and healthy. Watch available instead. When available heads to zero, then you're running out.

Cache is not the problem: diagram from the video

Reason 2: A memory leak

Reason two is a memory leak. The shop Ana runs keeps a list of recently viewed products, to show them again quickly. But the list has no limit, and nothing ever removes a page. Every visit adds a little. Memory that's never given back is a leak, and a leak only grows.

Reason 2: A memory leak: diagram from the video

Let's watch it. Here are the four workers. Now a hundred product visits, sent with curl. Every worker grew. Together, they hold about one hundred and two megabytes more, one for each visit. Nothing was freed afterwards.

ana@ubuntu · a hundred visits
ps -C shop-worker -o pid,rss,comm    PID   RSS COMMAND
   3521 167712 shop-worker
   3522 168264 shop-worker
   3523 167708 shop-worker
   3524 167740 shop-worker
ana@ubuntu · a hundred visits
for i in $(seq 100); do curl -s localhost:8080/product > /dev/null; done
ana@ubuntu · a hundred visits
ps -C shop-worker -o pid,rss,comm    PID   RSS COMMAND
   3521 185724 shop-worker
   3522 192948 shop-worker
   3523 199084 shop-worker
   3524 198088 shop-worker

Available is down to two hundred and eleven megabytes. Three hundred more visits, and the log shows it: a worker died with signal nine, the OOM killer again, and systemd started the shop again. After the restart, the workers are back to normal size. A restart hides a leak for a while, but the leak is still in the code, and it will grow again.

ana@ubuntu · three hundred more
free -h               total        used        free      shared  buff/cache   available
Mem:           1.0Gi       812Mi        50Mi        26Mi       187Mi       211Mi
Swap:             0B          0B          0B
ana@ubuntu · three hundred more
for i in $(seq 300); do curl -s localhost:8080/product > /dev/null; done
ana@ubuntu · three hundred more
journalctl -u shop -o cat | tail -n 6shop: worker 3524 died (signal 9), starting a new one
shop.service: A process of this unit has been killed by the OOM killer.
shop.service: Failed with result 'oom-kill'.
shop.service: Scheduled restart job, restart counter is at 1.
Started shop.service - Ana's shop.
shop 1.0: 4 workers, catalog 150 MB each
ana@ubuntu · three hundred more
ps -C shop-worker -o pid,rss,comm    PID   RSS COMMAND
   3951 167728 shop-worker
   3952 167728 shop-worker
   3953 167728 shop-worker
   3954 167728 shop-worker

Reason 3: One big job

Reason three is one big job. Once a month, Ana asks the shop for a sales report. The code loads a whole year of sales into memory at once: four hundred megabytes. Day to day, the server is fine, with three hundred and fifteen megabytes available. But four hundred doesn't fit into that.

Reason 3: One big job: diagram from the video

Ana asks for the report. Curl gets an empty reply: the worker died before it could answer. The log shows signal nine again: the OOM killer, and a new worker starting. One request took a worker down, and every customer it was serving.

ana@ubuntu · the monthly report
curl -sS localhost:8080/reportcurl: (52) Empty reply from server
ana@ubuntu · the monthly report
journalctl -u shop -o cat | tail -n 6shop.service: A process of this unit has been killed by the OOM killer.
shop.service: Failed with result 'oom-kill'.
shop.service: Scheduled restart job, restart counter is at 1.
Started shop.service - Ana's shop.
shop 1.0: 4 workers, catalog 150 MB each
shop: worker 3954 died (signal 9), starting a new one

The fixes

Now the fixes, one per reason. For workers, do the maths before you add them: workers times memory per worker must fit in what's available, with room to spare for busy moments. For a leak, fix the code: give every list or cache a limit. For a big job, do the work in pieces, so only one piece is in memory at a time.

The fixes: diagram from the video

Then her developer ships version one point one: it keeps only the last twenty pages, and builds the report twenty megabytes at a time. Four hundred visits this time, and the workers stay under a hundred and ninety thousand kilobytes: about one hundred and eighty-five megabytes. The report finishes too. And no worker died.

ana@ubuntu · version 1.1
sudo cp shop-1.1 /usr/local/bin/shop && sudo systemctl restart shop
ana@ubuntu · version 1.1
journalctl -u shop -o cat | tail -n 1shop 1.1: 4 workers, catalog 150 MB each
ana@ubuntu · version 1.1
for i in $(seq 400); do curl -s localhost:8080/product > /dev/null; done
ana@ubuntu · version 1.1
ps -C shop-worker -o pid,rss,comm    PID   RSS COMMAND
   3979 189688 shop-worker
   3980 189688 shop-worker
   3981 189688 shop-worker
   3982 189688 shop-worker
ana@ubuntu · version 1.1
curl -sS localhost:8080/reportreport ready (400 MB of sales, 20 MB at a time)

One more safety net: a fence. systemctl set-property gives the shop its own memory limit, here eight hundred and fifty megabytes. The status shows the shop's memory, with the maximum next to it. Now if a new bug makes the shop grow, the killer acts inside the shop's fence, and the database and the rest of the server keep running. The database, postgresql, and the shop are both active.

ana@ubuntu · a memory fence
sudo systemctl set-property shop.service MemoryMax=850M
ana@ubuntu · a memory fence
systemctl status shop | grep -E 'Active:|Memory:'     Active: active (running) since Thu 2026-10-29 18:27:24 UTC; 12s ago
     Memory: 722.8M (max: 850.0M available: 127.1M peak: 722.9M)
ana@ubuntu · a memory fence
systemctl is-active postgresql shopactive
active

Two last habits. Watch available, and get an alert when it stays low, so you find out before your customers do. And consider a small swap file. Swap is slow, so it's not extra memory, but it gives Linux somewhere to put memory nobody is using, and it buys you time.

The fixes: diagram from the video

Recap

So, a Linux server runs out of memory for three usual reasons: too many workers, a leak that only grows, or one big job. When it's full, the OOM killer stops the biggest process. Read available, not free. Size workers to fit, give every cache a limit, work in pieces, and give each service its own fence.

How much memory does your server have available right now? Run free dash h, and tell me in the comments.

Sources

Demos: a real Ubuntu 24.04 test server (a system container with 1 GB of memory) running PostgreSQL and a small stand-in web shop we wrote for this video. Watch the full video on YouTube.

More videos