Why this matters

Unix systems cap the number of open files per process. Since every network connection counts as an open file, k6 hits this limit at scale:

WARN[0127] Request Failed  error="Get http://example.com/: dial tcp: socket: too many open files"

Estimate your required limit before changing anything: max VUs × HTTP requests per iteration gives a baseline connection count.

Warning

Change settings conservatively and verify the effect before/after with netstat or ss. Document what you changed and why — these settings affect all processes on the machine, not just k6.


Open file limits

Linux

View current limits:

ulimit -Sa   # soft limits (per-session, user-configurable)
ulimit -Ha   # hard limits (absolute max, root only)

The default soft limit for open files is typically 1024. The hard limit is often 1048576.

Raise for the current session:

ulimit -n 250000

Persist across sessions (add to ~/.bashrc):

echo "ulimit -n 250000" >> ~/.bashrc

Raise the hard limit (root required) — edit /etc/security/limits.conf:

alice soft nofile 5000
alice hard nofile 1048576
# or for all non-root users:
* hard nofile 1048576

Changes take effect after logging out and back in.

macOS

View current limits:

launchctl limit maxfiles       # per-process soft + hard limits
sysctl kern.maxfiles           # system-wide total
sysctl kern.maxfilesperproc    # per-process limit

Raise for the current session:

sudo launchctl limit maxfiles 65536 200000

Persist across reboots — create /Library/LaunchDaemons/limit.maxfiles.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key><string>limit.maxfiles</string>
    <key>ProgramArguments</key>
    <array>
      <string>launchctl</string><string>limit</string>
      <string>maxfiles</string>
      <string>64000</string>
      <string>524288</string>
    </array>
    <key>RunAtLoad</key><true/>
    <key>ServiceIPC</key><false/>
  </dict>
</plist>

Note

macOS requires disabling System Integrity Protection (SIP) before editing these daemon files. Boot into Recovery Mode (hold Cmd+R), open Terminal, run csrutil disable, reboot. Re-enable with csrutil enable after changes are applied.


Local port range

Each outgoing TCP connection consumes one ephemeral port. The default range on Linux (~28k ports) can be exhausted at high VU counts.

Linux

View:

sysctl net.ipv4.ip_local_port_range
# default: 32768 60999

Expand:

sysctl -w net.ipv4.ip_local_port_range="1024 65535"

Persist by adding to /etc/sysctl.conf:

net.ipv4.ip_local_port_range=16384 65000

macOS

View:

sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
# default: 49152–65535 (16384 ports)

Expand to match Linux default:

sudo sysctl -w net.inet.ip.portrange.first=32768

Warning

Expanding the range below 49152 means your machine may use ports like 32768–49151 as source ports on outgoing connections. Some firewalls only allow traffic whose source port falls within the IANA dynamic range (49152–65535) and will silently drop the rest. This is only a concern when testing through a firewall to a remote network (e.g. corporate network to staging). On a local network or within the same VPC it’s a non-issue.


TCP TIME_WAIT

After a connection closes, the kernel holds the socket in TIME_WAIT for 15s–2min to handle late-arriving packets. Under heavy load, sockets pile up in this state and prevent new connections.

Enable fast reuse of TIME_WAIT sockets (Linux):

sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_timestamps=1   # required for tw_reuse to work safely

Alternative — avoid TIME_WAIT congestion without kernel changes:

Spread requests across multiple server ports (:8080, :8081, :8082, …).

A socket is uniquely identified by a 4-tuple: (local IP, local port, remote IP, remote port). When connections close, each 4-tuple sits in TIME_WAIT and can’t be reused until the timer expires.

If all requests go to the same remote port (e.g. :8080), every new connection must use a different local port — you burn through your ephemeral port pool and it fills with TIME_WAIT sockets. By varying the remote port, the same local port can be reused: local port 45000 → :8080 and local port 45000 → :8081 are two distinct 4-tuples that don’t interfere with each other, effectively multiplying your available socket pool.


Memory

  • Simple scripts: 1–5 MB per VU (1,000 VUs ≈ 1–5 GB)
  • File upload scripts: tens of MB per VU — each VU gets its own copy of the file

To estimate: run the test locally at 100 VUs, measure memory consumed, scale up linearly.

To reduce memory usage, add --compatibility-mode=base (disables some JS features but cuts RAM significantly).

To share large data across VUs without copying, use SharedArray.


See also

  • k6-large-tests — hardware thresholds, script optimisations, distributed execution
  • k6-vu-saturation — diagnosing when the load generator is the bottleneck