문제

curl: (7) Failed to connect to example.com port 443 after 2 ms: Connection refused. 네트워크 장애처럼 보이고, 온라인 조언의 절반은 그렇게 취급합니다 — DNS 비우고, 재부팅하고, 인터넷 확인하고. 하지만 종료 코드 7은 “네트워크가 죽었다”보다 훨씬 구체적이고, 문자 그대로 읽으면 한 시간의 추측을 아낍니다. curl이 포기하기 전까지 얼마나 갔는지를 정확히 알려주고, 지도 위 그 지점이 진단 전체입니다.

종료 7이 곧바로 배제하는 한 가지: 이건 DNS 문제가 아닙니다. 그건 아예 다른 코드예요 — (6) Could not resolve host. 7이 났다면 이름은 이미 IP로 해석됐습니다. curl은 어디로 갈지 알았습니다. 도착한 뒤 TCP 연결을 여는 데만 실패한 거죠.

종료 7의 정확한 의미

CURLE_COULDNT_CONNECT(7)는 대상 호스트·포트로의 TCP 3-way 핸드셰이크가 실패했다는 뜻입니다. 전송 계층 위로는 아무것도 안 돌았습니다 — TLS 협상도, HTTP 요청도, 인증서 검사도. curl이 SYN을 보냈고 연결이 올라오지 않았습니다. 그런 일이 벌어지는 이유는 셋뿐이고, 오류의 꼬리 텍스트가 어느 쪽인지 알려줍니다.

거부 vs 타임아웃: 코드 뒤 단어를 읽어라

진단 전체가 걸린 갈림길이고, curl이 공짜로 손에 쥐여줍니다:

“Connection refused” — 호스트는 살아 있고 도달 가능한데 능동적으로 거절한 겁니다. 그 커널이 TCP RST로 답했습니다 — 그 포트에서 아무도 안 듣고 있으니까요. 이건 빠른 실패입니다. 밀리초 안에 돌아옵니다. 거절은 진짜고 즉각적인 답이니까요. 뜻은 이 중 하나:

  • 닿으려는 서비스가 실행 중이 아니거나,
  • 실행 중이지만 다른 포트에 묶여 있거나(또는 127.0.0.1에만 묶여 외부 IP를 거부),
  • 포트 번호가 틀렸거나.

“Connection timed out”(또는 “No route to host”) — SYN이 나갔는데 아무것도 안 돌아왔습니다. 거절도 응답도 없이, curl이 포기할 때까지 침묵. 이건 느린 실패입니다. 몇 초씩 멈춥니다. 전송 계층의 침묵은 방화벽이나 클라우드 시큐리티 그룹이 패킷을 거절이 아니라 버리고 있다는 서명입니다. 버려진 패킷은 뽑힌 케이블과 일부러 구분 불가능합니다 — 패킷 필터링 방화벽이 바로 그렇게 보이도록 설계됐죠. 뜻은:

  • 방화벽 규칙(호스트·네트워크·클라우드 시큐리티 그룹)이 포트를 막고 있거나,
  • 호스트가 정말로 도달 불가(다운, 또는 경로 없음)거나.

응답 시간만으로 단어 하나 읽기 전에 압니다: 거부는 즉시, 타임아웃은 멈춤. 닫힌 문 대 보이지 않는 벽.

종료 코드 참고: 정상적으로 타임아웃된 연결은 여전히 “Connection timed out” 텍스트와 함께 종료 7을 냅니다. 대신 종료 28(CURLE_OPERATION_TIMEDOUT)이 보인다면, 그건 curl 자신의 타임아웃 예산이 만료된 것 — --connect-timeout·--max-time·기본 작업 한도에 걸린 겁니다 — 로 살짝 다른 진술입니다. “OS가 연결 오류를 돌려줬다”가 아니라 “내가 기다리다 포기했다”죠. 진단할 땐 28을 7의 타임아웃 갈래로 취급하세요.

프록시 함정

방화벽을 건드리기 전에 조용한 놈부터 배제하세요. curl은 http_proxy·https_proxy·ALL_PROXY 환경변수를 자동으로 따릅니다 — --proxy 플래그 필요 없이. 셸 프로필·컨테이너 이미지·CI 러너에 남은 낡은 프록시는 curl이 프록시로 연결을 시도하게 만들고, 프록시 주소를 상대로 종료 7이 납니다. 단서는 메시지에 있습니다: 실제 대상이 아니라 Failed to connect to some-proxy.internal port 3128이라고 말합니다.

env | grep -i proxy        # 뭐가 설정됐는지 확인
curl --noproxy '*' https://example.com   # 통째로 우회

--noproxy '*'로 요청이 성공하면, 대상은 내내 멀쩡했고 환경이 curl을 죽은 프록시로 겨누고 있던 겁니다. “브라우저에선 되고 터미널에선 실패”가 거의 항상 서버 문제가 아니라 프록시 설정인 이유죠 — 브라우저와 셸은 같은 프록시 설정을 공유하지 않습니다.

순서대로 진단하기

  1. 먼저 curl -v. 상세 모드는 각 단계를 찍습니다 — Trying <IP>... 다음 connect 결과 — 그래서 어느 IP·포트를 상대로 어디서 죽었는지 정확히 보입니다.
  2. 실패 단어를 읽어라. 거부 → 4단계로. 타임아웃 → 5단계로.
  3. 프록시 확인. env | grep -i proxy; --noproxy '*'로 재시도. 메시지가 프록시 호스트를 지목하면 그게 답입니다.
  4. 거부라면: 서비스가 실제로 실행 중이고 그 인터페이스의 그 포트에 묶여 있는지 확인(ss -tlnp). 포트가 맞는지 확인. 127.0.0.1에만 묶인 서비스는 로컬 아닌 모든 클라이언트를 거부합니다.
  5. 타임아웃이라면: 그 포트의 방화벽·클라우드 시큐리티 그룹을 확인. 호스트 밖에서 포트를 테스트하세요 — 당신 네트워크에서 보면 포트는 도달 가능이거나 필터링이고, 그게 차단이 호스트에 있는지 앞에 있는지 알려줍니다.

DechoNet으로 확인하기

  • 포트 점검은 curl의 대상이 인터넷을 보는 방식대로 바깥에서 포트를 테스트합니다. 열림(뭔가 듣고 있고 도달 가능), 닫힘/거부(도달 가능한 호스트, 포트엔 아무것도), 필터링(침묵 — 방화벽이 패킷을 버림)을 구분합니다 — 위의 거부-vs-타임아웃 갈림길을, 당신의 설정이 틀렸을지 모르는 기계가 아닌 관측점에서 답하는 것이죠.
  • DNS 조회는 호스트 이름이 기대한 IP로 해석되는지 확인합니다 — curl이 그저 해석만 되는 낡거나 틀린 주소로 연결하는 게 아닌지 한 번 볼 값어치가 있습니다.

해결 체크리스트

  • 종료가 6이 아니라 7인지 확인 — 7은 DNS는 이미 됐고 연결이 실패한 것.
  • 코드 뒤 텍스트를 읽기: “refused”(빠름, 포트 닫힘) vs “timed out”(느림, 방화벽 drop).
  • 프록시 배제: env | grep -i proxy 후 curl --noproxy '*'로 재시도.
  • 거부 → 서비스가 맞는 포트·인터페이스에 실행·바인딩됐는지 확인.
  • 타임아웃 → 방화벽·시큐리티 그룹을 확인하고 호스트 밖에서 포트를 테스트.

언제 확대할까

  • 포트가 밖에서는 열림인데 로컬 curl만 실패하면, 차단은 경로의 당신 쪽에 있습니다 — 로컬 방화벽, VPN 분할 터널, 또는 틀린 IP로 보내는 /etc/hosts 오버라이드.
  • 간헐적으로만 거부된다면, 서비스가 죽었다 재시작 중이거나 로드밸런서가 일부 연결을 죽은 백엔드로 보내는 것일 가능성이 큽니다 — 그건 연결 문제가 아니라 포트 뒤 가용성 문제로, 서비스 담당자의 몫입니다.

내 도메인에서 바로 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.