브라우저에 example.com.을 — 끝에 점을 찍어 — 입력해 보세요. 대개는 example.com과 똑같은 페이지가 뜹니다. 그 점은 오타처럼 보입니다. 아닙니다. 저 맨 끝의 점은 도메인 이름을 쓰는 가장 기술적으로 정확한 단 하나의 방식이고, 그게 대개 눈에 보이는 일을 아무것도 안 한다는 사실이 바로, 네트워크 소프트웨어에서 가장 끈질기고 은근한 버그의 원천 하나를 위한 밑밥입니다.
불편한 지점은 이겁니다. DNS는 맨 끝의 점이 무슨 뜻인지 정확히 알고, 1987년부터 알아왔습니다. 그런데 DNS 위에 쌓인 거의 모든 계층 — TLS, HTTP, 쿠키, HSTS — 은 동의하지 않거나 애초에 전달받지 못했습니다. 그 불일치는 학술적인 게 아니에요. 실제 CVE를 낳았고, 2026년에 새것 하나가 또 떨어졌습니다.
그 점이 곧 루트다
도메인 이름이 실제로 무엇인지부터 봅시다. www.example.com은 트리를 지나는 경로이고, 오른쪽에서 왼쪽으로 읽습니다. 루트가 위임한 com 존, 그 밑의 example, 다시 그 밑의 www. 트리에는 꼭대기가 있고, 꼭대기에도 이름이 있습니다 — 다만 그 이름은 아무것도 아닙니다. 빈 문자열. 길이 0의 레이블.
RFC 1034는 1987년 11월에 이렇게 못박았습니다. “완전한 도메인 이름은 루트 레이블로 끝나므로, 인쇄된 형태는 점으로 끝난다.” 루트를 포함한 모든 레이블을 다 적고 점으로 구분하면, 마지막 레이블 — 루트 — 은 비어 있으니, 이름은 뒤에 아무것도 없는 점으로 끝납니다. www.example.com. 맨 끝의 점은 장식이 아닙니다. 눈에 보이게 만든 루트 레이블입니다.
RFC 1035는 두 형태에 이름을 붙입니다. 존 파일을 설명하는 대목에서, 점으로 끝나는 이름은 절대(absolute) 라 “부르며, 완전한 것으로 받아들인다.” 점 없는 이름은 상대(relative) 이고, 무언가가 그걸 완성해야 — 거기서는 존의 원점(origin)을 덧붙여 — 비로소 뜻을 갖습니다. 리졸버도 사람이 입력한 이름에 같은 발상을 적용합니다. 그러니 example.com.은 온전한 진실이고, example.com은 엄밀히는 리졸버가 마저 채워줄 거라 믿고 쓰는 약식 표기입니다.
이 구분은 트래픽이 어디로 가는지가 바뀌는 걸 목격하기 전까진 현학적으로 들립니다.
약식 표기가 무는 지점: resolv.conf
유닉스 머신의 /etc/resolv.conf는 search 목록을 담을 수 있습니다 — 리졸버가 포기하기 전에 상대 이름에 덧붙여 보는 도메인들이죠. search corp.example.com을 걸어 두고 wiki를 물으면, 리졸버는 wiki.corp.example.com을 먼저 시도합니다. 편리합니다.
언제 그럴지를 정하는 손잡이가 ndots이고, 기본값은 1입니다. 규칙은 이렇습니다. 이름에 든 점의 수가 ndots보다 적으면, 이름을 있는 그대로 시도하기 전에 search 목록을 먼저 시도한다. 기본값에서는 맨 wiki(점 0개)에 접미사가 붙고, wiki.internal(점 1개)은 제 이름으로 먼저 시도됩니다.
이제 함정. 맨 끝에 점이 있는 이름은 절대라서 search 목록을 통째로 건너뜁니다 — 리졸버는 적힌 그대로만 조회하고 다른 어디도 안 봅니다. 점이 없는 이름은 접미사 붙이기의 사냥감이 됩니다. 대개는 눈치채지 못해요. 맨 이름이 해석되고 search 목록은 참조될 일이 없으니까요. 하지만 깔끔히 해석되지 않거나 ndots가 크게 올라가 있으면, 그 차이는 질의 한 번과 여러 번의 차이 — 그리고 가끔은 올바른 호스트에 닿느냐 완전히 다른 호스트에 닿느냐의 차이 — 가 됩니다.
쿠버네티스가 이걸 유명하게 만들었습니다. 파드는 기본으로 ndots:5를 달고 나옵니다. 그래서 클러스터 안 이름인 api.prod(점 1개, 5보다 적음) 같은 것도 맨 이름을 시도하기 전에 전체 search 목록이 덧붙어 걸어집니다. 바쁜 파드가 외부로 하는 조회마다 진짜 질의가 나가기 전에 실패한 접미사 질의의 부채살이 됩니다. 표준 해법이 바로 이 글 전체의 주제예요. 절대임을 아는 이름에 맨 끝 점을 찍는 것 — api.example.com. — 그러면 리졸버가 추측을 멈춥니다. “아무것도 안 하는” 점이 측정 가능한 지연 개선책입니다.
DNS 위로 올라가면 합의가 무너진다
그러니 DNS는 명확합니다. 점은 루트이고, 절대 대 상대는 실재하는 구분이고, 끝. 문제는 DNS 위의 어떤 계층도 그걸 지키기로 합의하지 않았고, 저마다 즉흥 처리를 했다는 겁니다.
TLS는 서버가 어떤 인증서를 내밀지 알도록 SNI 확장에 서버 이름을 실어 나릅니다. 명세대로라면 SNI는 맨 끝 점을 포함하면 안 되는 바이트 문자열입니다. 그래서 TLS 계층에서 example.com.과 example.com은 같은 문자열이어야 하고 — 점은 회선에 나가기 전에 제거돼야 합니다. 그런데 누가 제거하는지에 대해 클라이언트마다 의견이 다릅니다. curl은 SNI에서 맨 끝 점을 제거합니다. 주요 브라우저는 입력된 그대로 보냅니다. Go에는 오래 열려 있는 이슈(#63117)가 있습니다. Go의 TLS 서버가 맨 끝 점이 붙은 SNI 이름을 엄격히 거부해서, 그런 이름을 보내는 브라우저는 핸드셰이크부터 실패합니다. 같은 이름, 같은 서버, 그런데 어느 클라이언트를 쓰느냐에 따라 다른 답.
HTTP의 Host 헤더에도 같은 분열이 있습니다. example.com.과 example.com은 하나의 가상 호스트인가 둘인가? 서버와 설정 방식에 따라 다릅니다. 어떤 vhost는 점 붙은 형태를 매칭하고, 어떤 건 404를 냅니다.
쿠키에는 명시적인 규칙 하나와 침묵 하나가 있습니다. RFC 6265는 맨 끝이 점인 Domain 속성은 사용자 에이전트가 아예 무시하라고 말합니다. 하지만 정규화된 호스트의 정의에는 맨 끝 점 얘기가 없어서, example.com의 쿠키가 example.com.에도 속하는지는 구현마다 알아서 정합니다. 그리고 구현이 즉흥으로 정하는 순간, 이건 잡학이길 그만둡니다.
보안을 깨뜨린 그 점
두 계층이 두 표기가 같은 이름인지를 두고 의견이 갈리면, 보안 버그가 생깁니다. 한 표기로 수행한 검사가 다른 표기를 덮지 못하니까요.
curl은 이걸 두 번 배웠습니다. 2022년, CVE-2022-30115 — HSTS 우회. HSTS는 “이 호스트에는 다시는 평문 HTTP를 쓰지 말라”고 말하는 장치입니다. curl은 HSTS 플래그를 정확한 호스트명 아래 저장했습니다. example.com으로 저장해 두고 example.com.을 요청하면, 조회가 빗나갑니다 — curl은 그 “다른” 이름에 HSTS 항목이 있다고 생각하지 않고, 태연히 HTTP로 강등합니다. 또는 점 붙여 저장하고 점 없이 요청하거나. 어느 쪽이든 맨 끝 점이 바로 그 절대적이어야 할 보호를 스르륵 통과했습니다. 이 버그는 curl 7.82.0에서 들어와 7.83.1에서 고쳐졌고, 권고문이 나간 바로 그날 배포됐습니다.
그리고 2026년이 더 고약한 걸 하나 냈습니다. CVE-2026-8924, “트레일링 닷 도메인 슈퍼 쿠키.” 공개 접미사 목록(Public Suffix List)은 사이트가 co.uk나 com에 쿠키를 설정하지 못하게 막는 장치입니다 — 공개 접미사로 스코프된 쿠키는 그 아래 모든 사이트에서 읽힐 수 있고, 그게 바로 PSL이 막으려는 교차 사이트 유출이거든요. PSL을 아는 curl은 Domain=co.uk를 올바르게 거부합니다. 그런데 Domain=co.uk. — 같은 접미사에 점 하나 — 는 수락했습니다. 점 붙은 형태가 검사 대상인 목록의 항목과 매칭되지 않았기 때문이죠. 악성 서버가 공개 접미사로 스코프된 쿠키를 설정하고, curl이 그걸 그 접미사 아래 무관한 제3의 도메인들로 보내게 만들 수 있었습니다. PSL 검사는 실재했고, 맨 끝 점이 그 옆을 걸어서 지나갔습니다. curl 7.46.0부터 8.20.0까지 — 10년이 넘는 릴리스 — 에 영향을 줬고, 2026년 6월 8.21.0에서 고쳐졌습니다. (RFC 6265는 이미 맨 끝 점이 붙은 Domain 속성은 무시하라고 말하고 있었습니다.)
두 버그가 공유하는 모양에 주목하세요. 아무도 망가진 로직을 쓰지 않았습니다. 누군가 올바른 검사 — “이 호스트에 HSTS가 설정됐나,” “이 도메인이 공개 접미사인가” — 를 썼고, 맨 끝 점이 그 검사가 알아보지 못하는 같은 이름의 두 번째 표기를 만들어냈을 뿐입니다. 취약점은 두 계층의 “같음” 정의 사이의 틈에 삽니다.
왜 이게 계속 일어나는가
규칙 하나를 정해 어디서나 강제하면 되지 않느냐고 물을 수 있습니다. 정직한 답은, 우리가 이미 40년째 깊이 들어와 있다는 겁니다. 점=루트는 DNS의 프로토콜 레벨에 박혀 있어 바꿀 수 없습니다. 그 위의 모든 계층은 점을 정규화해 없앨지를 두고 그때그때 합리적인 독립 결정을 내렸고, 그 결정들이 이제는 서로 계속 상호운용해야 하는, 배포된 클라이언트·서버·라이브러리에 얼어붙어 있습니다. TLS 스택과 HTTP 서버와 쿠키 저장소와 HSTS 캐시에 일관성을 한꺼번에 소급 적용할 중앙 권위 같은 건 없습니다.
그래서 맨 끝 점은 시스템에 난 일종의 영구적 이음매로 남습니다 — 대개 보이지 않고, 쓸 줄 알면 가끔은 성능 이득이고, 이따금 이음매 양쪽의 두 부품이 자기가 보는 게 뭔지를 두고 의견이 갈릴 때 보안 구멍입니다. 작은 것입니다. 바로 그래서 계속 놓칩니다.
실용적 결론은 짧습니다. 호스트명을 비교·캐시·보안검사하는 소프트웨어를 짠다면, 어떤 비교보다 먼저 맨 끝 점을 정규화하고, 어디서나 같은 방식으로 하세요 — 하나의 정규 형태를 일관되게 적용하는 것, 그게 해법의 전부입니다. 인프라를 운영하며 조회 지연을 신경 쓴다면, 절대임을 아는 이름에 맨 끝 점을 찍는 건 공짜이고 효과적입니다. 그리고 언젠가 URL에서 example.com.을 보고 실수라고 넘겨짚는다면 — 정반대입니다. 그게 이름 전체를 당신에게 말해 주는 유일한 형태입니다.