The Linux kernel exposes hundreds of runtime settings, called kernel parameters, under /proc/sys. The sysctl tool reads and changes them without rebooting or recompiling anything, which makes it the main tool for adapting a general-purpose kernel to a specific workload. In this tutorial you will learn how sysctl works on Ubuntu 24.04, how to make changes persistent the right way, and which memory, file and network parameters are worth changing on a server, with a way to measure whether each change helped.

Prerequisites

To follow this tutorial, you will need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has sudo privileges.
  • A workload you can measure (a web server, a database or an application), so you can compare before and after.

Step 1 - Reading kernel parameters

Parameter names map directly to files under /proc/sys: dots in the name become slashes in the path. These two commands read the same value:

sysctl vm.swappiness
cat /proc/sys/vm/swappiness
vm.swappiness = 60
60

To list every parameter, or search for a group, use -a and filter:

sudo sysctl -a | grep -E '^net\.core\.(somaxconn|netdev_max_backlog)'
net.core.netdev_max_backlog = 1000
net.core.somaxconn = 4096

Parameters are grouped by prefix: vm.* for memory management, fs.* for file systems and file handles, net.* for the network stack and kernel.* for core kernel behavior. Each one is documented in the kernel's Documentation/admin-guide/sysctl/ tree and, for networking, in Documentation/networking/ip-sysctl.rst.

Step 2 - Changing parameters temporarily

sysctl -w changes a value immediately. The change is lost at the next reboot, which makes it the right way to test:

sudo sysctl -w vm.swappiness=10
vm.swappiness = 10

If a value makes things worse, set it back to the value you read in Step 1. Write down the original values before you start experimenting.

Step 3 - Making changes persistent

At boot, systemd-sysctl applies every *.conf file from /usr/lib/sysctl.d/, /run/sysctl.d/ and /etc/sysctl.d/, sorted by file name regardless of the directory. A file in /etc/sysctl.d/ replaces a file with the same name in /usr/lib/sysctl.d/, and when two files set the same parameter, the one read last wins. On Ubuntu, /etc/sysctl.conf is linked as /etc/sysctl.d/99-sysctl.conf, so it is read near the end.

Keep your own settings in a dedicated file instead of editing /etc/sysctl.conf, so they are easy to find and remove:

sudo nano /etc/sysctl.d/90-server-tuning.conf

You will fill this file in the next steps. After saving it, apply every configuration file in the same order the boot process uses:

sudo sysctl --system
* Applying /usr/lib/sysctl.d/10-apparmor.conf ...
* Applying /etc/sysctl.d/10-console-messages.conf ...
...
* Applying /etc/sysctl.d/90-server-tuning.conf ...
* Applying /etc/sysctl.d/99-sysctl.conf ...
* Applying /etc/sysctl.conf ...
vm.swappiness = 10

The output lists every file and the values it set, so you can see if a later file overrides yours. To apply only your file, use sudo sysctl -p /etc/sysctl.d/90-server-tuning.conf.

Step 4 - Measuring a baseline

Tuning without measurement is guessing. Before changing anything, record how the system behaves under its normal load. The sysstat package provides sar, which reports CPU, memory, swap and I/O over time:

sudo apt install sysstat

Take a one-minute sample of memory, paging and swap activity:

sar -r -B -W 5 12

The most useful columns are %memused and kbcached (memory usage and page cache), pgpgin/s and pgpgout/s (disk paging), and pswpin/s and pswpout/s (swapping). Consistent swapping activity or high majflt/s values indicate memory pressure. For disks, run iostat -xz 5 3 and look at %util and await.

Also run the benchmark that matters to your application (for example wrk or ab against your web server, or sysbench against your database) and keep the numbers.

Step 5 - Tuning memory management

These parameters control how the kernel balances application memory, page cache and disk writes.

vm.swappiness (default 60) sets how willing the kernel is to swap out application memory instead of dropping page cache. On database and application servers, a lower value keeps the working set in RAM. A value of 10 is a common choice; 0 does not disable swap but makes it a last resort, which can lead to sudden OOM kills under pressure.

vm.dirty_background_ratio (default 10) and vm.dirty_ratio (default 20) are percentages of available memory that can hold modified data not yet written to disk. At the first threshold the kernel starts flushing in the background; at the second, processes that write are blocked until data is flushed. On servers with a lot of RAM, 20% can be many gigabytes, which causes long write stalls when it is finally flushed. Lower values spread the writes more evenly.

vm.vfs_cache_pressure (default 100) controls how aggressively the kernel reclaims the cache of directory entries and inodes. Values below 100 keep that metadata cached longer, which helps file servers and applications that walk large directory trees.

Add the memory settings to your file:

sudo nano /etc/sysctl.d/90-server-tuning.conf
# Memory management
vm.swappiness = 10
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
vm.vfs_cache_pressure = 50

On machines with more than 64 GB of RAM, consider absolute values instead of ratios, for example vm.dirty_background_bytes = 268435456 (256 MB) and vm.dirty_bytes = 1073741824 (1 GB). Setting a *_bytes parameter automatically zeroes the matching *_ratio, so use one form or the other.

Some software asks for specific values. Redis, for example, recommends vm.overcommit_memory = 1 so background saves do not fail, and Elasticsearch requires vm.max_map_count of at least 262144. Check the current value with sysctl first and set these only on the servers that run that software.

Step 6 - Tuning file handles

The system-wide limit on open files, fs.file-max, is already very large on 64-bit Ubuntu 24.04 and rarely needs changing:

sysctl fs.file-max fs.file-nr
fs.file-max = 9223372036854775807
fs.file-nr = 1344	0	9223372036854775807

The limit that applications actually hit ("Too many open files") is the per-process limit, which is not a sysctl. For services managed by systemd, raise it in the unit with an override:

sudo systemctl edit nginx
[Service]
LimitNOFILE=65535

Restart the service and verify the limit of the running process:

sudo systemctl restart nginx
grep 'open files' /proc/$(pgrep -o nginx)/limits
Max open files            65535                65535                files

Applications that watch many files for changes (IDEs, file sync tools, some Node.js tools) may also need more inotify watches. Check fs.inotify.max_user_watches and raise it only if the application reports that it ran out.

Step 7 - Tuning basic network parameters

Network tuning deserves its own guide, but three parameters matter for almost any busy server:

net.core.somaxconn caps the queue of connections that have been accepted by the kernel but not yet picked up by the application. The default is already 4096 on Ubuntu 24.04. Raise it only if you see overflows, and remember that the application's own backlog (for example listen 80 backlog=4096; in Nginx, whose default is 511) must be raised too.

net.core.netdev_max_backlog (default 1000) is the queue of packets received by the network card but not yet processed by the kernel. Servers handling high packet rates benefit from a larger value.

net.ipv4.ip_local_port_range (default 32768 60999) is the range of source ports for outgoing connections. Reverse proxies and applications that open many connections to backends or databases can run out of ports.

Check whether the listen queue actually overflows before changing it:

nstat -az TcpExtListenOverflows TcpExtListenDrops
#kernel
TcpExtListenOverflows           0                  0.0
TcpExtListenDrops               0                  0.0

If these counters grow under load, add the network settings:

# Network
net.core.somaxconn = 8192
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.ip_local_port_range = 10240 65535

If any service listens on a port inside the new ephemeral range, reserve it so the kernel never hands it out as a source port, for example net.ipv4.ip_local_reserved_ports = 11211,27017. TCP buffers, congestion control (BBR) and TIME_WAIT handling are covered in the TCP/IP tuning guide.

Step 8 - Applying and verifying the configuration

Your complete file should now look similar to this:

# /etc/sysctl.d/90-server-tuning.conf

# Memory management
vm.swappiness = 10
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
vm.vfs_cache_pressure = 50

# Network
net.core.somaxconn = 8192
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.ip_local_port_range = 10240 65535

Apply it and confirm the values:

sudo sysctl --system
sysctl vm.swappiness vm.dirty_ratio net.core.somaxconn
vm.swappiness = 10
vm.dirty_ratio = 10
net.core.somaxconn = 8192

Reboot once to confirm that the values survive a restart, then check them again with the same command. Finally, repeat the measurements from Step 4 under the same load and compare. Keep a change only if it produced a measurable improvement or fixed a specific problem.

Troubleshooting

sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_max: No such file or directory. The parameter belongs to a kernel module that is not loaded yet. For connection tracking, the module loads when the firewall first uses it. Load it at boot by adding nf_conntrack to /etc/modules-load.d/conntrack.conf.

A value is correct after sysctl --system but different after a reboot. Another file sets the same parameter later. Run sudo sysctl --system and look for other files in the output that set it, or search with grep -r parameter_name /etc/sysctl.d /usr/lib/sysctl.d /etc/sysctl.conf.

sysctl: setting key "...": Read-only file system inside a container. Containers cannot change most host kernel parameters. Set them on the host or through your container runtime's options for namespaced parameters.

The system became unstable after a change. Remove the file or the offending line, run sudo sysctl -w parameter=original_value to restore the value immediately, and reintroduce changes one at a time.

Conclusion

You now know how sysctl maps to /proc/sys, how to test values with sysctl -w, how to persist them in /etc/sysctl.d/ and how the load order decides which value wins. You also have a small, safe baseline for memory, file and network parameters and a method to verify each change. As next steps, tune the TCP stack for your bandwidth and latency, raise per-service limits with systemd overrides, and track the effect of your changes with sar over several days.