BlueByte
10048Fixed

Windows: Only one usage of each socket address (WSAEADDRINUSE 10048)

By Haneul SeoUpdated September 10, 20265 min

Hi, it's BlueByte. You start a service and it dies immediately with Only one usage of each socket address (protocol/network address/port) is normally permitted. Nothing is wrong with your code — another socket already owns the port you asked for, and Windows refuses the second bind. The symptom is a startup failure carrying error 10048 (WSAEADDRINUSE), or the .NET form SocketException (10048). We'll walk through what the message means, why the port is taken, how to find and free it, verify, and keep it from clashing again.

What error 10048 is telling you

10048 is the Winsock error WSAEADDRINUSE, and Microsoft's docs describe it plainly as "Address already in use ... Typically, only one usage of each socket address (protocol/IP address/port) is permitted." It shows up in a few shapes depending on the stack:

System.Net.Sockets.SocketException (10048): Only one usage of each socket
address (protocol/network address/port) is normally permitted.

A native Win32 app fails at the bind() call; a .NET app throws SocketException with ErrorCode 10048; IIS or Kestrel logs the same 10048. The constant across all of them: a socket tried to bind to an IP and port that another socket already holds. Your program is fine — the port isn't free.

Why the port is already taken

The bind fails for one of a few reasons:

  • Another program is already listening on that port — a second copy of your own service, or a different app that grabbed it first.
  • A previous instance is still shutting down. A closed TCP connection sits in TIME_WAIT for a while, and if the old process bound without address reuse, the port stays reserved until that clears.
  • The app opened the port twice — two listeners in one process, or a restart that never released the first socket.
  • The bind is delayed. When a socket binds to a wildcard address (ADDR_ANY), Microsoft notes the 10048 "could be delayed until the specific address is committed" — so the error can surface on connect/listen rather than the bind line.

Find the process holding the port with netstat

Don't guess which program owns the port — ask Windows. netstat -ano lists every connection with its owning PID (-o "includes the process ID (PID) for each connection", -n numeric, -a all listeners):

netstat -ano | findstr :8080
  TCP    0.0.0.0:8080     0.0.0.0:0      LISTENING       17624
  TCP    [::]:8080        [::]:0         LISTENING       17624

LISTENING on :8080 with PID 17624 is your answer. Turn the PID into a name:

tasklist /fi "PID eq 17624"

If you'd rather see the executable directly, netstat -anob adds it — the docs describe -b as "Displays the executable involved in creating each connection or listening port" — but it needs an elevated prompt.

Or ask PowerShell: Get-NetTCPConnection

On current Windows, PowerShell joins the port to the process in one line:

Get-NetTCPConnection -LocalPort 8080 -State Listen |
  Select-Object LocalAddress, LocalPort, OwningProcess,
    @{ n = 'Process'; e = { (Get-Process -Id $_.OwningProcess).ProcessName } }
LocalAddress LocalPort OwningProcess Process
------------ --------- ------------- -------
0.0.0.0           8080         17624 node

That names both the PID and the process without a second lookup, and it works without an elevated prompt.

Fix it: free the port or move off it

Once you know the owner, choose the fix. If it's a stale instance of your own app, stop it:

taskkill /PID 17624 /F

Stop-Process -Id 17624 -Force does the same in PowerShell. Re-running your service now binds cleanly. If the port belongs to something you can't kill — a system service, or a port you don't own — change your app to a free port instead; that's the honest fix, not fighting over a shared one. If the clash only happens on restart because of TIME_WAIT, set SO_REUSEADDR on the listening socket so a new bind can reuse the recently-closed address. Note Windows also has SO_EXCLUSIVEADDRUSE, which does the opposite — it claims a port exclusively — and is what makes another app's bind fail with WSAEACCES instead.

A real case: a dev server that never let go

You stop a Node dev server with the window's close button and restart it — instant 10048 on :3000. Get-NetTCPConnection -LocalPort 3000 -State Listen shows OwningProcess 8840, Process node. The old process was orphaned when the terminal closed, not killed, so it still holds the socket. You run taskkill /PID 8840 /F, restart, and it binds first try. The port was never broken — the previous node still owned it.

Verify the port is free and the app binds

Confirm nothing owns the port, then start the service:

netstat -ano | findstr :8080

Empty output means the port is free. Start your app; it should reach LISTENING with no 10048. Re-run the netstat line and you'll now see your own service's PID on the port instead of the old one.

Keep the port from clashing again

Shut services down cleanly so the socket is released instead of orphaned — a supervised service (a Windows Service, nssm, or a container restart policy) beats closing a terminal window. Run one instance per port; if you need several, give each its own port or put them behind a reverse proxy. And pin your application to a fixed port you control, so a short-lived outbound connection using an ephemeral port can't grab it first.

How 10048 differs from 10013 and Linux EADDRINUSE

WSAEADDRINUSE (10048) means the address is in use. WSAEACCES (10013, "Permission denied") is different: another socket bound the same address with exclusive access via SO_EXCLUSIVEADDRUSE, or you lack permission — no amount of SO_REUSEADDR moves it. And on Linux the same idea is EADDRINUSE ("address already in use"), but you find the owner with ss -ltnp and free it with kill, not netstat -ano and taskkill. Same concept, different tools and a different address-reuse model.

Related questions

I killed the process but the port is still busy for a minute.

The old connection is in TIME_WAIT, a normal TCP wind-down that holds the address briefly. Wait it out, or set SO_REUSEADDR on your listener so a new bind can reuse the recently-closed address immediately.

netstat -ano shows the port owned by PID 4 or PID 0.

PID 4 is the System process — often http.sys reserving the port for an HTTP service like IIS or WinRM; PID 0 is the idle/kernel entry. You can't taskkill those, so stop the owning service or pick a different port for your app.

Do I need administrator rights to find the owning process?

Not for the PID: netstat -ano and Get-NetTCPConnection show OwningProcess without elevation. You only need an elevated prompt for netstat -anob, which resolves the executable name with the -b option.

Should I always use taskkill /F?

/F force-terminates, which is right for an orphaned process holding the port. For your own running service prefer a graceful stop (Stop-Service, or the app's shutdown) so it flushes state; reach for /F only when a clean stop won't release the socket.

Is this the same as Node's EADDRINUSE?

Same root cause — a port already in use. On Windows the underlying code is 10048 / WSAEADDRINUSE, so Node prints EADDRINUSE while the OS reports 10048. Find the owner with netstat -ano or Get-NetTCPConnection and free it the same way.

References

Haneul Seo

Infrastructure engineer · 10+ years running Linux fleets

More in this category

Start request repeated too quicklyFixed

systemd: Start request repeated too quickly

systemd refuses to start a unit that was started more than StartLimitBurst times (default 5) within StartLimitIntervalSec (default 10s), and Restart= counts against that limit. With the 100 ms default RestartSec a crashing service burns all five attempts in under a second. Find the real crash in the journal, fix it, run reset-failed, and give restarts room with RestartSec.

systemd
Too many authentication failuresFixed

SSH: Received disconnect ... Too many authentication failures

Your agent is offering more keys than the server will let you try. Every public key sshd looks at burns one of the MaxAuthTries attempts — six by default, often three on a hardened host — so the right key never gets its turn and the server hangs up before you type anything. IdentitiesOnly=yes with an explicit IdentityFile pins the connection to one key and the attempt count drops to one.

OpenSSH
1722Fixed

Active Directory: replication fails with error 1722, The RPC server is unavailable

RPC reports 1722 (0x6ba, RPC_S_SERVER_UNAVAILABLE) when a lower layer fails to connect, so the real fault is almost never RPC itself — it is DNS, a blocked port, or a host-side setting on one of the two domain controllers. repadmin tells you which partner is failing, dcdiag /test:dns rules out name resolution, and Test-NetConnection plus the dynamic port range settle the firewall question. The most common miss is a rule that allows TCP 135 but not 49152–65535.

Windows Server Active Directory
Windows Server RDSFixed

Windows Server RDS: The remote session was disconnected because there are no Remote Desktop License Servers available to provide a license

The 120-day RD Licensing grace period ended and the session host has no usable license server, so it refuses sessions. GetGracePeriodDays returning DaysLeft 0 and an empty SpecifiedLSList confirm it in seconds. The fix is a real, activated license server with CALs that are new enough for the host — a 2019 CAL cannot serve a 2022 session host — configured through the deployment or the Licensing policies, plus RPC ports open between the two.

Windows Server RDS
0x80070035 / Event ID 31017Workaround

Windows 11: "Your organization's security policies block unauthenticated guest access" when opening a NAS share (0x80070035)

The SMB client on Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro and Windows Server 2019+ refuses guest logons by default, and Windows 11 24H2 Enterprise/Pro/Education also requires SMB signing, which guest sessions can't do. A NAS share that only offers guest access therefore fails with the 'block unauthenticated guest access' dialog, Error code 0x80070035, or System error 3227320323, and Event ID 31017 'Rejected an insecure guest logon' lands in the SmbClient/Security log. The fix Microsoft recommends is a real account on the NAS and signing support in its firmware; Set-SmbClientConfiguration -EnableInsecureGuestLogons $true (plus -RequireSecuritySignature $false on 24H2) is the escape hatch, and it costs you signing and encryption on that client.

Windows (SMB client)
E: Could not get lock /var/lib/dpkg/lock-frontendFixed

Ubuntu/Debian: E: Could not get lock /var/lib/dpkg/lock-frontend — who holds it and how to wait for it

Another package manager, usually Ubuntu's unattended-upgrades fired by a persistent systemd timer at boot, holds the dpkg frontend lock while your apt-get runs. apt-get gives up at once while apt waits because Ubuntu ships binary::apt::DPkg::Lock::Timeout "120" for the apt binary only. Read the PID from the message, let the run finish or pass -o DPkg::Lock::Timeout=<seconds> to apt-get, run dpkg --configure -a only after a genuinely interrupted run, and never delete the lock file: it is an fcntl lock the kernel releases when the holder exits.

APT (Ubuntu/Debian)