브라우저에서 화면이 뜨기까지

mongBrown·2026년 5월 1일

Enter를 누른 순간, 브라우저는 무엇을 하는가

주소창에 https://www.google.com을 입력하고 Enter를 누른다. 1초도 안 걸려 화면이 뜬다. 그 사이에 무슨 일이 일어날까?


IP 주소를 어떻게 찾는가 — DNS 재귀 조회

브라우저가 제일 먼저 하는 일은 www.google.com에 해당하는 IP 주소를 찾는 것이다. 사람이 읽기 편한 도메인을 컴퓨터가 실제로 통신할 수 있는 IP로 바꾸는 과정이다.

조회는 캐시를 먼저 확인하는 것부터 시작한다. 브라우저 캐시 → OS 캐시 → hosts 파일 순서로 찾고, 없으면 OS에 설정된 DNS 서버(로컬 DNS 리졸버)에 물어본다. 대부분의 환경에서는 공유기 또는 8.8.8.8 같은 외부 리졸버가 이 역할을 한다.

로컬 리졸버도 모른다면 그때부터 계층을 타고 올라간다.

로컬 DNS 리졸버
  → Root DNS:            ".com 담당 서버는 여기야" (IP는 모름, 위치만 알려줌)
  → .com TLD 서버:       "google.com 담당 서버는 여기야"
  → google.com Auth DNS: "142.250.x.x"

Root DNS가 IP를 직접 알지 못하는 이유가 있다. 전 세계 수억 개의 도메인 IP를 하나의 서버가 들고 있는 건 불가능하다. 대신 각 계층의 담당 서버 위치만 알고 있고, 리졸버가 대신 물어보러 다닌다. 이걸 재귀 조회라고 한다.

Root DNS는 논리적으로 13개(A~M)가 있다. 초창기 UDP 패킷 크기 제한 때문에 13개로 고정된 것이고, 실제로는 각 IP 뒤에 수백 대의 물리 서버가 Anycast 방식으로 분산 운영된다. 요청이 들어오면 가장 가까운 서버가 응답한다.


MAC 주소는 왜 매 구간마다 바뀌는가 — ARP

IP를 얻었다. 이제 패킷을 실제로 보내야 하는데, IP만으로는 부족하다. 물리 네트워크 위에서 패킷을 전달하려면 MAC 주소가 필요하다.

www.google.com의 IP는 내 로컬 네트워크 밖에 있다. ARP는 같은 네트워크 안에서만 동작하기 때문에, 내 PC는 Google 서버의 MAC 주소를 직접 알 수 없다. 그래서 내 PC가 ARP로 물어보는 대상은 Google이 아니라 같은 네트워크에 있는 게이트웨이(공유기)다.

ARP 브로드캐스트: "192.168.0.1의 MAC 주소 가진 사람?"
게이트웨이 응답: "나야, AA:BB:CC:DD:EE:FF"

이후 게이트웨이에서 다음 라우터로, 다음 라우터에서 또 다음 라우터로 이동하면서 매 구간마다 다음 홉의 MAC을 ARP로 얻어 전달한다. MAC 주소는 매 구간마다 교체된다. IP는 출발지(내 PC)와 목적지(Google)가 처음부터 끝까지 동일하게 유지된다.

IP가 "어디까지 가야 하는가"를 나타낸다면, MAC은 "지금 이 구간에서 누구에게 전달할 것인가"를 나타낸다.


연결을 맺는 과정 — TCP 3-way handshake

경로도 알았고 패킷 전달 방식도 정해졌다. 이제 연결을 맺는다. TCP는 데이터를 보내기 전에 양쪽이 서로 통신 가능한 상태인지 확인한다. 이게 3-way handshake다.

1. Client → Server: SYN       (연결 요청)
2. Server → Client: SYN + ACK (요청 확인 + 역방향 연결 요청)
3. Client → Server: ACK       (역방향 확인)

2단계에서 ACK만 보내지 않고 SYN도 함께 보내는 이유가 있다. 1단계가 끝나면 서버는 클라이언트가 "보낼 수 있다"는 것을 안다. 하지만 클라이언트는 아직 서버가 "보낼 수 있는지"를 모른다. 서버가 SYN을 함께 보내고, 클라이언트가 ACK로 응답해야 서버도 클라이언트가 "받을 수 있다"는 것을 확인한다. 3번의 교환으로 양방향 통신이 가능한 상태임을 서로 확인하는 것이다.


서버가 진짜인지 어떻게 확인하는가 — TLS 핸드셰이크

https이므로 TCP 연결 직후 TLS 핸드셰이크가 시작된다. 목적은 두 가지다. 내가 연결한 서버가 진짜인지 확인하는 것, 그리고 이후 통신에 쓸 암호화 키를 교환하는 것이다.

서버는 인증서를 보낸다. 인증서에는 서버의 공개키와 CA(인증 기관)가 서명한 값이 들어있다. 비밀키를 보내는 게 아니다. 비밀키는 서버 밖으로 절대 나가지 않는다.

브라우저는 신뢰할 수 있는 CA 목록과 그 공개키를 이미 내장하고 있다. 서버가 보낸 인증서의 서명을 CA 공개키로 검증해서 "신뢰할 수 있는 CA가 발급한 인증서가 맞다"고 확인한다.

인증서가 검증되면 세션키를 교환한다. 클라이언트가 세션키를 만들고, 서버의 공개키로 암호화해서 전송한다. 서버는 자신의 비밀키로 복호화해서 세션키를 얻는다. 이후 통신은 이 대칭키(세션키)로 암호화된다.

비대칭키로 세션키를 전달하고 이후 대칭키로 통신하는 이유는 성능이다. 비대칭키 암호화는 안전하지만 연산이 무겁다. 키 교환 한 번만 비대칭키로 하고, 본 통신은 가벼운 대칭키로 처리하는 것이다.


요청을 보내고 응답을 받는다 — HTTP

TLS가 끝나면 HTTP 요청을 보낸다.

GET / HTTP/1.1
Host: www.google.com

Host 헤더가 필요한 이유가 있다. 하나의 서버 IP에 도메인이 여러 개 올라가는 가상 호스팅이 흔하다. IP만으로는 어느 도메인에 대한 요청인지 서버가 판단할 수 없어서, Host 헤더로 명시한다.

서버는 응답 코드와 HTML을 돌려보낸다. 브라우저는 이걸 받아서 화면을 그리기 시작한다.


HTML이 화면이 되기까지 — 브라우저 렌더링

서버에서 HTML을 받았다. 브라우저는 HTML을 위에서 아래로 파싱하면서 DOM을 만든다. CSS를 만나면 CSSOM을 만든다. 두 트리가 합쳐져 Render Tree가 된다. Render Tree는 화면에 실제로 보이는 요소들만 포함한다. display: none인 요소는 여기서 빠진다.

Render Tree가 만들어지면 Layout 단계에서 각 요소의 위치와 크기를 계산하고, Paint 단계에서 픽셀로 그린다.

여기서 <script> 태그의 위치가 중요해진다. 기본 <script>는 HTML 파싱을 멈추고 JS를 실행한다. JS가 DOM을 조작할 수 있어서, 파싱을 계속 진행하다가 아직 만들어지지 않은 DOM에 접근하는 문제가 생길 수 있기 때문이다. 그래서 JS 파일이 크거나 네트워크가 느리면 화면 자체가 늦게 뜬다.

이를 해결하는 방법이 asyncdefer다.

<script async src="app.js"></script>
<script defer src="app.js"></script>

async는 HTML 파싱과 병렬로 다운로드하고, 완료되는 즉시 실행한다. 파싱이 진행 중이어도 멈추고 실행한다. 여러 스크립트가 있으면 완료 순서대로 실행되므로 순서가 보장되지 않는다.

defer는 마찬가지로 병렬 다운로드하지만, HTML 파싱이 완전히 끝난 뒤에 실행된다. DOMContentLoaded 이벤트가 발생하기 직전에 실행되고, 선언 순서대로 실행이 보장된다. DOM이 완성된 상태에서 실행되므로 document.getElementById 같은 코드가 null을 반환하는 문제가 없다.

실무에서 일반적으로 defer가 안전하다. 순서 보장이 필요 없고 독립적으로 동작하는 스크립트(광고, 분석 도구 등)에는 async가 적합하다.

profile
화이팅!

0개의 댓글