How to Optimize Ubuntu on Low-Resource Servers: A Complete Practical Guide

How to Optimize Ubuntu on Low-Resource Servers: A Complete Practical Guide

Compact Ubuntu server surrounded by optimization gauges showing improved CPU, RAM, disk, and network performance

Running Ubuntu on a low-resource server — whether it’s a 1 GB RAM VPS from a budget provider, an aging dedicated machine repurposed as a home lab, an ARM single-board computer, or a tight cloud instance you’re trying to stretch across more workloads — is a familiar challenge for system administrators. Modern Ubuntu releases are increasingly tuned for desktops and well-provisioned servers, which means out-of-the-box installations include services, daemons, and defaults that consume memory and CPU cycles you may not have to spare. The good news is that Ubuntu remains extraordinarily flexible, and a methodical optimization pass can reclaim hundreds of megabytes of RAM, dramatically reduce disk I/O, and noticeably improve responsiveness on hardware that initially feels inadequate for the job.

This guide is a comprehensive, hands-on manual for squeezing maximum performance out of Ubuntu Server on constrained hardware. It is organized roughly in the order an administrator should approach the work — measurement first, then service trimming, then memory and swap tuning, then disk and filesystem optimization, then network stack adjustments, and finally long-term maintenance practices. Every recommendation includes the exact commands to run and explains why the change helps, so you can adapt the advice to your specific situation rather than applying it blindly.

Step One: Measure Before You Optimize

The first rule of optimization is that you cannot improve what you do not measure. Before changing a single configuration file, establish a baseline of how your server is actually using its resources. The following commands provide a complete picture of memory, CPU, disk, and process behavior:

free -h
uptime
top -b -n 1 | head -20
ps aux --sort=-%mem | head -15
ps aux --sort=-%cpu | head -15
df -h
df -i
iostat -xz 1 5
vmstat 1 5

If iostat or vmstat are not installed, add them with:

sudo apt update
sudo apt install -y sysstat htop iotop ncdu

Pay particular attention to four numbers in the output of free -h: total RAM, available RAM (not “free” — available is what matters), swap usage, and the buff/cache value. A server with 80% of its RAM consumed by services and only 50 MB available is fundamentally different from one with 60% used and 400 MB available, even if both report similar “free” figures.

Step Two: Identify and Remove Unnecessary Services

A default Ubuntu Server installation enables several services that are not strictly necessary for many workloads. List all currently active services with:

systemctl list-units --type=service --state=running

For a more comprehensive view including failed and inactive units:

systemctl list-unit-files --type=service | grep enabled

The table below summarizes services commonly enabled on Ubuntu Server that can often be safely disabled on minimal installations. Always verify your specific workload does not depend on the service before disabling it.

Service Purpose Safe to Disable?
snapd Snap package management Yes, if you don’t use snaps
cups Printing service Yes, on headless servers
avahi-daemon mDNS/zeroconf discovery Yes, on remote servers
bluetooth Bluetooth stack Yes, on virtual or remote servers
ModemManager Mobile broadband management Yes, on most servers
whoopsie Crash reporting to Canonical Yes, almost always
apport Crash report collection Yes, on production servers
multipathd Multipath I/O for SAN storage Yes, if no SAN attached
unattended-upgrades Automatic security updates Keep enabled in most cases
systemd-resolved Local DNS resolver cache Keep enabled — minimal cost

To disable a service immediately and prevent it from starting on boot:

sudo systemctl stop snapd.service snapd.socket snapd.seeded.service
sudo systemctl disable snapd.service snapd.socket snapd.seeded.service
sudo systemctl mask snapd.service

The mask command goes one step beyond disable and prevents the service from being started even by dependency. To completely remove snapd and reclaim its disk and memory footprint:

sudo apt purge -y snapd
sudo apt-mark hold snapd
sudo rm -rf ~/snap /snap /var/snap /var/lib/snapd

For other unnecessary services, the same pattern applies — stop, disable, and optionally purge if you are confident the package is not needed.

Step Three: Tame Memory Usage and Swap Behavior

On low-RAM servers, memory pressure is the most common cause of poor performance. The Linux kernel’s default behavior is tuned for desktops and well-provisioned servers, and several adjustments can help small machines significantly. The most important kernel parameter for low-memory systems is vm.swappiness, which controls how aggressively the kernel moves pages from RAM to swap. The default value of 60 is too aggressive for servers with fast storage and counterproductive on tiny servers where swap is slow.

For most server workloads, a swappiness value between 10 and 30 strikes the right balance — swap is used as emergency overflow rather than a routine tier of memory. Set this and other useful parameters in a sysctl drop-in file:

sudo tee /etc/sysctl.d/99-low-memory.conf > /dev/null <<'EOF'
vm.swappiness = 10
vm.vfs_cache_pressure = 50
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
vm.overcommit_memory = 1
EOF
sudo sysctl --system

The table below explains what each parameter does and why these values help on constrained machines.

Parameter Default Recommended Effect
vm.swappiness 60 10 Reduces unnecessary swapping while keeping swap available for emergencies
vm.vfs_cache_pressure 100 50 Keeps directory and inode caches in memory longer for faster filesystem access
vm.dirty_ratio 20 10 Limits how much memory dirty pages can consume before forced writeback
vm.dirty_background_ratio 10 5 Triggers background writeback earlier, smoothing I/O bursts
vm.overcommit_memory 0 1 Allows applications that allocate but don’t use memory to start successfully

Step Four: Configure zRAM for Compressed In-Memory Swap

One of the most effective optimizations on low-RAM servers is replacing or supplementing disk-based swap with zRAM — a kernel feature that creates a compressed block device in RAM. Pages “swapped” to zRAM are compressed in memory rather than written to slow storage, often achieving 2:1 or 3:1 compression ratios for typical workloads. The result is effectively more usable memory at the cost of some CPU overhead.

sudo apt install -y zram-tools
echo -e "ALGO=zstd\nPERCENT=50" | sudo tee -a /etc/default/zramswap
sudo systemctl restart zramswap.service
swapon --show
zramctl

The configuration above allocates 50% of total RAM for zRAM swap using the zstd compression algorithm, which provides an excellent balance of speed and compression ratio. On a 1 GB VPS, this typically yields 1.5–2 GB of effective memory capacity.

Step Five: Optimize Disk I/O and Filesystem Behavior

Disk I/O is often the second-biggest performance constraint on small servers, particularly those with slow shared storage. Several adjustments reduce unnecessary writes and improve responsiveness. Start by adding the noatime mount option to your filesystems, which prevents the kernel from updating file access timestamps on every read — a surprisingly expensive operation on busy servers.

Edit /etc/fstab:

sudo nano /etc/fstab

For each filesystem entry, change defaults to defaults,noatime. After saving, remount without rebooting:

sudo mount -o remount /
sudo mount -o remount /home   # if /home is separate

For SSD-backed servers, ensure the appropriate I/O scheduler is in use. Modern Ubuntu kernels typically default to mq-deadline or none for NVMe devices, which is correct, but on older kernels or rotational disks you may want to verify:

cat /sys/block/sda/queue/scheduler
echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler

To make scheduler changes persistent across reboots, create a udev rule:

sudo tee /etc/udev/rules.d/60-ioschedulers.rules > /dev/null <<'EOF'
ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
EOF

Step Six: Reduce Logging Overhead

Default systemd journal settings can consume hundreds of megabytes of disk space and generate constant write activity. Cap journal size and force in-memory storage where appropriate:

sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/00-limit.conf > /dev/null <<'EOF'
[Journal]
SystemMaxUse=100M
SystemKeepFree=200M
RuntimeMaxUse=50M
MaxFileSec=1week
ForwardToSyslog=no
EOF
sudo systemctl restart systemd-journald

If you do not need persistent logs across reboots — for example, on a server that ships logs to a remote aggregator — you can switch to volatile storage entirely by setting Storage=volatile in the same file, eliminating disk writes from logging altogether.

Step Seven: Optimize the Network Stack

The default TCP configuration in Ubuntu is reasonable but can be tightened on small servers handling many short connections. Add the following to your sysctl configuration:

sudo tee /etc/sysctl.d/99-network-tune.conf > /dev/null <<'EOF'
net.core.somaxconn = 1024
net.core.netdev_max_backlog = 2048
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sudo sysctl --system

The last two lines enable BBR congestion control, which often delivers noticeably better throughput on connections with any meaningful latency or packet loss. Verify BBR is active:

sysctl net.ipv4.tcp_congestion_control
lsmod | grep bbr

Step Eight: Trim the Boot and Initramfs

On servers that reboot rarely, boot time is not critical, but the initramfs can occupy significant disk space and the kernel package set may include drivers and modules unnecessary for your hardware. To rebuild a minimal initramfs:

sudo nano /etc/initramfs-tools/initramfs.conf

Change MODULES=most to MODULES=dep, then rebuild:

sudo update-initramfs -u -k all

This typically reduces initramfs size by 60–80%, which matters on small root partitions.

Step Nine: Clean and Maintain

Regular maintenance prevents gradual resource creep. The following commands clean accumulated package caches, old kernels, and orphaned dependencies:

sudo apt update
sudo apt autoremove --purge -y
sudo apt clean
sudo journalctl --vacuum-time=7d
sudo journalctl --vacuum-size=100M

To list and remove old kernels (keeping the current and one previous):

dpkg --list | grep linux-image
sudo apt purge -y $(dpkg --list | grep '^ii  linux-image-[0-9]' | awk '{print $2}' | grep -v $(uname -r) | head -n -1)

Inspect what is actually consuming disk space with ncdu, which is dramatically more useful than du for finding bloat:

sudo ncdu /

Step Ten: Choose Lighter Alternatives Where Possible

Beyond tuning the existing stack, replacing heavy default components with lighter alternatives can yield substantial gains. The table below summarizes useful substitutions for common server roles.

Heavy Default Lightweight Alternative Typical RAM Savings
Apache HTTP Server nginx or Caddy 50–150 MB
MySQL/MariaDB SQLite (for low-concurrency apps) 200–400 MB
Postfix (full) msmtp or ssmtp (send-only) 30–60 MB
NetworkManager systemd-networkd 20–40 MB
rsyslog systemd-journald only 10–25 MB
Standard OpenSSH Dropbear (resource-constrained only) 5–15 MB
Snap-based packages Native .deb packages 50–200 MB

Step Eleven: Monitor Continuously

Optimization is not a one-time event. Workloads evolve, package updates change defaults, and gradual resource creep can erase earlier gains. Lightweight monitoring tools that suit constrained servers include:

  • htop — interactive process viewer, replaces top with much better usability
  • glances — single-pane overview of CPU, memory, disk, and network
  • vnstat — long-term network bandwidth tracking with negligible overhead
  • iotop — identifies which processes are actually hitting the disk
  • node_exporter + Prometheus — for serious monitoring across multiple servers, with very small per-host footprint

Install the basic set with:

sudo apt install -y htop glances vnstat iotop

A Recommended Optimization Order for a New Low-Resource Server

For administrators setting up a fresh Ubuntu installation on constrained hardware, the following sequence delivers the most impact for the least effort:

  1. Install Ubuntu Server with the minimal package selection during installation.
  2. Run the measurement commands and record baseline memory, disk, and CPU usage.
  3. Remove snapd if not needed, then disable other unnecessary services from the table above.
  4. Install zram-tools and configure compressed swap.
  5. Apply the sysctl tuning files for memory and network parameters.
  6. Add noatime to fstab and remount filesystems.
  7. Cap journal size and configure log rotation appropriately.
  8. Install and configure lightweight monitoring.
  9. Reboot once and re-measure to confirm improvements and identify remaining bottlenecks.

Final Thoughts

Ubuntu on a low-resource server can be remarkably capable when given thoughtful configuration. The differences between an unoptimized 1 GB VPS struggling to keep services responsive and a tuned one running the same workload comfortably are not subtle — they are often the difference between a server that works and a server that doesn’t. The techniques in this guide are conservative, well-tested, and reversible, so an administrator can apply them incrementally and observe their effects rather than committing to a wholesale reconfiguration. Keep notes of what you change and why, take a snapshot before major modifications, and treat optimization as an iterative process informed by real measurement rather than dogma. Done well, these adjustments will keep modest hardware running smoothly for years longer than its specifications suggest.

© All Right Reserved 2019-2020 futuredesktop.org