Quick answer: Run netstat -ano | find /c "ESTABLISHED" to count open sockets. If it's near 16,384, you've hit the default ephemeral port limit. Increase it with netsh int ipv4 set dynamicport tcp start=10000 num=55000, then reboot the app.
Let me guess — you're running a web server, database, or some custom service on Windows Server 2016/2019 or Windows 10/11, and out of nowhere it starts throwing WSAEMFILE (0X00002728). The app still runs, but new connections just fail. You check the event log and see nothing useful. The culprit here is almost always socket exhaustion — your process has hit the OS limit for open sockets. On Windows, that default limit is tied to the ephemeral port range, which is only 16,384 ports by default (starting from 49152). But here's the thing: it's not just about ports. Each socket consumes kernel memory and handle table slots. If your app opens and closes sockets rapidly without properly closing them, or if you've got a connection leak, you'll hit this wall fast.
I've seen this with IIS servers under heavy load, SQL Server linked server connections, and custom .NET apps that forget to call Dispose() on TcpClient. One real-world trigger: a file sync service that opens a new socket per file transfer but doesn't handle network interruptions gracefully — sockets pile up in TIME_WAIT state.
Step-by-step fix
Identify the process and count its sockets. Open an Administrator PowerShell and run:
netstat -ano | find "PID"where PID is your app's process ID. Count the lines. If it's over 10,000, you're close to the wall. Better yet, run this to get a raw count per PID:
netstat -ano | Select-String "YOUR_PID" | Measure-Object | % { $_.Count }Check the current ephemeral port range. Run:
netsh int ipv4 show dynamicport tcp
You'll see something like Start Port : 49152 Number of Ports : 16384. That's your problem. 16,384 sockets max.Increase the ephemeral port range. This is the real fix. Run these commands as Administrator:
netsh int ipv4 set dynamicport tcp start=10000 num=55000
netsh int ipv6 set dynamicport tcp start=10000 num=55000
Why start at 10000? Leaves room for well-known ports (0-1023) and registered ports (1024-49151). 55,000 ports should handle most scenarios unless you're running something truly massive.Reboot the service or process. The change takes effect immediately for new sockets, but existing ones still hold their old ports. Restart your app so it reconnects fresh. For IIS, run
iisreset. For SQL Server, restart the service.Verify the fix. Re-run
netstat -ano | find /c "ESTABLISHED"after the restart. You should see the count well below the new limit. If it's still climbing fast, you've got a socket leak — see the alternative fixes below.
Alternative fixes if the port range increase didn't work
Kill stuck sockets in TIME_WAIT
Sometimes the app doesn't close sockets properly, and they sit in TIME_WAIT for 4 minutes by default. On Windows Server 2019 and later, you can use netsh int tcp set global timestamps=disabled to reduce TIME_WAIT from 240s to 60s. Or use Set-NetTCPSettings -SettingName InternetCustom -TimeWait MSL 30 in PowerShell. Don't bother with the registry key TcpTimedWaitDelay — it's been deprecated since Windows Server 2012.
Scan for socket leaks
Run netstat -ano | find "TIME_WAIT" | find "YOUR_PID" every 10 seconds. If the count climbs by more than 50 per minute, your app leaks sockets. On the app side, check for unclosed Socket or TcpClient objects. In .NET, wrap them in using blocks. For Java, check ServerSocket and Socket close calls in finally blocks. I once fixed a Node.js app that wasn't calling socket.destroy() on error — the fix was one line.
Increase the max user port limit
If you're on Windows Server 2022 or Windows 11, you can also set Set-NetTCPSetting -SettingName InternetCustom -MaxSynRetransmissions 2 and Set-NetTCPSetting -SettingName InternetCustom -InitialRto 2000. This reduces the number of half-open sockets. But honestly, start with the port range — that's the fix for 9 out of 10 cases.
Prevention tips
- Monitor socket counts proactively. Use Performance Monitor counter
TCPv4\Connections Establishedper process. Alert when it hits 80% of the ephemeral port range. - Implement connection pooling. If your app opens a new socket per operation, pool them. SQL Server connection pooling is built-in — just make sure you're not bypassing it.
- Set a maximum number of concurrent connections in your app's config. For IIS, that's
maxConcurrentRequestsPerCpu. For custom services, hard-code a limit that's 10-20% below the OS limit. - Use HTTP keep-alive where possible. Reduces the TCP handshake overhead and socket churn.
- Test your app under load with tools like Apache JMeter or
wrkbefore going to production. Simulate 10,000 concurrent connections and watch the socket count.
One more thing: if you're running a containerized app on Windows (like Docker Desktop), the limit applies to the container's network namespace. Increase the host's ephemeral port range, not inside the container. That's a gotcha that trips people up.