도메인은 어떻게 연결할까?
· updated

프로필 사이트를 하나 만들어보고 싶었습니다. 링크 몇 개 있는 간단한 페이지인데,
github.io 주소 말고 제 이름으로 된 주소를 쓰고 싶었습니다.
그러려면 도메인이 필요합니다. 사는 것까지는 알겠는데 산 다음에 그게 어떻게 제 페이지로
연결되는지를 몰랐습니다. A 레코드니 CNAME 이니 하는 말은 봤는데 뭘 골라야 하는지도
몰랐고요.
알아보면서 몇 가지가 예상과 달랐습니다. 순서대로 적습니다. 나중에 이 블로그를 같은 도메인 아래에 붙일 때는 DNS 가 한 줄로 끝났는데, 왜 그렇게 달랐는지는 마지막에 적겠습니다.
도메인은 사는 게 아니라 빌리는 것이었습니다#
먼저 도메인이 있어야 합니다. 그런데 결제하려고 보니 “산다” 는 말이 정확하지 않았습니다. 연 단위 사용권이고, 갱신하지 않으면 유예기간 뒤에 다른 사람이 등록할 수 있습니다. 자동 갱신을 켜두는 것이 첫 번째 할 일이었습니다.
어디서 살지를 보다가 알게 된 것이 있습니다.
첫해 가격은 기준이 아닙니다. 첫해만 싸게 받고 갱신에서 회수하는 곳이 흔합니다. 계속 쓰는 물건이라 실제로 내는 값은 갱신가입니다.
.me는 몬테네그로 국가 도메인인데 등록 제한이 없고 영어로 “나” 를 뜻해서 개인 사이트에 관행적으로 쓰입니다. 대신.com보다 갱신비가 비쌉니다 — 2026년 7월에 본 값이 연 $16 대였습니다.그리고 등록 후 60일은 다른 곳으로 옮길 수 없다고 보면 됩니다. ICANN 정책이 그 기간의 이전 요청을 파는 곳이 거부할 수 있게 열어뒀습니다.1
그리고 파는 곳(레지스트라)은 판매 창구일 뿐입니다. 어디서 사든 등록되는 원장은 같습니다. 비싼 곳에서 산다고 도메인이 좋아지지 않습니다. 차이는 가격과 UI, 딸려 오는 기능 정도입니다.
그러면 볼 것은 가격이 아니라 딸려 오는 것들입니다. WHOIS 프라이버시부터 확인합니다 — 등록자 정보는 원래 공개 조회 대상이라, 가려주지 않으면 이름과 주소와 전화번호가 열립니다.
Cloudflare 에서 샀습니다. 갱신가가 등록가와 같은 것도 좋았지만 정작 필요했던 것은 DNS 기능 하나였고, 그게 왜 필요했는지가 다음 이야기입니다.
apex 에는 CNAME 을 못 겁니다#
elkiss.me 처럼 앞에 아무것도 안 붙은 이름을 apex 라고 합니다.
GitHub Pages 는 <계정>.github.io 라는 이름으로 서빙합니다.
그러니 내 도메인을 그 이름으로 넘기면 되겠다 싶었습니다. CNAME 이 하는 일입니다.
그런데 apex 에는 안 됩니다.
CNAME 의 정의가 “이 이름에 대한 것은 전부 저쪽에서 가져와라” 이기 때문입니다.
필드 하나를 가리키는 게 아니라 이름 전체를 대체합니다.
그래서 CNAME 이 있는 이름에는 다른 레코드가 함께 있을 수 없습니다.2
그런데 도메인의 맨 위 이름에는 비워둘 수 없는 칸이 있습니다.
“이 도메인은 어느 서버에 물어봐야 하는가”(NS)와 “이 도메인의 기본 정보”(SOA) 둘인데,
이게 없으면 도메인이 도메인 노릇을 못 합니다. 그래서 apex 에는 항상 이 둘이 들어 있습니다.
apex 에 CNAME 을 걸면 → 다른 레코드가 있으면 안 된다
그런데 apex 에는 → 비울 수 없는 칸이 있다
→ 동시에 성립 불가
서브도메인에는 그 칸이 꼭 있어야 하는 건 아니라서 이 제약이 안 걸립니다.
blog.elkiss.me 를 붙일 때 이 절이 통째로 해당 없었던 이유입니다.
설정은 CNAME 인데 응답은 A 로 나갑니다#
우회로가 있습니다. 다만 그리로 가기 전에, 정공법이 왜 모자란지부터 보는 게 빠릅니다.
apex 에 CNAME 을 못 건다면 A 레코드로 IP 를 직접 적으면 됩니다.
GitHub 이 Pages 용 IP 를 문서에 공개해두고 있으니 그대로 넣으면 그날 사이트가 뜹니다.
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153
되기는 하는데, 이 넷은 고정하겠다는 약속이 아닙니다.
A 로 간다는 것은 이 값을 손으로 적어두고 GitHub 이 바꾸면 따라 고치겠다는 뜻입니다.
안 고치면 사이트가 죽습니다. 배포를 한 적도 설정을 건드린 적도 없는데 어느 날
안 열립니다.
A | CNAME | |
|---|---|---|
| 가리키는 것 | IP | 다른 이름 |
| 대상이 바뀌면 | 직접 고쳐야 한다 | 자동으로 따라간다 |
Cloudflare 는 이 사이를 CNAME flattening 으로 메웁니다. 설정 화면에서는 apex 에
CNAME 을 거는 것처럼 보이지만, 질의가 들어오면 Cloudflare 가 대신 대상을 조회해서
나온 IP 를 A 로 응답합니다. 설정은 CNAME 이라 GitHub 이 IP 를 바꿔도 따라가고,
응답은 A 라 표준을 어기지 않습니다.
같은 기능을 다른 업체는 ALIAS 나 ANAME 이라고 부릅니다. 표준이 아니라 업체별 기능이라 부르는 말이 제각각이고, 없는 곳도 있습니다. 도메인을 살 때 DNS 기능 하나를 보고 골랐다고 한 게 이것입니다.
그래서 실제로 넣은 것은 두 줄입니다.
| Type | Name | Target | Proxy |
|---|---|---|---|
CNAME | @ | <계정>.github.io | DNS only |
CNAME | www | <계정>.github.io | DNS only |
@ 는 apex 를 가리키는 표기입니다. www 는 없어도 사이트가 뜨지만 @ 는 필수입니다 —
GitHub 이 검사하는 것은 커스텀 도메인으로 등록한 이름 하나이고, 그게 apex 이기 때문입니다.
www 는 앞에 www. 를 붙여 치는 사람을 위한 줄입니다. 두 이름이 다 잡혀 있으면
GitHub 이 www 쪽을 apex 로 넘겨줍니다.
프록시(주황 구름)는 꺼야 합니다. 켜면 응답 IP 가 Cloudflare 것으로 바뀌어서 GitHub 의 도메인 검사가 통과하지 못합니다. 고른 이유가 된 기능과 켜면 안 되는 스위치가 같은 화면에 있습니다.
넣고 나서 바로 안 뜨더라도 기다릴 일이 아닙니다. 조회부터 해봐야 합니다.
nslookup elkiss.me 8.8.8.8
공용 리졸버를 지정해서 묻습니다. 그냥 nslookup elkiss.me 로 물으면 내 컴퓨터나
공유기가 들고 있는 캐시가 답할 수 있어서, 레코드를 이미 고쳤는데도 옛 답이 나옵니다.
IP 가 나오면 된 것이고, IP 없이 No answer 나 SOA 만 돌아오면
그 이름은 있는데 레코드가 없는 것입니다.
“DNS 전파” 는 무언가 퍼져나가는 게 아니었습니다. 레코드마다 “이 답을 몇 초 동안 다시 써도 된다” 는 시간(
TTL)이 붙습니다. 그동안 통신사나 회사가 돌리는 DNS 서버는 받아둔 답을 그대로 내줍니다. 바뀌었다고 알려주는 장치는 없습니다. 그래서 전파를 기다린다는 것은 그 시간이 세계 곳곳에서 각자 만료되기를 기다린다는 뜻입니다.다만 아직 안 넣은 레코드는 아무리 기다려도 안 생깁니다.
기본 배포는 저장소 루트를 통째로 서빙합니다#
DNS 가 붙으면 Pages 를 켭니다. 여기서 방식이 둘입니다.
기본값인 Deploy from a branch 는 저장소 루트를 통째로 서빙합니다.
따로 올리는 것이 없습니다. 브랜치에 있는 파일이 그대로 주소를 갖습니다 —
index.html 만이 아니라 전부.
이 블로그가 그 방식이었다면 작업 일지(docs/LOG.md)도 blog.elkiss.me 아래에서 열렸을 겁니다.
public 저장소라 유출은 아닙니다. 어차피 GitHub 에서 볼 수 있는 파일입니다.
다만 작업 문서가 개인 도메인에서 서비스되고 검색엔진에 색인될 이유는 없습니다.
하나가 더 붙습니다. 이 방식은 Jekyll 을 항상 돌립니다. 쓰겠다고 한 적이 없는데
기본 동작이 그렇고, 끄려면 저장소에 .nojekyll 이라는 빈 파일을 둬야 합니다.
index.html 이 없으면 Jekyll 이 README.md 를 기본 테마로 렌더링해 인덱스로 세웁니다.
그래서 빈 줄 알았던 주소를 열었더니 README 가 떠 있었습니다.
응답 HTML 에 Jekyll SEO tag 주석이 남아 있어서 알았습니다.

Deploy from a branch | 커스텀 Actions | |
|---|---|---|
| 주소가 생기는 것 | 저장소 루트 전부 | 워크플로가 담은 것만 |
docs/ | 도메인에서 열린다 | 담기지 않는다 |
index.html 이 없으면 | Jekyll 이 README.md 를 그린다 | 해당 없음 |
| 설정 | 클릭 두 번 | 워크플로 파일 하나 |
커스텀 Actions 로 옮겼습니다. 이쪽은 워크플로가 배포할 것만 골라 담아 올리니
docs/ 와 README.md 는 아예 안 실립니다.
CNAME 이 두 군데 나오는데 서로 다른 물건입니다#
기본 방식에서 Pages 에 커스텀 도메인을 저장하면 GitHub 이 저장소 루트에 CNAME 파일을 만듭니다.
그리고 그 커밋을 자기가 합니다.
커밋 f221818
작성자 elkiss87
제목 Create CNAME
제 이름으로 들어와 있습니다. 웹에서 일어난 일이라 그렇습니다.
만들지 않은 커밋이 제 히스토리에 제 이름으로 남았습니다.
로컬은 이걸 모르니 ahead 1, behind 1 로 갈라집니다. git pull --rebase 로 풉니다.
같은 낱말이 두 군데 나와서 헷갈렸는데, 둘은 층이 다른 물건이었습니다.
CNAME 레코드 | CNAME 파일 | |
|---|---|---|
| 어디에 | DNS | 저장소 루트 |
| 하는 일 | 이 이름을 저 이름으로 넘긴다 | 이 저장소가 받을 도메인은 이것이다 |
| 없으면 | 주소가 GitHub 을 못 찾는다 | 기본 방식에서는 GitHub 이 어느 도메인인지 모른다 |
방향이 반대입니다. 레코드는 밖에서 안으로 오는 길이고, 파일은 안에서 “나는 이 이름으로 불린다” 고 말하는 쪽입니다. 둘 다 있어야 연결됩니다.
그런데 커스텀 Actions 로 옮기면 이 파일은 더 이상 읽히지 않습니다.
도메인은 저장소의 Pages 설정에 남고, GitHub 문서도 이 방식에서는 CNAME 파일이
무시되며 필요 없다고 적습니다.3
저도 빼먹으면 도메인이 풀린다고 적어두고 워크플로에 챙겨 담고 있었습니다.
안에서 말하는 자리가 파일에서 설정으로 옮겨갔을 뿐, 둘 다 있어야 하는 것은 그대로입니다.
마지막으로 GitHub 이 Let’s Encrypt 인증서를 발급하면 Enforce HTTPS 를 켭니다. 발급되는 동안은 체크박스가 눌리지 않습니다.
블로그는 서브도메인이라 한 줄로 끝났습니다#
나중에 이 블로그를 붙일 때는 위의 것 중 도메인과 DNS 쪽이 거의 다 필요 없었습니다.
blog.elkiss.me 는 서브도메인입니다. apex 가 아니니 CNAME 을 그냥 걸 수 있고,
flattening 도 A 레코드도 필요 없습니다.
Cloudflare 에 blog → <계정>.github.io 한 줄, DNS only. 그게 전부였습니다.
저장소 이름은 안 들어갑니다. DNS 가 가리키는 것은 계정이고, 그중 어느 저장소가 이 도메인을 받을지는 저장소의 Pages 설정이 정합니다 — 앞에서 본 두 방향 그대로입니다.
돈도 안 듭니다. 사는 것은 elkiss.me 하나뿐이고 그 아래는 무제한입니다.
elkiss.me ← 구매 대상
├─ www.elkiss.me ← 무료
├─ blog.elkiss.me ← 무료
└─ 임의.elkiss.me ← 무료
Pages 는 저장소 하나당 커스텀 도메인 하나라서, 블로그는 별도 저장소로 만들고 서브도메인이 그 저장소를 받았습니다.
돌아보면 elkiss.me 의 @ 줄과 블로그의 blog 줄은 이름 칸 하나만 다릅니다.
둘 다 <계정>.github.io 로 넘기는 CNAME 입니다.
그런데도 처음이 훨씬 길었던 것은, 그 한 줄이 apex 에서는 원래 안 되고
되게 해주는 곳을 골라야 했기 때문입니다.
ICANN Transfer Policy 는 등록일로부터 60일 안에 들어온 이전 요청을 레지스트라가 거부할 수 있는 사유로 열거합니다. 금지가 아니라 거부를 허용하는 형태입니다. 레지스트라를 옮긴 직후에도 같은 60일이 붙습니다. 이것을 30일로 줄이고 의무로 바꾸는 개정안이 2025년 3월 GNSO 를 통과했지만, 이 글을 쓰는 시점에는 아직 시행 전입니다. ↩︎
RFC 1034 와 RFC 2181 이 정하는 제약입니다. RFC 2181 §10.1 은
CNAME옆에 같이 있어도 되는 것을 딱 셋만 열거하는데(전부 보안 확장용입니다),NS와SOA는 그 목록에 없습니다. ↩︎GitHub 문서 Managing a custom domain for your GitHub Pages site 는 브랜치에서 배포할 때만
CNAME파일을 커밋한다고 적습니다. 커스텀 Actions 워크플로로 배포하면 그 파일을 만들지 않고, 이미 있는 것도 무시하며 필요로 하지 않습니다. ↩︎