- 1. TCP BBR — Replacing Cubic with Data-Driven Congestion Control
- 2. File Limits — Eliminating Descriptor Bottlenecks for Nginx & PHP-FPM
- fs.file-max = 2,000,000
- fs.inotify.max_user_watches = 524,288
- 3. Connection Backlog — Surviving Traffic Spikes and SYN Floods
- net.core.somaxconn
- tcp_max_syn_backlog
- netdev_max_backlog
- tcp_syncookies = 1 — Lightweight SYN Flood Protection
- 4. Socket Lifecycle — Reclaiming Ports Faster and Detecting Dead Connections
- tcp_tw_reuse = 1
- tcp_fin_timeout = 15
- Keepalive Tuning: 600s / 30s / 5 Probes
- ip_local_port_range = 1024–65000
- Summary: Why These Numbers Matter
1. TCP BBR — Replacing Cubic with Data-Driven Congestion Control
By default, Linux ships with the Cubic congestion control algorithm. Cubic operates on a loss-based model: it keeps increasing transmission speed until packets start getting dropped, then backs off. This works fine on stable, low-latency networks, but it performs poorly on high-latency links, mobile networks, or paths with variable bandwidth (think international CDN routes).
BBR (Bottleneck Bandwidth and Round-trip propagation time), developed by Google, takes a fundamentally different approach. Instead of waiting for packet loss as a congestion signal, BBR actively measures the actual bottleneck bandwidth and round-trip time (RTT) in real time. It then paces packet delivery precisely at the measured rate. The results are dramatic:
- Significantly lower latency — especially on long-distance or congested links.
- Higher page load speeds — BBR can saturate available bandwidth without causing bufferbloat.
- Smoother throughput — no more “sawtooth” speed oscillations that plague Cubic on unstable networks.
InitOps enables BBR automatically, transforming your server from “guessing” at network capacity to operating on measured, empirical data.
2. File Limits — Eliminating Descriptor Bottlenecks for Nginx & PHP-FPM
Every TCP connection, every log file, every Unix domain socket consumes one file descriptor in the kernel. Under high traffic, Nginx and PHP-FPM can easily open tens of thousands of descriptors simultaneously. If the system limit is too low, you will hit Too many open files errors and the service will start silently dropping new connections — even with plenty of CPU and RAM left.
InitOps adjusts two critical parameters:
fs.file-max = 2,000,000
This sets the system-wide maximum number of file descriptors that can be open at once. Default Linux installations often set this around 500,000–1,000,000. InitOps pushes it to 2 million, ensuring that even with multiple Nginx worker processes, PHP-FPM pools, database connections, and monitoring agents running concurrently, the kernel never runs out of descriptor slots.
fs.inotify.max_user_watches = 524,288
This controls how many files and directories a single user can watch via the inotify subsystem — the kernel mechanism that notifies applications when files change. Tools like systemd, file watchers, and PHP opcache monitors all rely on inotify. The default limit (often 8,192) is trivially exceeded on servers with large codebases. A limit of 524,288 allows the system to monitor entire project trees without throwing ENOSPC (No space left on device) errors that often confuse developers into thinking their disk is full.
3. Connection Backlog — Surviving Traffic Spikes and SYN Floods
When a client sends a request, the connection does not get processed immediately. It first joins a backlog queue while the kernel waits for the application (e.g., Nginx) to call accept(). If this queue is too short, new connections get rejected outright — even when the server has idle CPU cycles ready to handle them.
InitOps raises three key backlog parameters to 65,535:
net.core.somaxconn
This is the maximum length of the socket listen backlog. When Nginx calls listen(), the number of pending connections waiting to be accept()ed cannot exceed this value. At 65,535, Nginx can buffer tens of thousands of incoming connections in the queue, feeding them to worker processes as fast as they can handle them. This is essential for absorbing micro-bursts of traffic without dropping requests.
tcp_max_syn_backlog
This queue holds incoming SYN packets — the first step of the TCP three-way handshake. During a traffic spike, thousands of clients may send SYN packets simultaneously. If the SYN backlog is too small, the kernel silently drops new SYNs. The client then times out and retries, creating the perception of a “slow server” when in reality the server never even had a chance to respond.
netdev_max_backlog
This is the backlog at the network device layer — the queue between the Network Interface Card (NIC) and the kernel’s protocol stack. When the CPU is temporarily busy processing earlier packets, a high netdev_max_backlog prevents the NIC from dropping newly arrived packets before the kernel can even look at them.
tcp_syncookies = 1 — Lightweight SYN Flood Protection
A SYN flood is a classic DDoS attack: the attacker sends a flood of SYN packets with spoofed source addresses, filling the SYN backlog with half-open connections that never complete the handshake. This exhausts kernel memory and blocks legitimate traffic.
When tcp_syncookies = 1 is enabled, the kernel switches to a defense mode. Instead of allocating kernel memory to track each half-open connection, it encodes connection state into a cryptographically signed SYN cookie and sends it back in the SYN-ACK packet. A legitimate client returns this cookie in its final ACK, which the kernel verifies and establishes the connection normally — without ever storing state for the half-open connection. This allows the server to survive moderate SYN floods without requiring complex firewall rules or additional hardware.
4. Socket Lifecycle — Reclaiming Ports Faster and Detecting Dead Connections
A TCP connection does not vanish the moment it closes. It lingers in states like TIME_WAIT and FIN_WAIT, holding onto ports and kernel table entries. Without tuning, a busy server can exhaust its local port pool or fill its connection table — while the CPU sits nearly idle.
tcp_tw_reuse = 1
When a socket enters TIME_WAIT state (typically held for 60 seconds per RFC), it continues to bind the local IP and port, preventing new connections from reusing that combination. On a high-traffic web server handling thousands of short-lived HTTP/1.1 or API connections, this quickly exhausts available port tuples.
Enabling tcp_tw_reuse allows the kernel to reclaim sockets in TIME_WAIT for new outgoing connections to the same remote address. It is safe because the kernel still validates TCP sequence numbers to prevent old packets from interfering with new connections. This is critical for reverse proxies, load balancers, and any server making large numbers of outbound connections.
tcp_fin_timeout = 15
This reduces the time a socket spends in FIN_WAIT_2 state from the default 60 seconds down to 15 seconds. If the remote peer never sends its FIN after the local side has already closed, the socket gets cleaned up much faster, freeing kernel resources for active connections.
Keepalive Tuning: 600s / 30s / 5 Probes
These three values configure tcp_keepalive_time, tcp_keepalive_intvl, and tcp_keepalive_probes:
- 600 seconds (10 minutes): The period of inactivity before the kernel starts sending keepalive probes. If a connection has been idle for 10 minutes, the kernel begins checking whether the peer is still alive.
- 30 seconds: The interval between consecutive probes. If no response is received, the kernel waits 30 seconds before sending the next probe.
- 5 probes: The maximum number of unanswered probes before the kernel declares the connection dead and closes the socket.
Total time to detect a dead connection: 600 + (5 × 30) = 750 seconds (~12.5 minutes). This strikes a deliberate balance: aggressive enough to detect and clean up stale connections (preventing resource leaks), yet conservative enough to avoid flooding the network with unnecessary probe traffic.
ip_local_port_range = 1024–65000
Every outbound TCP connection (e.g., PHP-FPM connecting to MySQL, Nginx proxying to an upstream backend) requires an ephemeral source port. The default Linux range is often 32768–60999, yielding roughly 28,000 available ports. InitOps expands this to 1024–65000, providing approximately 64,000 concurrent outbound connections — nearly double the default capacity.
This matters enormously for:
- Reverse proxy servers forwarding to multiple backend nodes.
- Microservices opening thousands of connections to databases and third-party APIs.
- Containerized environments (Docker/Kubernetes) where each pod may initiate thousands of outbound connections.
Summary: Why These Numbers Matter
Kernel tuning is not about cranking every value to the maximum. It is about calibrated balance — matching kernel behavior to real-world workload patterns. InitOps has carefully selected each value in this profile:
| Parameter | Value | Primary Benefit |
|---|---|---|
| TCP BBR | Enabled | Maximize bandwidth utilization, reduce latency |
| fs.file-max | 2,000,000 | No descriptor exhaustion under high concurrency |
| fs.inotify.max_user_watches | 524,288 | Uninterrupted file monitoring for large codebases |
| net.core.somaxconn | 65,535 | Massive listen queue for traffic spikes |
| tcp_max_syn_backlog | 65,535 | Absorb SYN bursts without silent drops |
| netdev_max_backlog | 65,535 | Prevent NIC packet drops during CPU bursts |
| tcp_syncookies | 1 | Memory-efficient SYN flood resistance |
| tcp_tw_reuse | 1 | Efficient port recycling from TIME_WAIT |
| tcp_fin_timeout | 15 seconds | Faster socket cleanup |
| TCP Keepalive | 600/30/5 | Detect dead connections without excessive probing |
| ip_local_port_range | 1024–65000 | Nearly double concurrent outbound connections |
Together, these settings transform a stock Linux server into a high-throughput, resilient web engine capable of handling tens of thousands of concurrent connections, responding faster under load, and surviving traffic spikes without immediate hardware scaling. This is the power of precise kernel tuning — not expensive hardware, but deep understanding of the networking stack.
Comments