
September 10, 2026
12 min read
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.
/proc/sys and raises per-process caps like open files. Apply sysctl in /etc/sysctl.d/, set ulimits in /etc/security/limits.d/, then reload and test under load.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.
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.
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.
| Setting | Scope | Config location | Needs service restart? |
|---|---|---|---|
| fs.file-max | Entire kernel | /etc/sysctl.d/ | No — apply with sysctl |
| net.core.somaxconn | All sockets | /etc/sysctl.d/ | No |
| nofile (ulimit) | Per user/process | /etc/security/limits.d/ | Yes — new sessions only |
| LimitNOFILE | systemd unit | /etc/systemd/system/*.d/ | Yes — daemon-reload + restart |
| worker_rlimit_nofile | Nginx only | nginx.conf | Yes — 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.
- Set
fs.file-max = 2097152andnet.core.somaxconn = 4096minimum — higher if traffic warrants it. - Set
www-dataand systemdLimitNOFILE=65535for PHP-FPM and Nginx. - Align PHP-FPM
pm.max_childrenso total connections stay within MySQLmax_connections. - Set MySQL
open_files_limitinmy.cnfto at least 65535. - Keep
vm.swappiness = 10unless the box is memory-starved — then fix RAM first. - 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.
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 filesin Nginx or PHP-FPM logsSYN floodordropcounters innetstat -sorss -s- MySQL
Can't open filewhenopen_files_limitis 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.
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_rmemandtcp_wmemvalues 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 -wfor 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/*.confand runsysctl --system— never rely onsysctl -walone. - Set matching
nofileulimits in/etc/security/limits.d/and systemdLimitNOFILEfor PHP-FPM, Nginx, MySQL, and Redis. - Verify with
/proc/PID/limitson 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
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.

