WinHTTP/WinINet 에러 12029(ERROR_INTERNET_CANNOT_CONNECT) 완벽 해결 가이드 — InternetOpenUrl 다운로드 실패 원인 진단법

Prompt Architect 편집팀 · 2026-06-18 · 7분

TL;DR — C/C++에서 InternetOpenUrl로 파일을 받을 때 터지는 에러 12029는 코드가 아니라 네트워크 연결 문제입니다. 서버 방화벽·포트·클라이언트 방화벽·ISP 필터·공유기까지 계층별로 원인을 좁혀가는 실전 진단 절차를 정리했습니다.

들어가며: 코드는 멀쩡한데 다운로드만 실패한다면

윈도우 네이티브 앱(WinINet 또는 WinHTTP API 기반)에서 원격 서버의 파일을 내려받는 자동 업데이터를 만들었다고 해봅시다. 개발 PC에서는 잘 돌아가는데, 고객사에 배포하자마자 특정 환경에서만 다운로드가 막히고 다음과 같은 에러가 떨어집니다.

Error Code: 12029
ERROR_INTERNET_CANNOT_CONNECT

이 에러를 처음 만나면 십중팔구 코드를 뜯어보기 시작합니다. URL 오타? 핸들 누락? 그러나 12029애플리케이션 코드의 버그가 아니라 "네트워크 경로 어딘가에서 TCP 연결 자체가 차단됐다"는 신호입니다. 즉 InternetOpenUrl()이 목적지 서버의 해당 포트로 소켓 연결을 시도했지만 끝내 연결을 맺지 못했다는 뜻이죠. 이 글에서는 이 에러를 코드 레벨이 아니라 네트워크 계층별로 추적해 빠르게 원인을 좁히는 방법을 정리합니다.


1. 에러 12029의 정체

12029는 WinINet/WinHTTP가 정의하는 에러 코드 ERROR_INTERNET_CANNOT_CONNECT입니다. 헤더 wininet.h(또는 winhttp.h의 대응 코드 ERROR_WINHTTP_CANNOT_CONNECT = 12029)에 정의돼 있습니다.

핵심은 이름 그대로 "연결할 수 없음(Cannot Connect)" 입니다. DNS 해석은 성공해서 서버 IP는 알아냈지만, 그 IP의 특정 포트로 TCP 핸드셰이크(SYN → SYN/ACK)를 끝내지 못하고 타임아웃되거나 거부당한 상황입니다. 비유하자면 전화번호는 정확히 눌렀는데 상대가 전화를 안 받거나, 중간 교환국이 통화를 끊어버린 것과 같습니다.

그래서 이 에러는 다음과 구분해야 합니다.

  • 12007 ERROR_INTERNET_NAME_NOT_RESOLVED — DNS 해석 실패(도메인 자체를 못 찾음)
  • 12002 ERROR_INTERNET_TIMEOUT — 연결은 시도했으나 응답 시간 초과
  • 12029 ERROR_INTERNET_CANNOT_CONNECT — 경로상에서 연결이 막힘/거부됨 ← 이번 주제

도메인 이름은 잘 풀리는데 12029가 난다면, 문제는 거의 항상 방화벽·포트·중간 네트워크 장비에 있습니다.


2. 연결을 막는 5가지 대표 원인

실제 운영 환경에서 12029를 유발하는 전형적인 시나리오는 다음 다섯 가지로 압축됩니다.

(1) 서버 측 인바운드 방화벽 차단

파일을 서빙하는 IIS(또는 다른 웹 서버)가 사용하는 포트가 서버 방화벽의 인바운드 허용 규칙에 등록되지 않은 경우입니다. 가장 흔합니다. 서버에 웹 서버를 새로 띄우거나 비표준 포트를 쓰기 시작했는데 방화벽 규칙을 안 열어준 케이스죠.

(2) 포트 불일치

서버 IIS가 바인딩한 포트와 클라이언트가 다운로드를 시도하는 포트가 다른 경우입니다. 예를 들어 서버는 12345 포트로 사이트를 열었는데, 클라이언트 코드의 URL은 :55555로 박혀 있다면 연결이 성립할 수 없습니다. 설정 파일과 실제 바인딩을 교차 확인해야 합니다.

(3) 클라이언트 측 아웃바운드 방화벽 차단

사용자 PC의 방화벽(또는 사내 보안 솔루션)이 아웃바운드 규칙으로 해당 포트로 나가는 트래픽을 막는 경우입니다. 특히 비표준 포트(80/443이 아닌)는 기업 환경에서 기본 차단되는 일이 잦습니다.

(4) HTTP 프로토콜 자체 차단

ISP(통신사)나 웹 필터링/콘텐츠 보안 소프트웨어가 HTTP 평문 통신을 막는 경우입니다. 보안 정책상 HTTP는 막고 HTTPS만 허용하는 환경에서 자주 발생합니다.

(5) 공유기/라우터 레벨 차단

가정·소규모 사무실의 공유기 설정에서 특정 포트, 특정 사이트, 또는 HTTP 트래픽을 차단하도록 잡혀 있는 경우입니다. 자녀 보호 기능이나 방화벽 옵션이 켜져 있을 때 생깁니다.

위 다섯 가지는 실제 테스트로 확인된 대표 패턴이며, 이 외에도 프록시 강제, mTLS 미스매치, MTU 문제 등 추가 원인은 얼마든지 존재합니다. 다섯 가지를 출발점으로 삼되 절대적인 목록으로 여기지 마세요.


3. 계층별 단계별 진단 절차

12029는 "내 앱"이 아니라 "내 앱과 서버 사이의 경로"를 의심해야 합니다. 가까운 곳부터 먼 곳으로 한 칸씩 검증해 나가는 것이 가장 빠릅니다.

1단계 — DNS는 먼저 배제한다

도메인은 풀리는데 연결만 안 되는지 확인합니다.

:: 도메인이 IP로 정상 해석되는지 확인 (해석 실패면 12007 영역이지 12029가 아님)
nslookup files.example.com

2단계 — 목적지 포트에 실제로 닿는지 본다

앱이 쓰는 바로 그 포트로 TCP 연결을 직접 시도해 봅니다. PowerShell의 Test-NetConnection이 가장 직관적입니다.

# 12345 포트로 TCP 핸드셰이크가 성립하는지 점검
# TcpTestSucceeded : True 면 네트워크 경로는 열려 있다는 뜻
Test-NetConnection -ComputerName files.example.com -Port 12345

TcpTestSucceeded : False라면 그 포트는 어딘가에서 막혀 있는 것이고, 곧바로 방화벽/포트 점검으로 넘어갑니다.

3단계 — 서버 측: 바인딩 포트와 인바운드 규칙 확인

서버에 접속해 IIS가 실제로 어떤 포트를 듣고 있는지, 방화벽이 그 포트를 열어줬는지 확인합니다.

# 서버에서: 해당 포트를 LISTENING 중인지 확인
netstat -ano | findstr ":12345"

# 서버에서: 인바운드 방화벽 규칙 추가 (관리자 권한 PowerShell)
New-NetFirewallRule -DisplayName "App File Server 12345" `
  -Direction Inbound -Protocol TCP -LocalPort 12345 -Action Allow

4단계 — 클라이언트 측: 아웃바운드 차단 여부

사용자 PC에서 해당 포트로 나가는 트래픽이 막혀 있지 않은지 확인하고, 필요하면 예외를 추가합니다.

# 클라이언트에서: 아웃바운드 허용 규칙 추가
New-NetFirewallRule -DisplayName "App Downloader Out 12345" `
  -Direction Outbound -Protocol TCP -RemotePort 12345 -Action Allow

5단계 — 중간 경로: ISP·보안 SW·공유기

다른 네트워크(예: 휴대폰 핫스팟)로 바꿔서 같은 다운로드를 시도해 봅니다. 핫스팟에서는 되는데 사무실 네트워크에서만 12029가 난다면 원인은 거의 확실히 ISP/방화벽/공유기 쪽입니다. 가능하면 HTTP 대신 443 포트의 HTTPS로 전환하는 것이 근본 해결책입니다.


4. C/C++ 코드 예제: 에러를 안전하게 잡아내기

진단을 빠르게 하려면 앱 자체가 실패 시 정확한 에러 코드를 로그로 남겨야 합니다. WinINet 기반의 다운로드 코드에서 GetLastError()로 코드를 받아 12029를 명시적으로 처리하는 예시입니다.

#include <windows.h>
#include <wininet.h>
#include <cstdio>
#pragma comment(lib, "wininet.lib")

// 원격 파일을 열어 첫 청크를 읽어보며 연결 가능 여부를 점검하는 함수
bool TryOpenRemoteFile(const wchar_t* fileUrl)
{
    // 세션 핸들 생성 (User-Agent는 식별을 위해 임의로 지정)
    HINTERNET hSession = InternetOpenW(
        L"AppUpdater/1.0", INTERNET_OPEN_TYPE_PRECONFIG,
        nullptr, nullptr, 0);
    if (!hSession) {
        wprintf(L"InternetOpen 실패: %lu\n", GetLastError());
        return false;
    }

    // 대상 URL로 직접 연결 시도 — 여기서 12029가 자주 발생한다
    HINTERNET hUrl = InternetOpenUrlW(
        hSession, fileUrl, nullptr, 0,
        INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0);

    if (!hUrl) {
        DWORD err = GetLastError();   // 실패 원인 코드 확보
        if (err == ERROR_INTERNET_CANNOT_CONNECT /* 12029 */) {
            // 코드 버그가 아니라 네트워크 경로 차단임을 명확히 안내
            wprintf(L"[12029] 서버 포트에 연결할 수 없습니다. "
                    L"방화벽/포트/공유기 설정을 확인하세요.\n");
        } else {
            wprintf(L"InternetOpenUrl 실패: %lu\n", err);
        }
        InternetCloseHandle(hSession);
        return false;
    }

    // 여기까지 왔다면 연결 성공 — 자원 정리
    InternetCloseHandle(hUrl);
    InternetCloseHandle(hSession);
    return true;
}

WinHTTP API를 쓴다면 동일한 상수가 ERROR_WINHTTP_CANNOT_CONNECT(12029)로 정의돼 있어 동일하게 분기 처리하면 됩니다. 핵심은 실패 시 에러 코드를 반드시 로그에 남겨, 사용자/지원팀이 코드가 아니라 네트워크를 보도록 유도하는 것입니다.


5. 흔한 실수와 엣지케이스

  • 80/443만 점검하고 끝낸다: 비표준 포트를 쓰면서 방화벽은 표준 포트만 본다. 실제 URL에 박힌 포트를 정확히 확인하세요.
  • 개발 PC에서만 테스트한다: 개발 환경은 보통 방화벽이 느슨합니다. 반드시 실제 배포 환경(또는 격리된 VM)에서 재현하세요.
  • HTTP 고집: 보안 솔루션이 HTTP를 막는 환경이 늘고 있습니다. 장기적으로는 HTTPS(443)로 전환하는 것이 12029뿐 아니라 (4)·(5) 원인까지 한 번에 줄입니다.
  • 프록시 무시: 사내망은 강제 프록시를 거치는 경우가 많습니다. INTERNET_OPEN_TYPE_PRECONFIG로 시스템 프록시를 따르게 하거나, 프록시를 명시적으로 지정해야 합니다.
  • 타임아웃과 혼동: 응답이 매우 느린 서버는 12002(타임아웃)를 낼 수도 있습니다. 코드별로 메시지를 분리해 두면 원인 추적이 훨씬 빨라집니다.

6. 요약 체크리스트

  1. DNS는 풀리는가? (nslookup) — 풀리면 12029는 연결 문제다.
  2. 그 포트에 TCP가 닿는가? (Test-NetConnection -Port)
  3. 서버가 그 포트를 듣고 있고, 인바운드 방화벽이 열려 있는가?
  4. 클라이언트 PC의 아웃바운드가 막혀 있지 않은가?
  5. ISP/보안 SW/공유기가 HTTP나 해당 포트를 차단하지 않는가? (다른 네트워크로 교차 테스트)
  6. 가능하면 HTTPS(443)로 전환했는가?

12029는 "코드가 아니라 길이 막힌 것"이라는 한 문장만 기억하면, 시간을 코드 디버깅이 아니라 네트워크 진단에 올바르게 쓸 수 있습니다.


AI에게 물어볼 때 (프롬프트 팁)

이런 네트워크/Win32 API 에러는 AI에게 물어볼 때 환경 정보를 구조화해서 한 번에 주는 것이 정확도를 크게 끌어올립니다. Prompt Architect의 분석 기준(맥락·제약·기대 출력 명시)에 맞춘 예시를 소개합니다.

예시 1 — 원인 분기 요청

나는 C++ WinINet의 InternetOpenUrl로 http://files.example.com:12345/app.zip 를
다운로드하는 코드를 작성했어. 개발 PC에선 성공하지만 고객사 PC에서만 GetLastError가
12029(ERROR_INTERNET_CANNOT_CONNECT)를 반환해. DNS는 정상 해석돼.
가능한 원인을 "서버 / 클라이언트 / 중간 경로" 3개 계층으로 나눠 표로 정리하고,
각 원인을 검증할 Windows 명령(PowerShell 우선)을 함께 제시해줘.

예시 2 — 진단 스크립트 생성

대상 호스트와 포트를 인자로 받아, DNS 해석 → TCP 연결 가능 여부 → HTTP 응답 코드를
순서대로 점검하고 어느 단계에서 막혔는지 알려주는 PowerShell 진단 스크립트를 만들어줘.
각 단계마다 한국어 로그를 남기고, 12029에 해당하는 'TCP 연결 실패' 케이스를 명확히 표시해줘.

예시 3 — 코드 견고화

아래 WinINet 다운로드 함수에서 InternetOpenUrl 실패 시 GetLastError 값에 따라
12029/12002/12007을 각각 다른 사용자 메시지로 분기하도록 리팩토링해줘.
재시도 로직(지수 백오프 3회)도 추가하고, 핸들 누수가 없도록 RAII 래퍼로 감싸줘.

이렇게 에러 코드 + 재현 조건 + 원하는 출력 형식을 함께 주면, AI가 일반론 대신 바로 쓸 수 있는 진단 절차와 코드를 돌려줍니다.