systemd Resource Controls: Stop One App from Crashing Your Whole VPS

Updated on Sep 26, 2026
Kimberly N
9 MINS READ
Table of Contents
Use systemd Resource Controls to limit CPU, Memory, and I/O

If one app on your VPS starts using all the CPU, RAM, or disk I/O, every other app on that server slows down or crashes with it. Systemd resource controls solve this problem. They let you put a tight budget on each service, so one noisy app can never take down the whole server's resources.

This guide shows you how to limit CPU, memory, disk I/O, and process count for real services on a Linux VPS. 

What Are systemd Resource Controls?

These settings are simple lines you add to a service file. They tell Linux how much CPU, memory, disk I/O, and how many processes a service can use.

Systemd resource controls use a kernel feature called cgroup v2. Every service gets its own cgroup, and these lines write limits straight into it. You never touch cgroup files manually. You just write a few lines in a systemd unit, and systemd turns them into cgroup v2 rules for you. That's why this works so well on a shared VPS. No extra software, no containers, just a text file you already know how to edit.

As of late 2026, systemd v261 is the latest stable version. Since systemd v259, cgroup v2 is the only supported option; cgroup v1 is gone. Every modern VPS distro such as Ubuntu 22.04+, Debian 11+, or Rocky Linux 9+ uses cgroup v2 by default.

Requirments

Before you start, make sure you have these things ready:

  • A Linux VPS running Ubuntu 22.04/24.04, Debian 11/12, or a similar modern distribution.
  • Root or sudo access.
  • systemd version 247 or newer. Almost every VPS image today has this.
  • A little free disk space and RAM to run test workloads safely.

Check your systemd version and confirm cgroup v2 is active:

Bash
systemctl --versionmount | grep cgroup2

Note: Resource limits only make sense if you know exactly what resources your server has. If you're choosing a plan, Perlod's Linux VPS hosting gives you dedicated and predictable CPU and RAM. That way, the limits you set below are based on real numbers, not a shared server.

Also, install one extra tool used for the CPU and memory stress tests in this guide:

Bash
sudo apt updatesudo apt install stress-ng -y

Step 1: Protect a Real Service with a Drop-In

Instead of editing a service's original unit file, systemd uses drop-in files. A drop-in is a small override file that sits alongside the main unit and adds or replaces settings.

We assume Nginx is already installed and running on your VPS. To add a resource budget to it, you can run:

Bash
sudo systemctl edit nginx.service

This opens an editor and creates an empty override file at /etc/systemd/system/nginx.service.d/override.conf. Type this inside the block systemd shows you:

Bash
[Service]CPUQuota=50%MemoryMax=512MTasksMax=200IOWeight=50

Save and exit. Modern systemd reloads the unit database automatically when you use systemctl edit, but it is good practice to run it manually too:

Bash
sudo systemctl daemon-reloadsudo systemctl restart nginx

Now nginx can never use more than half a CPU core, 512 MB of RAM, or 200 processes/threads. It also gets a smaller share of disk I/O when the disk is busy.

That's the whole point of systemd resource controls: real limits on a real service, with no code changes needed.

Note: If you'd rather create the drop-in manually instead of through the editor, you can use:

Bash
sudo mkdir -p /etc/systemd/system/nginx.service.dsudo nano /etc/systemd/system/nginx.service.d/resource.conf

Paste the same [Service] block, save, then run daemon-reload and restart as above.

Step 2: Build a Noisy Test Service

To actually see the effect of these limits, you need something that misbehaves on purpose. You can create a fake noisy app unit:

Bash
sudo nano /etc/systemd/system/noisy-app.service
Bash
[Unit]Description=Noisy Test Application [Service]ExecStart=/usr/bin/stress-ng --cpu 4 --vm 2 --vm-bytes 1G --io 2 --timeout 180sRestart=on-failureRestartSec=5StartLimitIntervalSec=60StartLimitBurst=3 [Install]WantedBy=multi-user.target

Reload and start it without any resource limits first, so you can see what an unmanaged app does to a small VPS:

Bash
sudo systemctl daemon-reloadsudo systemctl start noisy-apptop

Watch top for a few seconds. Without limits, this service tries to use every CPU core and up to 2 GB of RAM. On a small VPS, that alone can slow down or crash other services, like nginx. This is the exact problem resource limits are meant to stop. Stop it before moving on:

Bash
sudo systemctl stop noisy-app

Step 3: Limit CPU with CPUQuota

The main CPU setting in systemd resource controls is CPUQuota. It sets a hard limit on CPU time, shown as a percentage of one core. CPUQuota=50% means half a core. CPUQuota=200% means two full cores. This limit applies even if the rest of the server is completely idle.

Add a drop-in for the noisy app:

Bash
sudo systemctl edit noisy-app.service
Bash
[Service]CPUQuota=50%CPUWeight=50

CPUQuota is the hard limit. CPUWeight only matters when two or more services fight for the same CPU. For example, if one service has weight 50 and another stays at the default 100, the weight-50 service gets roughly a third of the CPU, not half. Restart and test:

Bash
sudo systemctl daemon-reloadsudo systemctl restart noisy-apptop

Even though stress-ng --cpu 4 tries to load four cores, top will show the service stuck at about 50% total CPU across all its threads. The kernel is throttling it in every scheduling cycle, exactly as CPUQuota tells it to.

Step 4: Limit Memory with MemoryMax

MemoryMax is the hard memory limit in systemd resource controls. If a service goes over it, the kernel kills processes inside that service's cgroup. Other services on the VPS are not affected.

Bash
sudo systemctl edit noisy-app.service
Bash
[Service]MemoryHigh=200MMemoryMax=256M
  • MemoryHigh is a soft limit. If the service goes over it, it gets slowed down and pushed to free up memory, but it isn't killed.
  • MemoryMax is the hard limit. If the service crosses it, the kernel kills a process inside that service's cgroup.
Bash
sudo systemctl daemon-reloadsudo systemctl restart noisy-appjournalctl -u noisy-app -f

Since stress-ng --vm-bytes 1G tries to use 1 GB but the limit is only 256 MB, the service gets killed within seconds.

The journal will show a line like Main process exited, code=killed, status=9/KILL. Running systemctl status noisy-app will show the unit as failed, with an OOM reason.

Only the noisy service gets killed. Nginx and everything else on the VPS keep running fine; that's the whole point of setting a per-service memory limit.

Step 5: Limit Disk I/O with IOWeight

IOWeight is the I/O version of these controls. Like CPUWeight, it's not a fixed limit; it only decides how disk bandwidth is shared when two or more services are reading or writing at the same time. The default weight is 100, and it can be set anywhere from 1 to 10000.

Bash
sudo systemctl edit noisy-app.service
Bash
[Service]IOWeight=25IOAccounting=yes

Then, in a second terminal, start a heavy write test on the same disk while the noisy app runs its own --io 2 stress workers:

Bash
sudo systemctl restart noisy-appdd if=/dev/zero of=/tmp/testfile bs=1M count=2048 oflag=direct

With IOWeight=25 on the noisy app and the default 100 for everything else, the noisy app gets roughly a fifth of the disk bandwidth when both are competing. The rest goes to the dd write and other services.

If you want a fixed limit instead of a shared split, use IOReadBandwidthMax=/dev/sda 20M and IOWriteBandwidthMax=/dev/sda 20M in the same drop-in.

Step 6: Limit Process Count with TasksMax

A runaway script can create thousands of processes and freeze a VPS by filling up its process table. TasksMax stops this. It limits the number of tasks a service's cgroup can ever hold.

Bash
sudo systemctl edit noisy-app.service
Bash
[Service]TasksMax=50

Test it safely with a controlled scope:

Bash
sudo systemd-run --scope -p TasksMax=50 bash -c 'for i in $(seq 1 200); do sleep 60 & done; wait'

After the 50th background process, every further fork() call inside that scope fails with Resource temporarily unavailable. You can confirm the exact numbers directly from the cgroup files:

Bash
cat /sys/fs/cgroup/system.slice/noisy-app.service/pids.maxcat /sys/fs/cgroup/system.slice/noisy-app.service/pids.current

Step 7: Limit Failed-Restart Loops

A service that keeps crashing and restarting can itself become the noisy neighbor. Every restart uses CPU and I/O. You can add restart limits along with your other settings:

Bash
[Unit]StartLimitIntervalSec=60StartLimitBurst=3 [Service]Restart=on-failureRestartSec=5

With this, if the service fails and restarts three times within 60 seconds, systemd stops trying and marks it as failed instead of retrying forever. Check the state with:

Bash
systemctl status noisy-app

If you see start request repeated too quickly, the limit is working as it should. Once you've fixed the problem, reset it with systemctl reset-failed noisy-app.

Step 8: Set a Budget for Multiple Apps

On a VPS running several apps, it helps to limit them as a group, not just one at a time. Create a slice, a systemd grouping unit, with its own shared budget:

Bash
sudo nano /etc/systemd/system/apps.slice
Bash
[Slice]CPUQuota=200%MemoryMax=2GTasksMax=500IOWeight=50

Then, add one line to each service's drop-in to point it at this slice:

Bash
[Service]Slice=apps.slice

No matter how many app services you run, or what their own CPUQuota and MemoryMax values are, everything inside apps.slice together can never go over 2 CPUs and 2 GB of RAM.

This is how good systemd resource controls stop one noisy app from starving a multi-app VPS: you limit the whole group, not just one service.

Monitor Live cgroup Stats

You don't have to guess if your limits are working. Systemd and the kernel show you live numbers:

Bash
systemctl status noisy-appsystemctl show noisy-app -p CPUQuotaPerSecUSec -p MemoryMax -p TasksMax -p IOWeightsystemd-cgtop

systemd-cgtop gives you a live, top-style view of every cgroup on the VPS. It shows CPU%, memory, and tasks, sorted by usage, the fastest way to spot which service is being noisy right now.

For raw kernel numbers, read the cgroup v2 files directly:

Bash
cat /sys/fs/cgroup/system.slice/noisy-app.service/cpu.maxcat /sys/fs/cgroup/system.slice/noisy-app.service/memory.maxcat /sys/fs/cgroup/system.slice/noisy-app.service/memory.currentcat /sys/fs/cgroup/system.slice/noisy-app.service/io.stat

cpu.max shows your quota and period in microseconds. memory.max and memory.current show your cap and live usage in bytes. io.stat shows read/write bytes and operations for each disk.

If you use self-hosted monitoring like Beszel to see these numbers on a dashboard instead of raw files, our Beszel common issues and fixes guide covers the most common agent and metric problems.

Conclusion

Systemd resource controls turn a shared VPS into a set of predictable, isolated budgets. With just a few lines in a drop-in file, you can stop any single service from grabbing all the CPU, RAM, disk I/O, or process slots on your server. 

We hope you enjoy this guide.