BlueByte
Connection refusedFixed

Linux: "Connection refused" (ECONNREFUSED) 진단하기

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

안녕하세요, BlueByte입니다. 서비스에 닿아야 할 명령이 곧바로 Connection refused로 되돌아오는 경우가 있습니다 — curl: (7) Failed to connect to localhost port 8080: Connection refused, ssh: connect to host db01 port 22: Connection refused, 코드에서는 ConnectionRefusedError: [Errno 111] Connection refused. 다행히 이것은 빠르고 정직한 실패입니다: 호스트가 응답했고, 다만 거절한 것입니다. 오늘은 거부가 실제로 뜻하는 것, 네 가지 원인 중 어느 것인지 찾는 법, 원인별 해결, 그리고 연결이 완료되는지 확인하는 것까지 하나씩 짚어보겠습니다.

"Connection refused"의 뜻 — 타임아웃이 아니라 거절

거부는 반대편 커널이 "여기엔 아무것도 없다"고 적극적으로 말하는 것입니다. 여러분의 컴퓨터는 포트로 TCP SYN을 보냈는데, 핸드셰이크 대신 원격 커널이 RST로 답한 것입니다 — 아무 프로세스도 듣고 있지 않거나, 방화벽 규칙이 거절을 보냈기 때문입니다. 변형은 모두 같은 뜻입니다:

curl: (7) Failed to connect to localhost port 8080: Connection refused
nc: connect to 10.0.0.5 port 5432 (tcp) failed: Connection refused
ConnectionRefusedError: [Errno 111] Connection refused

curl은 이를 exit code 7이라 부르며, 자체 설명은 Failed to connect() to host or proxy입니다. 중요한 점은 거부가 즉시 일어난다는 것입니다. 응답이 밀리초 안에 돌아왔다면 호스트는 살아 있고 닿는 것이며, 문제는 네트워크가 아니라 포트입니다.

포트가 여러분을 거부하는 네 가지

  • 아무것도 듣고 있지 않음 — 서비스가 멈췄거나 크래시했거나 애초에 시작되지 않아 포트가 닫혀 있습니다.
  • 잘못된 인터페이스에서 듣고 있음 — 서비스가 127.0.0.1에 바인딩되어 로컬 클라이언트에는 답하고 원격은 모두 거부합니다.
  • 포트가 틀림 — 클라이언트는 :8080을 가리키는데 서비스는 :8000에서 듣습니다.
  • 방화벽이 거절 중 — 규칙이 REJECT(능동적인 RST나 ICMP)를 보내며, 이는 아무것도 듣지 않는 것과 똑같아 보입니다.

먼저 실제로 무엇이 듣고 있는지 묻기

클라이언트를 건드리기 전에 서버를 보세요. ss는 소켓을 나열합니다 — -t는 TCP, -l은 listening, -n은 이름 해석 생략, -p는 소유 프로세스:

ss -tlnp | grep 8080
LISTEN 0  511  127.0.0.1:8080  0.0.0.0:*  users:(("gunicorn",pid=812,fd=7))

아무것도 반환하지 않으면 듣는 게 없는 것 — 첫 번째 원인입니다. 한 줄이 나오면 로컬 주소를 유심히 읽으세요; 그게 다음 확인 대상입니다.

바인드 주소 읽기: 127.0.0.1 대 0.0.0.0

포트 앞의 주소가 전부입니다. 127.0.0.1:8080은 서비스가 같은 컴퓨터에서 오는 연결만 받는다는 뜻이라 — 원격 클라이언트, 심지어 다른 인터페이스의 컨테이너도 Connection refused를 받습니다. 0.0.0.0:8080(또는 *:8080)은 모든 인터페이스에서 듣는다는 뜻입니다. 그래서 위 출력에서는 로컬 curl은 되지만 원격에서 이 장비를 치는 동료는 거부됩니다 — 방화벽이 아니라 바인드 주소 때문입니다. 한 가지 함정이 사람을 걸려 넘어뜨립니다: 많은 시스템에서 localhost가 ::1(IPv6)로 먼저 해석되므로, 서비스가 IPv4 127.0.0.1에만 바인딩했다면 ::1로 가는 클라이언트는 거부되고 127.0.0.1은 됩니다. curl -4나 curl -6으로 계열을 강제해 어느 쪽이 빠졌는지 보세요.

curl과 nc로 경로 증명하기

클라이언트 쪽에서 서버가 무엇을 하는지 확인하세요:

curl -v http://db01:8080/ ; echo "exit: $?"
nc -vz db01 8080
*   Trying 10.0.0.5:8080...
* connect to 10.0.0.5 port 8080 failed: Connection refused
exit: 7
nc: connect to db01 (10.0.0.5) port 8080 (tcp) failed: Connection refused

curl -v는 이름을 해석하고 IP에 닿은 뒤 거부됐음을 보여 줍니다 — 즉 DNS는 멀쩡하고 호스트는 살아 있습니다. exit: 7은 타임아웃이 아니라 거부임을 확인해 줍니다. nc -vz는 요청을 보내지 않고 같은 한 줄 판정을 줍니다.

원인별 해결: 시작하거나, 재바인딩하거나, 열기

  1. 듣는 게 없음 — 서비스를 시작하고 올라왔는지 확인:
sudo systemctl start myapp
ss -tlnp | grep 8080
  1. 잘못된 인터페이스 — 서비스의 listen 주소를 127.0.0.1에서 0.0.0.0(또는 특정 인터페이스)으로 바꾸고, 재시작한 뒤 ss로 다시 확인하세요. 소켓이 이제 0.0.0.0:8080으로 보여야 합니다.

  2. 잘못된 포트 — ss가 실제로 보여 주는 포트로 클라이언트를 맞추세요.

  3. 방화벽 거절 — 규칙을 살펴보고 포트를 허용하세요:

sudo nft list ruleset | grep -i reject
sudo iptables -L -n | grep 8080

REJECT 규칙은 닫힌 포트처럼 즉시 거부하고, DROP 규칙은 대신 클라이언트가 타임아웃까지 멈춰 있게 합니다. 그러니 ss가 리스너를 보여 주는데도 클라이언트가 빠르게 거부되면 REJECT 규칙을 콕 집어 찾으세요 — DROP이라면 거부가 아니라 타임아웃으로 나타납니다.

실제 사례: localhost에서만 듣는 앱

한 동료가 10.0.0.5:8080의 웹 앱에 닿지 못하고 곧바로 Connection refused를 받습니다. ping은 되므로 호스트는 살아 있습니다. 서버에서 ss -tlnp | grep 8080은 127.0.0.1:8080을 보여 줍니다 — 앱이 localhost에 바인딩된 것입니다. 설정을 0.0.0.0:8080에서 듣도록 고치고 sudo systemctl restart myapp을 실행하니 ss가 이제 0.0.0.0:8080을 보여 줍니다. 동료의 컴퓨터에서 curl -sS http://10.0.0.5:8080/이 페이지를 돌려줍니다. 네트워크나 방화벽에는 아무 문제가 없었고, 서비스가 그저 자기 자신 말고는 포트를 내주지 않았을 뿐입니다.

연결이 실제로 완료되는지 확인하기

고친 뒤에는 요청이 거부가 아니라 진짜 응답을 받아야 합니다:

curl -sS -o /dev/null -w "%{http_code}\n" http://db01:8080/health
200

상태 코드가 돌아오면 — 404라도 — TCP 연결이 성공했고 무언가 응답했다는 뜻입니다. ss -tlnp가 기대하는 인터페이스에서 서비스를 보여 주는 것과 함께 확인하세요.

재발 방지, 그리고 타임아웃과의 차이

서비스를 실제로 의도한 인터페이스에 바인딩하고, 크래시한 서비스를 사용자가 거부를 만나기 전에 알아채도록 시작 헬스체크를 두고, 클라이언트가 엉뚱한 포트로 흘러가지 않게 각 서비스의 포트를 문서화하세요. 이것은 Connection timed out(curl exit 28)과는 다른 실패입니다: 타임아웃은 침묵입니다 — 패킷이 방화벽 DROP 규칙에 버려졌거나 호스트가 죽어 있어, 클라이언트가 기다리다 결국 포기합니다. 거부는 즉각적인 거절입니다. 그리고 둘 다 Could not resolve host(curl exit 6)와도 다릅니다 — 그것은 TCP를 시도하기도 전에 DNS에서 실패합니다. 다음에 Connection refused를 보면, 서버의 ss부터 시작하세요: 무언가 듣고 있는가, 그리고 어느 주소에서인가.

관련 질문

"Connection refused"와 "Connection timed out"은 어떻게 다른가요?

거부는 즉각적입니다 — 듣는 게 없거나 방화벽이 거절해 호스트가 RST를 보낸 것입니다. 타임아웃은 침묵입니다 — 패킷이 버려져 클라이언트가 기다리다 포기합니다. curl은 거부에 exit 7, 타임아웃에 exit 28을 보고합니다.

ss가 포트를 127.0.0.1에 보여 줍니다. 원격 클라이언트는 왜 연결하지 못하나요?

127.0.0.1은 같은 컴퓨터에서 오는 연결만 받기 때문입니다. 서비스를 0.0.0.0(또는 특정 인터페이스)으로 재바인딩하고 재시작하면 ss가 0.0.0.0:port로 보여야 합니다.

내 포트에 ss가 아무것도 반환하지 않습니다.

듣는 게 없는 것입니다. 서비스가 멈췄거나 크래시했거나 다른 포트에 바인딩된 것입니다. systemctl start로 시작하고 ss를 다시 돌려 기대하는 포트에 올라왔는지 확인하세요.

방화벽 거절과 "아무것도 듣지 않음"이 클라이언트에서 똑같아 보입니다. 어떻게 구분하나요?

서버를 확인하세요. ss가 리스너를 보여 주는데도 클라이언트가 거부되면 REJECT 규칙이 경로에 있는 것 — nft나 iptables를 살펴보세요. ss가 리스너를 보여 주지 않으면 방화벽이 아니라 서비스입니다.

localhost에서 "Connection refused"가 나는데 서비스는 실행 중입니다.

그렇다면 다른 포트이거나 다른 주소 계열입니다. ss -tlnp로 정확한 포트를 확인하고, IPv6와 IPv4를 눈여겨보세요 — ::1에 있는 서비스는 127.0.0.1로 강제된 클라이언트에 답하지 않습니다.

참고 자료

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)