도메인을 서버 IP로 세팅했다는데 왜 브라우저에서는 안 열릴까. DNS가 하는 일과 못 하는 일, 그 사이를 잇는 리버스 프록시, 그리고 이 문제를 프론트 개발자가 떠안게 되는 이유까지 정리했습니다.
프론트엔드 개발자로 일하고 있지만, 사내망 서비스를 다루다 보면 화면 바깥의 일이 자꾸 넘어옵니다. 이번에는 운영 중인 웹 서비스에 도메인을 붙이는 작업이 그랬습니다. 전산 담당자분이 "도메인을 서버 주소로 세팅했다"고 알려주셨는데, 정작 브라우저에 도메인을 치면 접속이 되지 않았습니다.
전달받은 내용은 이랬습니다. app.example.co.kr 을 172.16.10.5:8080 으로 세팅했다는 것이었습니다. 서버 IP에 포트까지 붙어 있길래 처음에는 뭐가 문제인지 감이 안 왔습니다. 세팅을 했다는데 왜 안 되지, 싶었습니다.
파보니까 원인은 코드가 아니라 제가 DNS를 잘못 이해하고 있던 부분에 있었습니다. 그리고 이 문제는 "누구 잘못이냐"가 아니라 "요청이 지나가는 길 중 어느 칸이 비었느냐"의 문제였습니다. 이 글은 그때 정리한 내용입니다.
DNS는 포트를 모른다
제가 처음 놓친 게 이 부분이었습니다. DNS가 하는 일은 딱 하나입니다. 도메인 이름을 물어보면 IP 주소를 알려주는 것입니다.
브라우저: "app.example.co.kr 어디야?"
DNS: "172.16.10.5 야"여기서 끝입니다. 포트는 DNS가 알려줄 수 있는 정보가 아닙니다. "172.16.10.5:8080으로 세팅했다"는 말은 사실 성립할 수 없고, 실제로 세팅된 건 IP까지였던 것입니다.
왜 포트는 못 담을까 - 레코드 타입의 문제
DNS가 부족해서가 아니라, 이름을 IP로 바꾸는 데 쓰는 그릇(레코드) 자체에 포트 칸이 없기 때문입니다. 우리가 도메인에 흔히 다는 레코드는 이런 종류입니다.
| 레코드 | 담는 값 | 예 |
|---|---|---|
| A | IPv4 주소 | app.example.co.kr → 172.16.10.5 |
| AAAA | IPv6 주소 | app.example.co.kr → 2001:db8::5 |
| CNAME | 다른 도메인 이름(별칭) | www → app.example.co.kr |
담당자분이 세팅했다는 건 이 중 A 레코드였고, A 레코드에는 IP를 적는 칸만 있습니다. 포트를 담을 수 있는 SRV 레코드라는 것도 표준에는 존재하지만, 웹 브라우저는 일반 주소창 접속에서 SRV를 조회하지 않습니다. 그래서 "도메인에 포트를 붙여 등록한다"는 개념 자체가 브라우저 접속에서는 성립하지 않습니다.
그러면 포트는 누가 정하나
브라우저가 정합니다. 주소창에 포트를 안 적으면 브라우저는 스킴(scheme)에 따른 기본 포트로 접속합니다. http는 80, https는 443입니다.
사용자 입력: http://app.example.co.kr
브라우저: 172.16.10.5 의 80 포트로 접속 시도
서버: 앱은 8080 포트에서만 듣고 있음
결과: 80 포트에 아무도 없어서 접속 실패그러니까 DNS 세팅은 정상이었고, 80 포트로 들어온 요청을 받아줄 프로그램이 서버에 없던 것이 원인이었습니다. 실제로 http://app.example.co.kr:8080 처럼 포트를 직접 붙여서 접속하면 잘 열렸습니다. 이 확인 방법 하나로 "DNS는 정상, 80 포트 연결만 없음"을 확정할 수 있었습니다. 원인을 절반으로 좁히는 데 가장 빨랐던 한 줄이었습니다.
브라우저가 IP를 받기까지
한 가지 더 파고들면, "브라우저가 DNS에 물어본다"는 말도 실제로는 여러 단계를 압축한 표현이었습니다. 순서는 대략 이렇습니다.
1. 브라우저/OS 캐시 - 최근에 물어본 적 있으면 여기서 끝
2. hosts 파일 - 여기 적혀 있으면 DNS를 아예 안 물어봄
3. 스텁 리졸버 - PC에 설정된 DNS 서버 한 곳에 질의
4. 그 DNS 서버가 대신 여기저기 물어봐서(재귀 질의) 최종 IP를 찾아 응답여기서 두 가지를 알게 됐습니다. 하나는 hosts 파일이 DNS보다 우선이라는 것(그래서 진단할 때 hosts부터 봐야 합니다). 다른 하나는 내 PC는 전 세계 DNS를 뒤지는 게 아니라 설정된 DNS 서버 한 곳에만 물어본다는 것입니다. 이 두 번째 사실이 뒤에 나올 "같은 도메인이 위치마다 다른 곳을 가리키는" 현상의 열쇠였습니다.
또 하나, DNS 응답에는 TTL(Time To Live)이 붙어 있어서 그 시간만큼 캐시됩니다. 레코드를 바꿔도 바로 반영이 안 되고, 이전 값이 TTL이 만료될 때까지 남아 있을 수 있다는 뜻입니다. "분명히 바꿨는데 왜 예전 주소로 가지"의 흔한 범인이 이 캐시입니다.
그래서 리버스 프록시가 필요했습니다
사용자에게 매번 :8080 을 붙여서 접속하라고 할 수는 없었습니다. 80 포트로 온 요청을 8080으로 넘겨주는 중간 안내자가 필요했고, 이 역할을 하는 것이 리버스 프록시입니다. 저는 nginx를 사용했습니다.
server {
listen 80;
server_name app.example.co.kr;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}프록시가 실제로 하는 일 - 연결을 끊고 다시 잇는다
"80을 8080으로 넘긴다"를 처음에는 우편물 전달처럼 상상했는데, 실제 동작은 조금 다릅니다. 리버스 프록시는 요청 패킷을 그대로 흘려보내는 게 아니라, 사용자와의 TCP 연결을 자기 선에서 완전히 끝맺고(종단), 앱을 향해 새 TCP 연결을 따로 하나 맺습니다. 연결이 하나가 아니라 둘입니다.
[사용자] ──① TCP 연결──▶ [nginx:80] ──② 새 TCP 연결──▶ [앱:8080]
응답도 이 길을 거꾸로 되돌아옵니다그래서 앱 입장에서 요청을 보낸 상대는 언제나 nginx(127.0.0.1)입니다. 이대로 두면 앱은 진짜 사용자가 누구인지, 원래 http로 왔는지 https로 왔는지 알 수가 없습니다. 위 설정의 proxy_set_header 들이 하는 일이 바로 이겁니다. 끊기기 전 원래 요청의 정보(진짜 클라이언트 IP, 원래 호스트, 원래 스킴)를 헤더에 적어서 앱에게 넘겨주는 것입니다. 이 헤더가 없으면 앱 로그에 찍히는 접속자가 전부 127.0.0.1 로 보이는, 흔한 함정에 빠집니다.
포워드 프록시와 헷갈리지 않기
프록시라는 말이 하나 더 있어서 정리하고 넘어갑니다. 포워드 프록시는 클라이언트를 감추고, 리버스 프록시는 서버를 감춥니다.
- 포워드 프록시: 회사에서 직원 PC들이 외부로 나갈 때 거치는 관문. 바깥 서버는 진짜 요청자가 누구인지 모릅니다. (클라이언트 쪽 대리)
- 리버스 프록시: 사용자가 서버에 들어올 때 만나는 관문. 사용자는 뒤에 앱이 몇 개 있는지, 몇 번 포트인지 모릅니다. (서버 쪽 대리)
이번에 제가 세운 건 후자입니다. 사용자에게는 "포트 없는 도메인 하나"만 보여주고, 뒤에서 8080으로 연결하는 구조를 숨긴 셈입니다.
server_name은 소유가 아니라 선언
여기서 하나 짚고 넘어가고 싶은 게 있습니다. nginx 설정의 server_name 은 도메인을 소유하거나 예약하는 것이 아닙니다. "이 이름표를 달고 나한테 도착한 요청을 처리하겠다"는 선언일 뿐입니다. 트래픽이 어느 서버로 갈지는 DNS가 정하고, 도착한 트래픽을 어떻게 처리할지는 nginx가 정합니다. 이 역할 분담이 머리에 들어오고 나서야 전체 그림이 이해됐습니다.
실시간 알림이 있다면 한 가지 더
제가 다루던 서비스에는 SSE(Server-Sent Events)로 작업 진행 상태를 실시간으로 보내주는 기능이 있었습니다. nginx를 기본 설정으로만 얹으면 이 기능이 조용히 깨집니다.
nginx는 기본적으로 응답을 모았다가 한꺼번에 전달하고(버퍼링), 일정 시간 조용한 연결은 끊어버립니다(read timeout 기본 60초). SSE는 연결을 계속 열어두고 찔끔찔끔 흘려보내는 방식이라 이 기본값과 상극입니다. 화면도 뜨고 로그인도 되는데 실시간 알림만 안 오는, 원인을 찾기 어려운 증상으로 나타납니다.
이 구조를 그림으로 정리해봤습니다. 위쪽이 기본 설정에서 알림이 막히는 과정이고, 아래쪽이 예외 설정을 넣었을 때입니다.
앱이 보낸 알림 조각들이 nginx 버퍼에 쌓이기만 하고 브라우저로 흘러가지 않으니, nginx 입장에서는 "조용한 연결"이 되어 60초 뒤에 스스로 끊어버리는 구조였습니다.
location /sse {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;
}그래서 SSE 경로에는 버퍼링을 끄고 타임아웃을 길게 잡은 블록을 따로 뒀습니다.
사설 IP라서 생기는 일
하나 더 정리하고 싶은 개념이 있습니다. 위의 서버 주소 172.16.10.5 는 사설 IP입니다.
IP에는 두 종류가 있습니다. 공인 IP는 인터넷 전체에서 유일한 주소라 어디서든 찾아갈 수 있습니다. 사설 IP는 내부망 전용 주소입니다. 건물의 도로명주소와 건물 안의 "3층 회의실" 같은 관계라고 이해했습니다. "3층 회의실"은 그 건물 안에서만 통하고, 다른 건물에도 똑같은 이름의 회의실이 있을 수 있습니다.
전 세계 공통 약속으로 아래 세 대역은 사설 전용입니다.
10.0.0.0 ~ 10.255.255.255
172.16.0.0 ~ 172.31.255.255
192.168.0.0 ~ 192.168.255.255 (집 공유기가 주는 그 주소)사설 IP 서버는 인터넷에서 도달이 불가능하므로, 이 도메인은 태생이 내부망 전용입니다. 그래서 도메인 등록도 공인 DNS가 아니라 회사 내부 DNS에 하게 됩니다.
여기서 재미있는 점이 있는데, 같은 도메인 이름이 위치에 따라 다른 곳을 가리킬 수 있다는 것입니다. 앞서 본 것처럼 브라우저는 자기 PC에 설정된 DNS 서버 하나에만 물어봅니다. 내부망 PC는 내부 DNS에 물어봐서 사설 IP를 받고, 외부에서는 공인 DNS에 물어봐서 다른 답을 받거나 아예 답을 못 받습니다. 이런 구성을 스플릿 호라이즌(split-horizon) DNS라고 부른다는 것도 이번에 알게 됐습니다.
말로만 들으면 헷갈려서 그림으로 정리해봤습니다.
양쪽 PC가 같은 이름을 물었는데, 어느 DNS에게 물었느냐가 답을 갈랐습니다.
내부 DNS에 레코드가 없을 때의 동작도 설정에 따라 갈립니다. 내부 DNS가 상위 공인 DNS에 대신 물어봐주는 설정(포워딩)이면 공인 쪽 답으로 넘어가고, 포워딩이 없거나 인터넷이 차단된 폐쇄망이면 그냥 "사이트를 찾을 수 없습니다"로 끝납니다.
어디서부터 다른 영역인가 - 인프라와 애플리케이션의 경계
이 문제를 겪으며 기술만큼 크게 남은 배움이 하나 더 있습니다. "세팅했다는데 접속이 안 된다"는 상황이, 사실 누구의 잘못이 아니라 요청이 지나는 길이 성격이 다른 두 영역으로 나뉘어 있고 그 경계에 빈 칸이 있던 것이었다는 점입니다.
요청이 사용자에서 앱까지 도달하려면 네 구간을 지납니다.
[사용자 PC] ─①이름 묻기─▶ [내부 DNS] ─②길 통과─▶ [서버의 문(80)] ─③안내─▶ [앱(8080)]| 구간 | 하는 일 | 영역 |
|---|---|---|
| ① DNS | app.example.co.kr = 172.16.10.5 등록 |
인프라 |
| ② 네트워크/방화벽 | 사용자 PC → 서버 80 포트 통행 허용 | 인프라 |
| ③ 80 포트 응대 | 도착한 요청을 앱으로 안내 (리버스 프록시) | 애플리케이션 |
| ④ 앱 | 실제 서비스 (8080) | 애플리케이션 |
제 사례에서 ①②는 정상이었고 ③이 비어 있었습니다. 인프라 쪽은 자기 영역을 다 갖춰뒀고, 정작 막힌 지점은 그 경계 바로 안쪽, 성격이 다른 영역이라 다른 지식이 필요한 자리였습니다.
경계를 가르는 기준은 한 줄로 정리됐습니다. 서버 바깥이냐, 서버 안이냐.
- 서버 바깥 (DNS 레코드, 방화벽 정책, 네트워크 경로) = 인프라 영역. 패킷이 어느 경로로 흐르고 어디서 막히는지를 다루는 지식이 필요합니다.
- 서버 안 (nginx, 앱, 설정 파일, 배포) = 애플리케이션 영역. 도착한 요청을 어떤 프로세스가 받아 어떻게 처리하는지를 다루는 지식이 필요합니다.
두 영역은 필요한 배움의 결이 다릅니다. 같은 "접속"이라는 현상을 두고도, 한쪽은 길과 경로를 보고 다른 한쪽은 그 길 끝에서 돌아가는 프로그램을 봅니다. 그래서 한 영역을 잘 안다고 다른 영역이 저절로 보이지는 않았습니다.
이 구분은 OSI 7계층과도 상당히 겹칩니다. 물리에 가까운 아래층일수록 네트워크·시설을 다루는 인프라 영역, 소프트웨어에 가까운 위층일수록 그 위에서 도는 애플리케이션 영역이라는 상식과 맞아떨어집니다.
다만 계층 번호로 영역을 딱 자를 수 없는 예외도 있었습니다. DNS는 응용 계층(L7)이지만 네트워크 인프라로 다뤄지고, 포트는 전송 계층(L4)이지만 "방화벽에서 80을 여는 것"은 인프라, "80에서 실제로 듣는 프로그램을 띄우는 것"은 애플리케이션으로 한 지점에 두 영역이 맞물립니다. 제가 겪은 문제가 정확히 이 포트 지점, 열려는 있는데 듣는 사람이 없던 자리였습니다. 그래서 계층 번호보다 "서버 바깥이냐 안이냐"가 경계를 보는 데 더 쓸모 있었습니다.
경계의 성격을 알고 나니 협업도 짧아졌습니다. 내가 어느 영역까지 확인했고 어느 영역이 남았는지를 근거로 적으면 한 번에 통했습니다.
- 내 영역을 확인했을 때: "DNS와 네트워크는 정상 확인했고, 80 포트를 받아줄 프록시가 서버에 없어서 생긴 문제입니다. 이건 서버 안쪽이라 제가 설정 추가하겠습니다."
- 다른 영역이 의심될 때: "서버 내부에서는 앱이 정상 응답합니다. 사용자 PC에서 서버 80 포트로 접근이 안 되는 것 같은데, 이 구간은 네트워크 영역이라 확인 한번 부탁드립니다."
프론트 개발자가 왜 이런 것까지 알아야 하나 싶기도 했지만, 화면을 만드는 일도 결국 이 네 구간의 맨 끝에 얹혀 있습니다. 경계를 안다는 건 다른 영역까지 떠맡는다는 게 아니라, 내 영역이 끝나는 지점과 다음 영역이 시작되는 지점을 알아서 "여기까지는 정상, 여기서 끊겼다"를 말할 수 있게 되는 것이었습니다. 그 한 문장이 되는 사람과 "왜인지 모르겠어요"에서 멈추는 사람은 다르게 보입니다.
접속이 안 될 때의 진단 순서
이번 일을 겪고 나서, 도메인 접속 문제는 아래 순서로 확인하면 대부분 갈래를 잡을 수 있다고 정리했습니다. 바깥에서 안쪽으로 한 칸씩 좁혀 들어가는 순서입니다.
cat /etc/hosts # hosts가 DNS보다 우선. 여기 값이 있으면 DNS를 다 무시함
nslookup 도메인 # 이 PC가 받는 IP 확인. 실패 → ① DNS 등록 문제
telnet 서버IP 80 # 응답 없음 → ② 방화벽/네트워크 문제
curl -I http://127.0.0.1/ # 서버 안에서. 이상 → ③ nginx(리버스 프록시) 문제
curl -I http://127.0.0.1:8080/ # 이상 → ④ 앱 문제에러 메시지도 구간을 알려주는 힌트입니다.
- "사이트를 찾을 수 없음" → ① DNS에서 실패
- "응답 시간 초과" → 주소는 받았는데 ② 길이 막힘
도메인:포트로는 열리는데 포트 없이는 안 열림 → ③ 기본 포트(80/443) 리스너 부재, 즉 리버스 프록시가 필요한 상황- 엉뚱한 페이지(nginx 환영 페이지 등)가 뜸 → ③ 프록시 설정은 있으나 라우팅이 안 맞음
마치며
돌이켜보면 "도메인을 IP:포트로 세팅했다"는 말을 그대로 믿고 출발한 것이 헤맨 원인이었습니다. DNS는 이름을 IP로 바꿔줄 뿐이고, 포트는 브라우저의 기본값이 정하고, 그 사이를 잇는 것이 리버스 프록시라는 역할 분담을 알고 나니 접속 문제를 보는 눈이 달라졌습니다.
그리고 이 일은 프론트엔드라는 직군의 경계가 생각보다 흐리다는 것도 알려줬습니다. 화면 안쪽만 붙잡고 있으면 "왜 안 되는지 모르겠다"에서 멈추지만, 요청이 지나가는 길 전체를 구간으로 볼 수 있으면 문제를 어디에 두고 누구와 이야기해야 하는지가 보입니다. 인프라를 대신 하겠다는 게 아니라, 경계에서 대화가 되는 개발자가 되는 것 - 이번에 가장 남는 감각이었습니다.
접속이 안 되면 코드보다 먼저 nslookup을 칩니다. 이름, 주소, 포트 중 어디서 끊겼는지부터 확인하게 됐습니다. 끊긴 지점이 서버 바깥이면 인프라 영역, 안이면 내 영역입니다.