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:
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:
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:
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:
[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:
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:
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:
sudo nano /etc/systemd/system/noisy-app.service
[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:
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:
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:
sudo systemctl edit noisy-app.service
[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:
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.
sudo systemctl edit noisy-app.service
[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.
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.
sudo systemctl edit noisy-app.service
[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:
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.
sudo systemctl edit noisy-app.service
Test it safely with a controlled scope:
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:
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:
[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:
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:
sudo nano /etc/systemd/system/apps.slice
[Slice]CPUQuota=200%MemoryMax=2GTasksMax=500IOWeight=50
Then, add one line to each service's drop-in to point it at this slice:
[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:
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:
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.