Linux Kernel TCP/IP Stack Hardening: `sysctl.conf` Tuning for 100,000+ Concurrent WebSockets

Out-of-the-box Linux kernel networking limits drop incoming SYN packets, choke on file descriptors, and exhaust connection queues under heavy real-time traffic. Discover the production sysctl parameters required to sustain 100,000+ concurrent WebSockets on a single VPS.

The Default Linux Ceiling for High-Concurrency WebSockets

Modern real-time systems—such as conversational voice agents, live trading terminals, telemetry ingestors, and collaborative canvases—rely on persistent WebSockets and HTTP/2 connections. While a standard REST API opens a socket, exchanges a request/response in 50ms, and closes the connection, a WebSocket client holds its TCP socket open for minutes or hours.

When you scale a single Linux VPS beyond 10,000 concurrent persistent connections, standard distributions (Ubuntu, Debian, AlmaLinux) hit hard operating system ceilings. Incoming connection attempts begin timing out, Nginx reports 111: Connection refused, and kernel dmesg logs show ominous alerts: TCP: possible SYN flooding on port 443. Sending cookies. This is not a hardware CPU shortage; it is the default Linux kernel TCP/IP stack rejecting traffic due to conservative buffer sizes and queue limits.

1. Anatomy of the TCP Connection Lifecycle Under Load

Before a WebSocket connection upgrades from HTTP, it must complete the standard TCP three-way handshake (SYN → SYN-ACK → ACK). Under high concurrent arrival rates, connection handshakes pass through two kernel queues:

Kernel Queue / Parameter Default Linux Value Production 100k Value Failure Mode If Undersized
net.ipv4.tcp_max_syn_backlog 128 - 512 65,535 SYN queue overflows; new handshakes silently dropped
net.core.somaxconn 128 - 4096 65,535 Accept queue overflows; Gunicorn/Node.js cannot accept connections fast enough
net.ipv4.tcp_fin_timeout 60s 15s Sockets linger in TIME_WAIT for a full minute, exhausting ephemeral ports
net.ipv4.ip_local_port_range 32768 60999 10240 65535 Out of outbound ephemeral ports when reverse proxying
fs.file-max ~100,000 2,097,152 Too many open files OS error across all processes

2. Production `sysctl.conf` Configuration for 100k Connections

Apply these hardened kernel parameters to /etc/sysctl.d/99-highconcurrency.conf to scale network socket capacity and optimize memory consumption per connection:

# /etc/sysctl.d/99-highconcurrency.conf
# ==============================================================================
# Linux Kernel TCP/IP Hardening for 100,000+ Concurrent Sockets
# ==============================================================================

# 1. Expand Connection Handshake Queues
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 65535

# 2. Ephemeral Port Range Expansion (Provides ~55,000 outbound ports)
net.ipv4.ip_local_port_range = 10240 65535

# 3. Aggressive TIME_WAIT Socket Recycling
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1

# 4. Memory Buffer Tuning per Socket (Enables 100k sockets to fit in 4GB RAM)
# Min, Default, Max buffer allocations in bytes
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.tcp_wmem = 4096 65536 4194304
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

# 5. TCP Keepalive Optimization (Detect dead mobile connections in 60s instead of 2 hours)
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5

# 6. Global File Descriptor Limits
fs.file-max = 2097152
fs.nr_open = 2097152

Activate the configuration immediately without rebooting:

sudo sysctl -p /etc/sysctl.d/99-highconcurrency.conf

3. File Descriptor & Process Limits (`limits.conf`)

In Unix, every socket is represented as a file descriptor. Even if the kernel allows 2 million files globally, user processes are constrained by system security limits. Update /etc/security/limits.conf and your systemd unit service files:

# /etc/security/limits.conf
* soft nofile 1048576
* hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576
www-data soft nofile 1048576
www-data hard nofile 1048576

For services managed by systemd (Nginx, Gunicorn, Daphne), ensure the unit file explicitly declares LimitNOFILE:

[Service]
LimitNOFILE=1048576
LimitNPROC=512000

4. Nginx Worker & Event Loop Sizing

Finally, align Nginx with your kernel's enlarged epoll capabilities in /etc/nginx/nginx.conf:

user www-data;
worker_processes auto;
worker_rlimit_nofile 1048576;

events {
    worker_connections 65535;
    use epoll;
    multi_accept on;
}

http {
    # Keepalive timeouts for persistent WebSocket proxying
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

To inspect active connections and verify your limits live on production, see our companion checklist on Production Linux VPS Hardening.

Architectural Continuity & Deep Dives

For related production architectures and system implementations, explore these companion guides:

Production Engineering Takeaways

  • Keepalive matters: Dropping default TCP keepalive from 7200s to 300s prevents zombie connections from severed mobile devices from consuming RAM indefinitely.
  • Buffer sizing dictates scale: Keeping default socket buffers at 64KB–87KB allows 100,000 idle WebSockets to reside comfortably in ~6GB of system RAM.
  • Monitor queue drops: Use netstat -s | grep -i listen to verify that listen queue overflows remain at zero during traffic spikes.
All Insights
Chat on WhatsApp