--- title: "How to Tune Linux Kernel Parameters with sysctl on Ubuntu" description: "Learn how sysctl works on Ubuntu 24.04, persist kernel changes in /etc/sysctl.d, and tune memory, file and network parameters for servers without guesswork." category: "Performance Optimization" language: en source: https://cubepath.com/docs/performance-optimization/linux-performance-optimization-sysctl-and-kernel-parameters updated: 2026-09-25 --- # How to Tune Linux Kernel Parameters with sysctl on Ubuntu 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. > Important: Modern kernels ship with sensible defaults, and many values found in old tuning guides are already the default or are counterproductive today. Change one group of parameters at a time, measure, and keep only what helps. ## 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: ```bash sysctl vm.swappiness cat /proc/sys/vm/swappiness ``` ```output vm.swappiness = 60 60 ``` To list every parameter, or search for a group, use `-a` and filter: ```bash sudo sysctl -a | grep -E '^net\.core\.(somaxconn|netdev_max_backlog)' ``` ```output 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: ```bash sudo sysctl -w vm.swappiness=10 ``` ```output 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: ```bash 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: ```bash sudo sysctl --system ``` ```output * 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: ```bash sudo apt install sysstat ``` Take a one-minute sample of memory, paging and swap activity: ```bash 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: ```bash sudo nano /etc/sysctl.d/90-server-tuning.conf ``` ```ini # 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: ```bash sysctl fs.file-max fs.file-nr ``` ```output 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: ```bash sudo systemctl edit nginx ``` ```ini [Service] LimitNOFILE=65535 ``` Restart the service and verify the limit of the running process: ```bash sudo systemctl restart nginx grep 'open files' /proc/$(pgrep -o nginx)/limits ``` ```output 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: ```bash nstat -az TcpExtListenOverflows TcpExtListenDrops ``` ```output #kernel TcpExtListenOverflows 0 0.0 TcpExtListenDrops 0 0.0 ``` If these counters grow under load, add the network settings: ```ini # 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: ```ini # /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: ```bash sudo sysctl --system sysctl vm.swappiness vm.dirty_ratio net.core.somaxconn ``` ```output 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.