Back to articles
Technology Insight

Deep Linux Kernel Tuning: Scaling Node.js VPS to 100,000 Concurrent WebSockets

May 30, 2026

Introduction: The Concurrency Challenge in Real-Time Applications

In the modern web ecosystem, real-time communication is no longer a luxury—it is a core requirement. Whether you are building financial trading dashboards, collaborative SaaS tools, or massive multiplayer gaming platforms, WebSockets provide the persistent, bi-directional communication channel necessary to drive these experiences. However, when deploying a Node.js application on a standard Virtual Private Server (VPS), developers quickly hit an invisible wall long before CPU or memory utilization maxes out. By default, standard Linux distributions are configured as general-purpose operating systems, heavily restricting resource allocations to prevent a single process from monopolizing the system.

To scale a Node.js VPS to handle 100,000 concurrent WebSocket connections, you must look past application-level code and venture deep into the Linux kernel architecture. This comprehensive guide outlines the exact kernel-level optimizations, network stack modifications, and system resource adjustments required to unlock high-concurrency performance on production servers.

Understanding the 100k Concurrency Bottlenecks

Before modifying any configuration files, it is vital to understand the exact constraints the Linux operating system imposes. In a high-concurrency WebSocket environment, the three main architectural bottlenecks are:

  • File Descriptors (FDs): In Linux, everything is a file. Each incoming TCP connection and active WebSocket requires a unique file descriptor. Standard system defaults often cap these limits at 1,024 per process, causing immediate connection drops under load.
  • The TCP IP Stack Concurrency Limits: The network stack maintains internal queues to manage incoming connections, connection tracking (conntrack), and TCP handshakes. Default queue lengths are entirely insufficient for sudden traffic spikes.
  • Memory Allocation per Socket: Every open connection consumes a portion of system memory for read and write buffers. Without precise tuning, 100,000 connections can easily trigger the Out-Of-Memory (OOM) killer, crashing the entire Node.js runtime.

Phase 1: Expanding System-Wide File Descriptor Limits

To overcome the notorious EMFILE: too many open files error, we must elevate the hard and soft limits for file descriptors globally and for the specific user running the Node.js process.

Step 1: Modifying PAM Limits

Open the security limits configuration file using your preferred text editor (e.g., /etc/security/limits.conf) and append the following parameters to increase limits for the application user (assuming the user is named nodejs):

nodejs soft nofile 150000
nodejs hard nofile 150000
root   soft nofile 150000
root   hard nofile 150000

The soft limit represents the threshold currently enforced by the kernel, which the application can increase up to the hard limit without administrative privileges. Setting both to 150,000 ensures ample headroom for 100,000 active WebSockets alongside standard system overhead.

Step 2: Configuring System-Wide Limits

Next, ensure the global system-wide file allocation limit is updated to support these changes across all processes combined. Edit /etc/sysctl.conf and add:

fs.file-max = 2000000

Apply the changes instantly by executing sudo sysctl -p in your terminal.

Phase 2: Advanced Network Stack Tuning via sysctl

The core of Linux kernel optimization occurs within the /etc/sysctl.conf configuration file. The default networking parameters prioritize low memory usage and fairness over extreme throughput. For a dedicated WebSocket server, we must shift this balance completely toward high concurrency.

1. Optimizing TCP Window and Memory Buffers

By default, the Linux kernel allocates relatively large memory buffers for each TCP connection. While excellent for high-throughput file transfers, this strategy is catastrophic for 100,000 persistent, mostly idle WebSocket connections. We must adjust the min, default, and max memory footprints:

net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

The three numbers represent the minimum, default, and maximum bytes allocated per socket. Keeping the minimum buffer low allows idle WebSocket connections to scale efficiently without consuming unnecessary RAM.

2. Enhancing the Connection Backlog and Queues

When tens of thousands of users attempt to connect simultaneously, the kernel places incoming handshakes into a queue. If this backlog is too small, incoming connections are rejected before Node.js can even acknowledge them. Apply the following configurations to enlarge these critical queues:

# The maximum number of packets allowed in the input queue
net.core.netdev_max_backlog = 100000

# The maximum number of outstanding connection requests (syn backlog)
net.ipv4.tcp_max_syn_backlog = 65536

# The maximum number of connections waiting to be accepted by Node.js
et.core.somaxconn = 65536

3. Accelerating Socket Reassignment and Recycling

WebSocket connections are frequently opened and closed by client-side applications. When a socket closes, it enters a TIME_WAIT state for up to two minutes to catch lingering packets. To prevent the server from running out of available local ports, we must enable rapid socket reuse:

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

Setting tcp_tw_reuse allows the kernel to safely recycle TIME_WAIT sockets for outgoing connections, while reducing tcp_fin_timeout frees up closed sockets significantly faster than the default configuration.

Phase 3: Preventing Connection Tracking Bottlenecks

If your VPS uses a firewall (such as UFW or iptables), the Linux kernel uses a module called nf_conntrack to monitor stateful connections. Once the connection tracking table fills up, the server will silently drop all subsequent incoming TCP packets.

Check your system's limits and scale them appropriately by adding the following to /etc/sysctl.conf:

net.netfilter.nf_conntrack_max = 200000
net.netfilter.nf_conntrack_tcp_timeout_established = 7200

By expanding the tracking maximum and reducing the timeout threshold for stale connections, you guarantee that active client connections are never blocked by firewall limitations.

Phase 4: Node.js Level Adjustments

While kernel optimization lays the vital groundwork, the Node.js application layer must be explicitly instructed to utilize these expanded limits. Node.js operates on a single-threaded event loop, meaning managing massive I/O workloads requires specific care.

1. Scaling the Threadpool

Certain asynchronous operations (such as DNS lookups and crypto functions) are offloaded to Node's internal libuv threadpool. For 100k connections, increase the default threadpool size from 4 to 128 by setting the environment variable prior to booting your application:

export UV_THREADPOOL_SIZE=128

2. Tuning Garbage Collection for High Memory Profiles

Managing the memory state of 100,000 connections places significant pressure on the V8 engine's garbage collector. Prevent premature crashes by adjusting the max old space size boundary via your execution script:

node --max-old-space-size=4096 server.js

Conclusion and Verification

Achieving 100,000 concurrent WebSocket connections on a single VPS is not merely an application-level milestone; it is an infrastructure orchestration triumph. By systematically removing OS limitations—increasing file descriptors, reducing TCP buffer sizes, widening connection backlogs, and bypassing connection tracking limits—you provide the underlying Linux environment necessary for Node.js to thrive at enterprise scale.

After applying these changes and rebooting your server, always perform a baseline load test using open-source tools like Autocannon or Artillery. Monitor your resource footprint closely with htop and ss -s to verify that your tuned Linux kernel is seamlessly managing the high-volume real-time traffic your modern architecture demands.

Deep Linux Kernel Tuning: Scaling Node.js VPS to 100,000 Concurrent WebSockets | DPTCloud