
Your site slows down every evening, a backup failed with No space left on device, or a process was killed overnight, and the next plan up is one click away. A bigger VPS fixes a resource you are really short of. It does not fix a log that fills the disk, a program that leaks memory or a host that keeps your vCPUs waiting. Here is how to tell them apart, what to fix first and how to upgrade safely. Commands are for Ubuntu 24.04 and Debian 12.
Quick answer: Upgrade when one resource runs out at your normal busy hour and no fault explains it: little available memory with steady swapping or OOM kills, a load above your vCore count from your own work, a disk still nearly full after cleanup, or traffic on course to pass your monthly allowance. Fix leaks, runaway logs and repeated work first; a bigger plan only postpones them. Then choose the size that adds the missing resource, back up, upgrade at your quietest hour and check the result.
Take a Snapshot at Your Busiest Hour
Run these at your busy hour (peak players, the nightly backup, a sale), not right after a reboot:
uptime
nproc
free -h
df -h /
df -i /
vmstat 5 5
head /proc/pressure/*
Skip the first row of vmstat numbers, which "gives averages since the last reboot". head labels each pressure file.
| Signal | Where you see it | Upgrade when | Check first |
|---|---|---|---|
| Memory | free -h, si and so in vmstat, OOM kills | Little is available at every peak, with swapping or kills | Leaks, caches, worker counts |
| CPU | Load against nproc; us, sy and st in vmstat | Load above your vCores, us plus sy near 100, st near 0 | A runaway process, steal from the host |
| Disk space | df -h, df -i | Nearly full after cleanup | Logs, Docker, old backups, deleted files held open |
| Disk I/O | wa and b in vmstat, iostat -x | Waits climb at every busy hour | Jobs at peak time, too little RAM |
| Traffic | vnstat -m | The month's estimate passes your allowance | Compression, a CDN for static files |
Memory: Pressure, Swap and OOM Kills
available in free -h is the "Estimation of how much memory is available for starting new applications, without swapping." In vmstat, si and so are the memory swapped in from and out to disk per second. Used but quiet swap is harmless; si and so busy in most lines at your peak are not.
Pressure stall information (PSI), on by default in the Debian 12 and Ubuntu 24.04 kernels, shows whether a shortage costs time. Its some line is "the share of time in which at least some tasks are stalled on a given resource". The full line counts the time all non-idle tasks stall at once, and "a workload that spends extended time in this state is considered to be thrashing." Values are percentages over 10, 60 and 300 seconds.
cat /proc/pressure/memory
sudo journalctl -k | grep -i 'killed process'
journalctl -k shows the kernel messages of the current boot. Out of memory: Killed process means the VPS ran out; Memory cgroup out of memory means a container or service hit its own limit. Memory full above zero through your busy hour, or weekly kills, mean the work no longer fits. The RAM guide covers sizing and swap.
CPU: Load Average, Busy Cores and Steal
uptime shows load averages over 1, 5 and 15 minutes: processes running, waiting for a CPU or waiting for disk. They "are not normalized for the number of CPUs in a system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time." Compare them with nproc, then let vmstat tell you why:
usplussynear 100,stnear 0: your own work fills the vCores. Find the process withpidstat 5 1(from sysstat, below). If it is normal work, more vCores help.strising when you slow down: your vCPUs wait for the host, and more vCores on the same host can wait in the same queue. Shared vCPU vs dedicated CPU shows how to log it.wahigh andbabove zero: processes are "blocked waiting for I/O to complete", a disk problem.
In /proc/pressure/cpu, read only some: "CPU full is undefined at the system level".
Disk Space and Inodes
A full filesystem makes writes fail with No space left on device. df -i / shows inodes, which millions of small files can use up while space is left. To find what fills the disk:
sudo du -xh -d1 / | sort -h | tail -n 15
-x stays on the root filesystem, -d1 totals each top-level directory and sort -h sorts sizes like 2K and 1G. Repeat on the biggest one, often /var.
If df says full but du can't find the space, a process often holds a deleted file open; such a file "will remain in existence until the last file descriptor referring to it is closed." sudo lsof +L1 (package lsof) lists those files; restart the process that holds them. Upgrade for disk when it stays nearly full after the fixes below; leave room for your largest single write, such as a database dump.
Disk I/O: When the Disk Is Too Slow
A busy disk hides in the CPU numbers. For detail, install sysstat:
sudo apt update
sudo apt install sysstat
iostat -x -y 5 3
sudo pidstat -d 5 3
cat /proc/pressure/io
-y skips the report since boot. r_await and w_await are the average milliseconds per read or write, "the time spent by the requests in queue and the time spent servicing them". Don't judge an NVMe disk by %util: for modern SSDs, the man page says, "this number does not reflect their performance limits". pidstat -d shows which process reads and writes.
Waits that climb at every busy hour, with I/O full above zero, mean the disk holds your work up. Rule out a job that does too much at peak time (a backup, a log at debug level) and too little RAM, which makes a database read from disk what it could cache; that one needs more RAM, not more disk.
Traffic: Close to the Monthly Allowance
vnStat counts your monthly traffic:
sudo apt install vnstat
sudo systemctl enable --now vnstat
vnstat -m
Counting starts with the daemon, so there is no earlier history. The estimate line projects the current month; add -i and your interface name (ip -br link lists them) if vnstat picks the wrong one. vnstat --alert 2 2 m tx 8 TB warns, and exits with status 1, only when this month's outgoing estimate passes 8 TB. If your provider counts both directions, use total instead of tx.
On a website, sum the bytes per path in the nginx access log. In the default combined format, field 7 is the path and field 10 is $body_bytes_sent, the "number of bytes sent to a client, not counting the response header":
sudo awk '{b[$7] += $10} END {for (p in b) printf "%.1f MiB %s\n", b[p]/1048576, p}' /var/log/nginx/access.log | sort -rn | head -n 10
Keep a History Before You Decide
One snapshot can mislead. sysstat's collector is off by default on Debian 12 and Ubuntu 24.04, so turn it on and answer Yes to "Activate sysstat's cron job?":
sudo dpkg-reconfigure sysstat
On systemd this enables a timer that stores a sample every 10 minutes in /var/log/sysstat. From the next day, -1 reads yesterday's file:
sar -q LOAD -1
sar -q PSI -1
sar -r -1
sar -u -1
sar -n DEV -1
-q LOAD gives load averages, -q PSI pressure for CPU, memory and I/O, -r memory with kbavail, -u CPU with %iowait and %steal, and -n DEV traffic per interface. A week shows whether a peak is a trend and which hour is quietest for the upgrade.
Fix These Before You Pay More
Logs That Keep Growing
By default the systemd journal may use 10% of the filesystem, "capped to 4G". Check and trim it:
sudo journalctl --disk-usage
sudo journalctl --vacuum-size=500M
For a lasting limit, create the folder with sudo mkdir -p /etc/systemd/journald.conf.d, put these lines in a file size.conf there and run sudo systemctl restart systemd-journald, which its man page calls safe:
[Journal]
SystemMaxUse=500M
Docker's default json-file driver does no log rotation, which "can lead to disk space exhaustion". Docker recommends the local driver, which rotates by default. Add this to /etc/docker/daemon.json (merge it into an existing file) and run sudo systemctl restart docker at a quiet hour, because by default the daemon "shuts down running containers" when it stops; check afterwards that yours run again. The new driver applies to containers created after the restart:
{
"log-driver": "local"
}
sudo docker system df shows the space used by images, containers and volumes, sudo docker image prune removes dangling images, and sudo apt-get clean empties /var/cache/apt/archives. Move old backups off the server too, for example with restic over SSH.
A Process That Keeps Growing
pidstat -r prints a process's RSS, "The non-swapped physical memory used by the task in kilobytes", here every 10 minutes (use the real PID):
pidstat -r -p 1234 600
Memory that climbs all day and never falls back points to a leak, which more RAM only delays. Fix or update the program, and meanwhile cap its systemd service:
sudo systemctl set-property myapp.service MemoryMax=1G
The cap is "applied immediately, and stored on disk for future boots". Above it, the "out-of-memory killer is invoked inside the unit", so only that service dies, and with Restart=on-failure systemd starts it again.
Work the Server Repeats
On Debian 12 and Ubuntu 24.04, /etc/nginx/nginx.conf has gzip on; but the gzip_types line is commented out, so nginx compresses only text/html. Uncomment it (it lists plain text, CSS, JSON, JavaScript and XML types) and run sudo nginx -t && sudo systemctl reload nginx. A CDN takes static files off your traffic: Cloudflare caches by file extension, including images, CSS and JS, and "does not cache HTML or JSON by default". For pages your app rebuilds on every request, a cache such as Redis saves CPU.
How to Upgrade Without Surprises
Size by the bottleneck. More vCores do nothing for a full disk, and the reverse. Our VPS plans list vCores, RAM, NVMe and monthly traffic per size, and the order form offers extra NVMe storage as an add-on.
Back up, then upgrade. On a Space-Node VPS you move to a bigger plan from the client area. Backups are not included in the plan price, so take your own first or arrange one through a support ticket. Pick the quietest hour from sar -q LOAD, and ask your provider whether the change needs a restart.
Check afterwards with nproc, free -h, lsblk and df -hT /. If lsblk shows the bigger disk but df the old size, grow the partition and then the filesystem. For ext4 (the type df -hT shows) on partition 1 of /dev/vda (check your names in lsblk):
sudo growpart /dev/vda 1
sudo resize2fs /dev/vda1
growpart (package cloud-guest-utils) rewrites the partition table "so that the given partition takes up as much space as it can". resize2fs then grows the mounted filesystem to the size of the partition. On XFS, use sudo xfs_growfs / instead.
Or move instead. A new VPS fits when you also want a newer OS or a clean install: see moving a website to a new VPS. If no single server is enough, split the work, for example with the database on its own VPS.
Which One Fits
- Fix first for a leak, a log, deleted files held open or uncompressed responses.
- Upgrade in place when one resource still runs out at every busy hour after the fixes.
- Move to a new VPS when you also want a new OS or a clean start.
FAQ
My VPS shows 90% of RAM used. Do I need more?
Probably not. Linux uses spare RAM as page cache (buff/cache in free -h) and gives it back on demand. Judge by available, by si and so, and by memory pressure.
What load average is too high?
One that stays above nproc through your busy hours. The load also counts processes waiting for disk, so check wa before you buy CPU.
Will a bigger plan fix high steal time? Not necessarily. Steal is time your vCPUs had work while the host ran something else, and more vCores on the same host can share the same wait. Send your provider your log first.
Can I go back to a smaller plan later?
Ask your provider first, because the disk is the hard part: resize2fs can grow a mounted ext4 filesystem, but shrinking needs it unmounted, which for the root filesystem means the server is offline.
Sources
All opened on 30 October 2026:
- Load, CPU and memory: uptime(1), nproc(1), free(1), vmstat(8), head(1), journalctl(1) for Ubuntu 24.04 and Debian 12, oom_kill.c in Linux 6.8
- Pressure stall information: kernel PSI documentation, kernel configs of Debian 12 and Ubuntu 24.04
- Disk: apt(8), df(1), du(1), sort(1), errno(3), unlink(2), lsof(8), iostat(1), pidstat(1), apt-get(8)
- History: sar(1) for Ubuntu 24.04 and Debian 12, sadc(8), Debian 12 sysstat debconf template, postinst, README.Debian and rules, Ubuntu 24.04 sysstat template, upstream collect timer and configure.in
- Traffic: vnstat(1) for Ubuntu 24.04 and Debian 12, vnstatd(8), vnstat package files for Debian 12 and Ubuntu 24.04, ip(8), nginx log module and core module variables, mawk(1)
- Logs: journald.conf(5), mkdir(1), Docker live restore, systemd-journald.service(8), Docker logging drivers, json-file driver, daemon configuration, docker system df, docker image prune
- Leaks: systemd.resource-control(5), systemctl(1), systemd.service(5), service.c in systemd v252 and v255
- Compression and caching: nginx gzip module, nginx(8), nginx.conf in Debian 12 and Ubuntu 24.04, Cloudflare default cache behavior
- Growing the disk: growpart(1), resize2fs(8), lsblk(8), xfs_growfs(8)