How to Optimize Ubuntu on Low-Resource Servers: A Complete Practical Guide
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
topwith 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:
- Install Ubuntu Server with the minimal package selection during installation.
- Run the measurement commands and record baseline memory, disk, and CPU usage.
- Remove snapd if not needed, then disable other unnecessary services from the table above.
- Install zram-tools and configure compressed swap.
- Apply the sysctl tuning files for memory and network parameters.
- Add
noatimeto fstab and remount filesystems. - Cap journal size and configure log rotation appropriately.
- Install and configure lightweight monitoring.
- 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.