Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Linux Performance Tuning with sysctl and ulimits

By Kokil Thapa | Last reviewed: September 2026

Your Laravel app runs fine on a laptop, then stalls under real traffic on a VPS. The logs show "too many open files" or slow outbound API calls. Linux performance tuning with sysctl and ulimits fixes that layer — the kernel and per-process limits — before you touch PHP-FPM pools or MySQL indexes. On production Ubuntu servers I maintain, these two knobs sit between raw hardware and your Linux system administration stack. This guide covers what to change, where to put it, and how to verify it without breaking the next deploy.

What is Linux performance tuning with sysctl and ulimits?

Linux splits tunable limits into two layers. sysctl changes kernel-wide parameters: TCP buffers, swap aggression, connection backlogs, and memory overcommit rules. ulimits cap what each user process may consume: open file descriptors, stack size, and max processes.

Application tuning alone cannot fix a kernel that drops SYN packets or a PHP-FPM worker blocked at 1,024 open files. I've seen this on booking portals and eCommerce sites where traffic spikes during festivals. The fix is rarely exotic. It is usually raising fs.file-max, bumping net.core.somaxconn, and matching Nginx, MySQL, and PHP-FPM to those values.

Two Layers of Linux LimitsKernel Layer — sysctlTCP, memory, file-max, somaxconn/proc/sys and /etc/sysctl.d/Process Layer — ulimitsnofile, nproc, stack per user/etc/security/limits.d/NginxPHP-FPMMySQL
Linux performance tuning with sysctl and ulimits: kernel settings first, then per-service process limits.

Think of sysctl as city infrastructure — roads and water mains. Ulimits are per-building occupancy rules. Both must align. A common mistake is setting PHP-FPM pm.max_children to 200 while nofile stays at 1,024. Each child may open sockets, log files, and DB connections. The math fails fast.

For background on the wider stack, see guides on PHP-FPM tuning for high traffic and MySQL performance tuning. Those sit above the kernel layer this article addresses.

How do you tune kernel parameters with sysctl?

Sysctl reads values from /proc/sys/. Persistent changes belong in drop-in files under /etc/sysctl.d/, not only in /etc/sysctl.conf. Ubuntu 22.04 and 24.04 both honour numbered files like 99-custom-tuning.conf.

Inspect current values

Before changing anything, snapshot what the running kernel uses:

sysctl fs.file-max
sysctl net.core.somaxconn
sysctl net.ipv4.ip_local_port_range
sysctl vm.swappiness
sysctl -a | grep tcp_keepalive

Compare output across staging and production. Drift between servers causes "works on server A" bugs. I log baseline values before every tuning pass on shared EC2 hosts running multiple Laravel sites.

Write a drop-in configuration file

Create /etc/sysctl.d/99-web-performance.conf with settings suited to a web + database box:

# File descriptors — kernel ceiling
fs.file-max = 2097152

# Connection backlog — must match or exceed Nginx/Apache listen backlog
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535

# Local ephemeral ports for outbound API calls
net.ipv4.ip_local_port_range = 1024 65535

# TCP tuning for busy HTTP servers
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# Memory — conservative swap use on app servers
vm.swappiness = 10
vm.overcommit_memory = 1

# Optional: if Redis persists to disk on same host
# vm.overcommit_memory = 1 is required by Redis fork

Apply without reboot:

sudo sysctl --system
sudo sysctl -p /etc/sysctl.d/99-web-performance.conf

The --system flag loads all files in /etc/sysctl.d/ in lexical order. Official behaviour is documented in the Linux kernel sysctl admin guide.

Sysctl Apply FlowEdit conf/etc/sysctl.d/sysctl --systemloads all drop-ins/proc/sys/*Running Kernel Uses New ValuesImmediate — no reboot requiredVerify After Applysysctl net.core.somaxconn — ss -s — dmesg | tail
Persistent sysctl tuning flow: edit drop-in, apply with sysctl --system, verify against /proc/sys.

Parameters worth understanding before you change them

  • net.core.somaxconn — maximum length of the socket listen backlog. Nginx listen 80 backlog=65535; cannot exceed this value.
  • fs.file-max — system-wide open file cap. Individual processes still need ulimits high enough to use it.
  • vm.swappiness — how eagerly the kernel swaps RAM to disk. Keep it low (1–10) on database and app servers with enough RAM.
  • net.ipv4.tcp_tw_reuse — allows reuse of TIME-WAIT sockets for outbound connections. Useful for apps that call many third-party APIs.

Do not copy tuning profiles from Redis or PostgreSQL docs blindly. A Redis primary on a dedicated box wants different swap rules than a shared LAMP host running WordPress 7.1 and WooCommerce 11.1. Match settings to actual workload and RAM.

How do you configure ulimits for web servers and databases?

Ulimits apply per user session. Web daemons run as www-data, MySQL as mysql, and Redis as redis. Each needs its own limits file. PAM reads /etc/security/limits.conf and files in /etc/security/limits.d/.

Create limits drop-in files

File: /etc/security/limits.d/99-web.conf

www-data  soft  nofile  65535
www-data  hard  nofile  65535
www-data  soft  nproc   65535
www-data  hard  nproc   65535

mysql     soft  nofile  65535
mysql     hard  nofile  65535

redis     soft  nofile  65535
redis     hard  nofile  65535

Soft limits can be raised up to hard limits by the process itself. Hard limits require root to increase. Set both to the same value on servers you control.

Systemd may override PAM limits

On Ubuntu 22.04 and 24.04, PHP-FPM and Nginx often start under systemd. PAM limits may not apply unless you set LimitNOFILE in the unit or a drop-in:

sudo systemctl edit php8.3-fpm

Add:

[Service]
LimitNOFILE=65535

Then reload:

sudo systemctl daemon-reload
sudo systemctl restart php8.3-fpm

See the article on managing services with systemd for unit file patterns. This overlap trips up many teams during their first production deploy.

Verify ulimit for a running process

cat /proc/$(pgrep -o php-fpm8.3)/limits | grep "open files"
su -s /bin/bash www-data -c 'ulimit -n'
ulimit -a

The /proc/PID/limits file shows what the kernel actually enforces. Trust it over assumptions from config files. The getrlimit(2) manual page explains each resource type.

SettingScopeConfig locationNeeds service restart?
fs.file-maxEntire kernel/etc/sysctl.d/No — apply with sysctl
net.core.somaxconnAll sockets/etc/sysctl.d/No
nofile (ulimit)Per user/process/etc/security/limits.d/Yes — new sessions only
LimitNOFILEsystemd unit/etc/systemd/system/*.d/Yes — daemon-reload + restart
worker_rlimit_nofileNginx onlynginx.confYes — nginx reload

Nginx also accepts worker_rlimit_nofile in its main context. Set it to match sysctl and ulimit values. Apache with mpm_event has similar directives. Compare both in the Nginx vs Apache performance guide.

What are the safest sysctl and ulimit settings for a Laravel production server?

A typical Laravel 13 or Laravel 12 stack on PHP 8.3/8.4 with Nginx, MySQL 8.4 or 9.7, and Redis 8.10 on a 4 GB VPS needs conservative but real limits. I use this checklist on sites deployed with Deployer 7 and GitLab CI.

  1. Set fs.file-max = 2097152 and net.core.somaxconn = 4096 minimum — higher if traffic warrants it.
  2. Set www-data and systemd LimitNOFILE=65535 for PHP-FPM and Nginx.
  3. Align PHP-FPM pm.max_children so total connections stay within MySQL max_connections.
  4. Set MySQL open_files_limit in my.cnf to at least 65535.
  5. Keep vm.swappiness = 10 unless the box is memory-starved — then fix RAM first.
  6. Document every change in your deploy repo or runbook for rollback.

On a legal-tech portal with document uploads, open files add up fast. Spatie Media Library and concurrent PDF generation can push workers toward descriptor limits. Raising ulimits is cheaper than debugging intermittent 500 errors at midnight.

Default vs Tuned LimitsDefault Ubuntunofile: 1024somaxconn: 4096swappiness: 60Risk: EMFILE errorsDropped connectionsTuned Web Servernofile: 65535somaxconn: 65535swappiness: 10Stable under loadRoom for API callstune
Default Ubuntu limits versus tuned values used in Linux performance tuning with sysctl and ulimits on production web hosts.

Pair kernel work with application-level guides: Laravel performance optimization, caching strategies, and speed up Ubuntu performance. The full picture spans every layer.

For a booking platform like Adventure Third Pole Trek, peak season traffic hits Nginx first, then PHP-FPM, then MySQL. All three must agree on connection and file limits. Our testing and optimization service often starts with this audit before touching query plans.

How do you verify sysctl and ulimit changes after tuning?

Tuning without verification is guesswork. Run these checks after every change and again under load.

Confirm sysctl values stuck

sysctl fs.file-max net.core.somaxconn vm.swappiness
grep -r . /etc/sysctl.d/ | grep -v '^#'

Confirm process limits for each daemon

for svc in nginx php8.3-fpm mysql redis-server; do
  pid=$(pgrep -o "$svc" 2>/dev/null) || continue
  echo "=== $svc (PID $pid) ==="
  grep "open files" /proc/$pid/limits
done

Load test and watch for errors

Use ab, wrk, or your staging CI pipeline. Monitor with ss -s, dmesg, and application logs. Watch for:

  • Too many open files in Nginx or PHP-FPM logs
  • SYN flood or drop counters in netstat -s or ss -s
  • MySQL Can't open file when open_files_limit is too low

The guide on diagnosing high CPU and memory complements this when tuning reveals resource pressure rather than limit walls. Add Netdata monitoring and alerts so limit exhaustion shows up before users complain.

When validating JSON API responses during load tests, a quick pass through the JSON formatter tool helps confirm error payloads are structured and not truncated by proxy buffers.

Limit Error Decision TreeError in logs?EMFILE / too manyopen filesConnection refusedunder loadRaise ulimit +LimitNOFILERaise somaxconn+ Nginx backlogVerify via /proc/PID/limits
Troubleshooting flow when Linux performance tuning with sysctl and ulimits is needed after production errors.

When should you avoid aggressive Linux kernel tuning?

Not every server needs maximum values. Overshooting wastes RAM on socket buffers or hides a memory leak by allowing 500 PHP children. Avoid aggressive tuning when:

  • RAM is under 2 GB — high net.ipv4.tcp_rmem and tcp_wmem values consume kernel memory per connection.
  • You have not measured — run baseline metrics first. Use MySQL tuning for web apps to find query problems before blaming the kernel.
  • Shared hosting restricts sysctl — many budget VPS plans block sysctl -w for security. Upgrade or move to a VPS where you control the kernel namespace.
  • Containers isolate limits differently — Docker and Kubernetes set cgroup v2 limits that override or duplicate ulimit semantics. See Kubernetes performance tuning for that path.

On sister sites sharing one EC2 instance — notary portals, translation sites, and legal guides — I tune once at the host level. Then I cap each site's PHP-FPM pool so one client cannot exhaust file descriptors for everyone. That operational split belongs in support and maintenance planning, not only in sysctl files.

Hosting choice matters too. A Rs 800/month (~USD 6) shared plan will not let you set fs.file-max. For production Laravel or WooCommerce workloads, budget for a VPS where you own the full stack. Our domain and hosting service covers that sizing conversation early.

Read more on the blog, browse the portfolio, or learn about my background in full-stack delivery. For related database work, see database indexing for performance and log rotation on Linux — disk full errors mimic limit errors in logs.

Key Takeaways

  • Apply persistent sysctl changes in /etc/sysctl.d/*.conf and run sysctl --system — never rely on sysctl -w alone.
  • Set matching nofile ulimits in /etc/security/limits.d/ and systemd LimitNOFILE for PHP-FPM, Nginx, MySQL, and Redis.
  • Verify with /proc/PID/limits on running processes, not only config files on disk.
  • Align net.core.somaxconn, Nginx backlog, and PHP-FPM child counts so no layer becomes the bottleneck.
  • Load-test after tuning and monitor for EMFILE and connection drop errors under peak traffic.
  • Skip aggressive TCP buffer inflation on small VPS instances — fix RAM and app config first.

People Also Ask

What is the difference between sysctl and ulimit?

Sysctl modifies kernel-global parameters visible under /proc/sys/. Ulimit sets per-user or per-process resource caps like max open files and max processes. Both are required for complete Linux performance tuning with sysctl and ulimits on a web server.

Do sysctl changes require a reboot?

No. Running sysctl --system or sysctl -p applies values immediately to the running kernel. Rebooting re-reads the same config files at boot — useful only to confirm persistence, not as the apply step itself.

Why does PHP-FPM still show 1024 open files after I changed limits.conf?

systemd starts PHP-FPM without a PAM login session, so /etc/security/limits.d/ may not apply. Add LimitNOFILE=65535 to a systemd drop-in for the PHP-FPM unit, then daemon-reload and restart the service.

Is tcp_tw_reuse safe on production servers?

For modern Linux kernels serving HTTP and outbound API traffic, net.ipv4.tcp_tw_reuse = 1 is widely used and documented. It affects outbound connection reuse, not inbound listener sockets. Test under your workload before enabling on database replication or custom TCP protocols.

Apply kernel tuning before your next traffic spike

Linux performance tuning with sysctl and ulimits is low-cost insurance on any production box running PHP, Nginx, and MySQL. The settings take minutes to apply. The "too many open files" class of failures takes hours to debug under pressure. Document your baseline, apply drop-in configs, verify with /proc/PID/limits, and load-test before Dashain or Black Friday traffic arrives.

Need hands-on help sizing and tuning a production host? Contact us for server setup, deploy pipelines, and ongoing optimization — or explore web development services if you are building the application layer from scratch.

Frequently Asked Questions

Sysctl adjusts kernel-wide parameters like TCP buffers, connection backlogs, and file descriptor ceilings via /proc/sys. Ulimits cap per-process resources such as open files and max processes. Together they fix kernel-layer bottlenecks before PHP-FPM or MySQL tuning.

Sysctl modifies kernel-global settings under /proc/sys/. Ulimit sets per-user or per-process caps like max open files. Both layers are required on a web server.

No. Run sysctl --system or sysctl -p to apply immediately. Reboot only re-reads the same config files at boot.

Put drop-in files under /etc/sysctl.d/, such as 99-web-performance.conf, not only in /etc/sysctl.conf. Ubuntu 22.04 and 24.04 load numbered files in lexical order. Apply with sudo sysctl --system without rebooting.

Snapshot current values with sysctl fs.file-max and sysctl net.core.somaxconn, compare staging and production, then write a drop-in under /etc/sysctl.d/. Apply with sysctl --system and confirm values match /proc/sys. I log baselines before every tuning pass on shared EC2 hosts running multiple Laravel sites.

The article’s 99-web-performance.conf sets fs.file-max to 2097152, net.core.somaxconn and netdev_max_backlog to 65535, ip_local_port_range to 1024–65535, tcp_fin_timeout to 15, tcp_tw_reuse to 1, tcp keepalive values, vm.swappiness to 10, and vm.overcommit_memory to 1. Match these to actual RAM and workload rather than copying Redis or PostgreSQL profiles blindly.

It sets the maximum socket listen backlog length. Nginx listen backlog=65535 cannot exceed this kernel value. If somaxconn stays at the default while Nginx expects a higher backlog, the kernel drops SYN packets under traffic spikes. Align somaxconn, Nginx backlog, and PHP-FPM child counts so no single layer becomes the bottleneck.

Create /etc/security/limits.d/99-web.conf with soft and hard nofile and nproc set to 65535 for www-data, mysql, and redis. On Ubuntu 22.04 and 24.04, also set LimitNOFILE=65535 in systemd drop-ins for PHP-FPM because PAM limits may not apply without a login session. Set Nginx worker_rlimit_nofile in nginx.conf to match.

systemd starts PHP-FPM without a PAM login session, so /etc/security/limits.d/ may not apply. Add LimitNOFILE=65535 via systemctl edit php8.3-fpm, run daemon-reload, restart the service, then verify with cat /proc/PID/limits | grep open files. Trust /proc/PID/limits over assumptions from config files on disk.

Confirm sysctl values with sysctl fs.file-max and grep through /etc/sysctl.d/. Loop through nginx, php8.3-fpm, mysql, and redis-server checking open files in /proc/PID/limits. Load-test with ab or wrk and watch ss -s, dmesg, and application logs for too many open files, SYN drops, or MySQL cannot open file errors.

On a 4 GB VPS running Laravel 12 or 13 with PHP 8.3 or 8.4, Nginx, MySQL 8.4 or 9.7, and Redis 8.10, set fs.file-max to 2097152, somaxconn to at least 4096, www-data and systemd LimitNOFILE to 65535, MySQL open_files_limit to 65535, and vm.swappiness to 10. Align PHP-FPM pm.max_children with MySQL max_connections and document every change in your deploy runbook.

For modern Linux kernels serving HTTP and outbound API traffic, net.ipv4.tcp_tw_reuse = 1 is widely used. It allows reuse of TIME-WAIT sockets for outbound connections, useful for apps calling many third-party APIs. It affects outbound reuse, not inbound listener sockets. Test under your workload before enabling on database replication or custom TCP protocols.

Watch for too many open files in Nginx or PHP-FPM logs, SYN flood or drop counters in netstat -s or ss -s, and MySQL cannot open file when open_files_limit is too low. Slow outbound API calls and stalls under real traffic on a VPS that runs fine locally often trace back to descriptor or connection limits rather than application code alone.

Skip maximum values when RAM is under 2 GB because high tcp_rmem and tcp_wmem consume kernel memory per connection. Avoid tuning before measuring baseline metrics. Shared hosting plans around Rs 800/month (~USD 6) often block sysctl changes entirely. On containers, Docker and Kubernetes cgroup v2 limits override ulimit semantics, so host-level tuning alone is not enough.

A common mistake is setting PHP-FPM pm.max_children to 200 while nofile stays at 1024 — each child opens sockets, log files, and DB connections and the math fails fast. Set fs.file-max at the kernel level, matching nofile ulimits and systemd LimitNOFILE per daemon, then cap each site’s PHP-FPM pool on shared EC2 hosts so one client cannot exhaust descriptors for everyone.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: