How Many Concurrent Requests Can Your Computer Actually Handle?
The real bottlenecks when you try to ping 160 million domains at once.
A 4-gig file where every line is a domain name. Average domain is maybe 25 bytes with the newline. That's roughly 160 million domains. You want to ping each one to see if it responds.
How many can you do at once?
The Short Answer
Out of the box, with no tuning: 1,000 to 5,000 concurrent connections. With some sysctl knob-turning and ulimit adjustments: 50,000 to 100,000. With serious tuning and decent hardware: 500,000+. With kernel bypass and specialized tools like masscan: millions per second.
But "how many can I open at once" isn't really the right question. The right question is "which limit will I hit first?" Because there are several, and they stack.
The Five Walls
1. File Descriptors
Every open socket is a file descriptor. Your OS has a limit on how many a single process can hold.
ulimit -n
# Probably says 1024
1,024. That's your first wall, and you'll hit it almost immediately. Every concurrent connection eats one.
Fix it:
# Temporary (current session)
ulimit -n 65535
# Permanent (in /etc/security/limits.conf)
* soft nofile 65535
* hard nofile 1048576
You can push this to over a million if you also adjust the system-wide limit:
sysctl -w fs.file-max=2097152
2. Ephemeral Ports
When your machine opens an outbound connection, the kernel assigns it a source port from a pool called the ephemeral port range.
cat /proc/sys/net/ipv4/ip_local_port_range
# Typically: 32768 60999
That's about 28,000 ports. Now, technically a connection is uniquely identified by the tuple (src_ip, src_port, dst_ip, dst_port), so the kernel can reuse the same source port for different destinations. But in practice, you'll run into this ceiling, especially once TIME_WAIT sockets start piling up (more on that in a second).
Fix it:
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
That gets you up to ~64,000. You can also enable reuse of TIME_WAIT sockets:
sysctl -w net.ipv4.tcp_tw_reuse=1
3. Memory
Each TCP connection costs kernel memory. The socket buffers, the TCP control block, the connection tracking entry - it adds up to roughly 3-4 KB per connection in kernel space. Plus whatever your application is holding in userspace per connection.
The math:
| Connections | Kernel Memory |
|---|---|
| 10,000 | ~40 MB |
| 100,000 | ~400 MB |
| 500,000 | ~2 GB |
| 1,000,000 | ~4 GB |
If you're on a machine with 16 GB of RAM, a million connections is feasible but you're eating a significant chunk just on socket overhead. At 160 million connections simultaneously? No. Not happening. Not on any single machine. You'll need to batch.
4. Connection Tracking (conntrack)
If you're running iptables, nftables, or you're behind NAT, the kernel maintains a connection tracking table. Default size:
sysctl net.netfilter.nf_conntrack_max
# Usually 65536
Every connection gets an entry, and each entry uses ~300 bytes. Overflow this table and new connections get silently dropped. No error, no warning. Just... nothing.
Fix it:
sysctl -w net.netfilter.nf_conntrack_max=1048576
Or, if you don't need connection tracking, disable it entirely for a big performance boost.
5. DNS Resolution - The Actual Bottleneck
This is the one nobody thinks about until it bites them.
Your file has domain names, not IP addresses. Every one of those 160 million domains needs a DNS lookup before you can even send a packet. And the standard DNS resolution path in Linux (getaddrinfo() in glibc) is synchronous and blocking. Even if you're using async I/O for everything else, DNS will serialize you down to a trickle.
On top of that, public resolvers like Google (8.8.8.8) and Cloudflare (1.1.1.1) will rate-limit you. Hit them with a few thousand queries per second and they'll start dropping your requests or returning SERVFAIL.
This is almost certainly the wall you'll hit first in practice.
Fix it:
- Use an async DNS library like
c-aresinstead of glibc's resolver - Run your own local recursive resolver (like
unbound) that talks directly to authoritative nameservers - Use purpose-built tools like
massdnsorzdnsthat handle high-throughput DNS natively - Pre-resolve everything into IPs first, then do your connectivity checks
Wait - Does "Ping" Mean ICMP or HTTP?
This matters a lot.
ICMP ping (the ping command) uses raw sockets. No TCP. No ports. No three-way handshake. You send an ICMP echo request and wait for an echo reply. The kernel tracks outstanding pings by identifier and sequence number, not by port. This is way cheaper than TCP.
Tools like fping can blast out tens of thousands of ICMP pings per second in parallel mode. masscan bypasses the kernel's network stack entirely and generates raw packets, hitting millions of targets per second on a single machine.
HTTP/TCP checks (hitting port 80 or 443 to see if a web server responds) are more expensive. Each one needs a full TCP connection: three-way handshake, file descriptor, ephemeral port, the whole stack of limits above.
If all you need is "does this domain resolve and is something listening?" - ICMP or a raw TCP SYN scan is orders of magnitude faster than actually establishing HTTP connections.
The Practical Approach
You're not going to open 160 million connections simultaneously. Nobody is. Here's what you'd actually do:
1. Resolve DNS in bulk first
# massdns - can resolve millions of domains per hour
massdns -r resolvers.txt -t A -o S domains.txt > resolved.txt
2. Scan the resolved IPs
# masscan - SYN scan millions of IPs per second
masscan -iL ips.txt -p 80,443 --rate 100000 -oL results.txt
3. Tune your concurrency to match your hardware
A sane starting point for TCP-based checks with async I/O:
| Machine | Concurrent Connections |
|---|---|
| Laptop (8 GB RAM) | 10,000 - 30,000 |
| Workstation (32 GB RAM) | 50,000 - 200,000 |
| Dedicated server (64+ GB RAM) | 200,000 - 1,000,000+ |
4. Use async I/O
If you're writing your own tool, you must use non-blocking I/O. On Linux, that means epoll or io_uring. On macOS, kqueue. Never spawn a thread per connection - that's how you OOM at 10,000 targets.
In Python, asyncio with aiohttp or httpx can comfortably manage 10,000-50,000 concurrent connections. In Go, goroutines with a semaphore pattern can do similar. In C/Rust with epoll directly, you can push much higher.
Tools the Pros Use
For the "4 gig file of domains" scenario specifically:
- massdns - High-throughput DNS resolver. Can chew through millions of domains using multiple upstream resolvers in parallel
- masscan - Scans the entire internet in under 6 minutes. Uses its own TCP/IP stack, bypassing the kernel entirely. Probably overkill for your use case, but it exists
- httpx (ProjectDiscovery) - Fast HTTP probing with built-in concurrency. Good for checking if web servers are alive
- zdns - High-speed DNS toolkit from the ZMap project
- fping - Parallel ICMP pinger, handles massive target lists natively
- zmap - Single-packet scanner that can survey the entire IPv4 space
So, How Many?
If you're asking "how many TCP connections can my computer hold open at the same time" - with tuning, somewhere between 50,000 and 1,000,000 depending on your hardware and OS config.
If you're asking "how fast can I check 160 million domains" - the answer is you don't hold them all open at once. You pipeline. Resolve DNS in bulk, scan in batches with high concurrency, and use tools purpose-built for the job. A single machine with massdns + masscan can get through 160 million domains in hours, not days.
The bottleneck isn't your computer's connection limit. It's DNS resolution, TIME_WAIT socket churn, and whether your ISP decides you look like a botnet.