BlueByte
10048Fixed

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

작성 Haneul Seo2026년 9월 10일 업데이트9 min

안녕하세요, BlueByte입니다. 서비스를 시작하면 곧바로 Only one usage of each socket address (protocol/network address/port) is normally permitted로 죽습니다. 코드에 잘못된 건 없습니다 — 다른 소켓이 이미 그 포트를 쥐고 있어서 Windows가 두 번째 bind를 거부하는 것입니다. 증상은 오류 10048(WSAEADDRINUSE)을 달고 죽는 시작 실패, 또는 .NET 형태인 SocketException (10048)입니다. 오늘은 이 메시지가 뜻하는 것, 포트가 왜 잡혀 있는지, 포트를 찾아 비우는 법, 확인, 그리고 다시 충돌하지 않게 하는 예방까지 하나씩 짚어보겠습니다.

오류 10048이 알려주는 것

10048은 Winsock 오류 WSAEADDRINUSE이고, Microsoft 문서는 이를 "Address already in use ... Typically, only one usage of each socket address (protocol/IP address/port) is permitted."로 담백하게 설명합니다. 스택에 따라 몇 가지 모습으로 나타납니다:

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

네이티브 Win32 앱은 bind() 호출에서 실패하고, .NET 앱은 ErrorCode 10048을 가진 SocketException을 던지며, IIS나 Kestrel도 같은 10048을 로그에 남깁니다. 모두를 관통하는 공통점은 하나입니다 — 어떤 소켓이, 다른 소켓이 이미 쥐고 있는 IP·포트에 bind하려 한 것입니다. 프로그램은 멀쩡합니다. 포트가 비어 있지 않을 뿐입니다.

포트가 이미 잡혀 있는 이유

bind는 다음 중 하나로 실패합니다:

  • 다른 프로그램이 이미 그 포트에서 리스닝 중 — 같은 서비스의 두 번째 사본이거나, 먼저 포트를 낚아챈 다른 앱입니다.
  • 이전 인스턴스가 아직 종료 중. 닫힌 TCP 연결은 한동안 TIME_WAIT에 머무는데, 옛 프로세스가 주소 재사용 없이 bind했다면 그게 정리될 때까지 포트가 예약된 채 남습니다.
  • 앱이 포트를 두 번 열었음 — 한 프로세스 안에 리스너가 둘이거나, 재시작이 첫 소켓을 끝내 놓지 않은 경우입니다.
  • bind가 지연됨. 소켓이 와일드카드 주소(ADDR_ANY)에 bind하면, Microsoft는 10048이 "could be delayed until the specific address is committed"(구체 주소가 확정될 때까지 지연될 수 있음)라고 언급합니다 — 그래서 오류가 bind 줄이 아니라 connect/listen에서 드러날 수 있습니다.

netstat으로 포트를 쥔 프로세스 찾기

어느 프로그램이 포트를 쥐었는지 추측하지 마세요 — Windows에 물어봅니다. netstat -ano는 모든 연결을 소유 PID와 함께 나열합니다(-o "includes the process ID (PID) for each connection", -n 숫자 표기, -a 모든 리스너):

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

:8080에서 LISTENING, PID 17624가 답입니다. PID를 이름으로 바꿉니다:

tasklist /fi "PID eq 17624"

실행 파일을 바로 보고 싶다면 netstat -anob가 그것을 더해 줍니다 — 문서는 -b를 "Displays the executable involved in creating each connection or listening port"로 설명합니다 — 다만 관리자 권한 프롬프트가 필요합니다.

또는 PowerShell에 물어보기: Get-NetTCPConnection

최신 Windows에서는 PowerShell이 포트와 프로세스를 한 줄로 이어 줍니다:

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

두 번 조회할 것 없이 PID와 프로세스를 한 번에 알려주고, 관리자 프롬프트 없이도 동작합니다.

해결: 포트를 비우거나 다른 포트로 옮기기

소유자를 알았으면 해결책을 고릅니다. 자기 앱의 남은 인스턴스라면 멈춥니다:

taskkill /PID 17624 /F

PowerShell에서는 Stop-Process -Id 17624 -Force가 같은 일을 합니다. 이제 서비스를 다시 실행하면 깔끔하게 bind됩니다. 포트가 죽일 수 없는 것 — 시스템 서비스이거나, 내 소유가 아닌 포트 — 에 속한다면, 공유 포트를 두고 다투지 말고 앱을 비어 있는 포트로 옮기는 것이 정직한 해결입니다. 충돌이 TIME_WAIT 때문에 재시작 때만 난다면, 리스닝 소켓에 SO_REUSEADDR를 설정해 새 bind가 방금 닫힌 주소를 재사용하게 하세요. Windows에는 반대로 포트를 배타적으로 차지하는 SO_EXCLUSIVEADDRUSE도 있는데, 이것이 켜져 있으면 다른 앱의 bind가 10048 대신 WSAEACCES로 실패합니다.

실제 사례: 놓아주지 않은 개발 서버

Node 개발 서버를 창의 닫기 버튼으로 끄고 다시 시작하면 :3000에서 즉시 10048이 납니다. Get-NetTCPConnection -LocalPort 3000 -State Listen이 OwningProcess 8840, Process node를 보여줍니다. 터미널이 닫힐 때 옛 프로세스가 죽지 않고 고아가 되어 여전히 소켓을 쥐고 있었던 것입니다. taskkill /PID 8840 /F를 실행하고 다시 시작하면 한 번에 bind됩니다. 포트는 애초에 고장 난 적이 없습니다 — 이전 node가 여전히 쥐고 있었을 뿐입니다.

포트가 비었고 앱이 bind되는지 확인하기

아무도 포트를 쥐고 있지 않은지 확인한 뒤 서비스를 시작합니다:

netstat -ano | findstr :8080

출력이 비어 있으면 포트가 비어 있는 것입니다. 앱을 시작하면 10048 없이 LISTENING에 도달해야 합니다. netstat 줄을 다시 실행하면 이제 옛 것이 아니라 자기 서비스의 PID가 그 포트에 보입니다.

다시 충돌하지 않게 예방하기

소켓이 고아가 되지 않고 해제되도록 서비스를 깔끔하게 종료하세요 — 관리되는 서비스(Windows Service, nssm, 또는 컨테이너 재시작 정책)가 터미널 창을 닫는 것보다 낫습니다. 포트당 인스턴스는 하나로 두세요. 여러 개가 필요하면 각각에 다른 포트를 주거나 리버스 프록시 뒤에 두세요. 그리고 앱을 자신이 통제하는 고정 포트에 묶어, 임시(ephemeral) 포트를 쓰는 짧은 아웃바운드 연결이 먼저 낚아채지 못하게 하세요.

10048이 10013 및 Linux EADDRINUSE와 다른 점

WSAEADDRINUSE(10048)는 주소가 사용 중이라는 뜻입니다. WSAEACCES(10013, "Permission denied")는 다릅니다 — 다른 소켓이 SO_EXCLUSIVEADDRUSE로 같은 주소를 배타적으로 잡았거나 권한이 없는 경우로, SO_REUSEADDR로는 꿈쩍도 하지 않습니다. 그리고 Linux에서 같은 개념은 EADDRINUSE("address already in use")인데, 소유자를 ss -ltnp로 찾고 kill로 비웁니다 — netstat -ano와 taskkill이 아닙니다. 개념은 같지만 도구가 다르고 주소 재사용 모델도 다릅니다.

관련 질문

프로세스를 죽였는데 포트가 1분쯤 계속 사용 중입니다.

옛 연결이 TIME_WAIT 상태입니다. TCP가 정상적으로 마무리되는 과정으로 잠시 주소를 붙들고 있습니다. 기다리거나, 리스너에 SO_REUSEADDR를 설정해 방금 닫힌 주소를 새 bind가 즉시 재사용하게 하세요.

netstat -ano에서 포트를 PID 4나 PID 0이 쥐고 있습니다.

PID 4는 System 프로세스로, 흔히 http.sys가 IIS나 WinRM 같은 HTTP 서비스를 위해 포트를 예약한 것이고, PID 0은 idle/커널 항목입니다. 이들은 taskkill할 수 없으니, 소유한 서비스를 멈추거나 앱에 다른 포트를 고르세요.

포트를 쥔 프로세스를 찾는 데 관리자 권한이 필요한가요?

PID를 보는 데는 필요 없습니다 — netstat -ano와 Get-NetTCPConnection은 권한 없이도 OwningProcess를 보여줍니다. 실행 파일 이름을 -b 옵션으로 풀어주는 netstat -anob에만 관리자 프롬프트가 필요합니다.

항상 taskkill /F를 써야 하나요?

/F는 강제 종료로, 포트를 쥔 고아 프로세스에는 맞습니다. 실행 중인 자기 서비스라면 상태를 비우도록 정상 종료(Stop-Service, 또는 앱의 종료 절차)를 먼저 쓰세요. /F는 정상 종료로 소켓이 안 풀릴 때만 꺼내세요.

이게 Node의 EADDRINUSE와 같은 건가요?

근본 원인이 같습니다 — 이미 사용 중인 포트입니다. Windows에서 밑바탕 코드는 10048 / WSAEADDRINUSE라, Node는 EADDRINUSE를 찍고 OS는 10048을 보고합니다. netstat -ano나 Get-NetTCPConnection으로 소유자를 찾아 같은 방식으로 비우세요.

참고 자료

Haneul Seo

Infrastructure engineer · 10+ years running Linux fleets

같은 카테고리 다른 글

Start request repeated too quicklyFixed

systemd: Start request repeated too quickly

systemd는 StartLimitIntervalSec(기본 10초) 안에 StartLimitBurst(기본 5회)보다 많이 시작된 유닛의 시작을 거부하며, Restart=도 이 제한에 포함됩니다. 기본 RestartSec 100ms로는 크래시하는 서비스가 1초도 안 되어 다섯 번을 소진합니다. 저널에서 진짜 크래시를 찾아 고치고, reset-failed를 실행한 뒤, RestartSec로 재시작에 여유를 주세요.

systemd
Too many authentication failuresFixed

SSH: Received disconnect ... Too many authentication failures

agent가 서버가 허용하는 것보다 많은 키를 내밀고 있습니다. sshd가 들여다보는 공개키 하나하나가 MaxAuthTries 시도를 한 번씩 소모하고 — 기본 6회, 하드닝된 호스트에서는 3회인 경우가 많습니다 — 정작 맞는 키는 차례가 오기 전에 연결이 끊깁니다. IdentitiesOnly=yes와 명시적 IdentityFile로 키 하나를 고정하면 시도 횟수가 1로 떨어집니다.

OpenSSH
1722Fixed

Active Directory: 복제가 error 1722(The RPC server is unavailable)로 실패

RPC는 하위 계층이 연결에 실패했을 때 1722(0x6ba, RPC_S_SERVER_UNAVAILABLE)를 보고합니다. 따라서 진짜 원인은 RPC 자신이 아니라 DNS, 차단된 포트, 두 도메인 컨트롤러 중 한쪽의 호스트 설정입니다. repadmin이 어느 파트너가 실패하는지 알려주고, dcdiag /test:dns가 이름 해석을 배제하며, Test-NetConnection과 동적 포트 범위가 방화벽 문제를 가릅니다. 가장 흔한 실수는 TCP 135만 허용하고 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

120일 RD Licensing 유예 기간이 끝났는데 세션 호스트가 쓸 수 있는 라이선스 서버가 없어 세션을 거부하는 상황입니다. GetGracePeriodDays 의 DaysLeft 가 0 이고 SpecifiedLSList 가 비어 있으면 몇 초 만에 확인됩니다. 해결은 활성화된 라이선스 서버와, 호스트 버전을 감당할 만큼 새 CAL 입니다. 2019 CAL 은 2022 세션 호스트를 서비스하지 못합니다. 여기에 배포 또는 Licensing 정책 설정과 두 서버 사이의 RPC 포트 개방이 따라옵니다.

Windows Server RDS
0x80070035 / Event ID 31017Workaround

Windows 11: NAS 공유 폴더를 열 때 "조직의 보안 정책이 인증되지 않은 게스트 액세스를 차단" (0x80070035)

Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro, Windows Server 2019 이후의 SMB 클라이언트는 기본적으로 게스트 로그온을 거부하고, Windows 11 24H2 Enterprise/Pro/Education 은 게스트 세션이 할 수 없는 SMB 서명까지 요구합니다. 그래서 게스트 접근만 제공하는 NAS 공유는 'block unauthenticated guest access' 대화상자, Error code 0x80070035, 또는 System error 3227320323 으로 실패하고, SmbClient/Security 로그에 Event ID 31017 'Rejected an insecure guest logon' 이 남습니다. Microsoft 가 권하는 해결은 NAS 에 실제 계정을 만들고 펌웨어에서 서명을 지원하게 하는 것입니다. Set-SmbClientConfiguration -EnableInsecureGuestLogons $true(24H2 는 -RequireSecuritySignature $false 까지)는 비상구이며, 그 클라이언트의 서명과 암호화를 포기하는 대가가 있습니다.

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 — 누가 쥐고 있고 어떻게 기다리는지

다른 패키지 관리자, 대개 부팅 때 persistent systemd 타이머로 발화한 Ubuntu 의 unattended-upgrades 가 여러분의 apt-get 이 도는 동안 dpkg 프런트엔드 락을 쥐고 있는 것입니다. Ubuntu 는 apt 바이너리에만 binary::apt::DPkg::Lock::Timeout "120" 을 실어 두어서 apt-get 은 즉시 포기하고 apt 는 기다립니다. 메시지의 PID 를 읽고, 실행이 끝나게 두거나 apt-get 에 -o DPkg::Lock::Timeout=<초> 를 주고, dpkg --configure -a 는 실제로 중단된 실행 뒤에만 쓰고, 락 파일은 절대 지우지 마세요. 보유자가 종료하면 커널이 푸는 fcntl 락입니다.

APT (Ubuntu/Debian)