birchholt 직접 해보고 남기는 기록

Cloudflare Pages에 www를 붙였더니 522가 났습니다 — DNS만으로는 안 됩니다

이 사이트 주소는 birchholt.com입니다. 앞에 www를 붙여서 쳐보면 아무것도 안 나왔습니다. 에러 화면도 아니고 브라우저가 그런 사이트를 못 찾겠다고만 합니다. www를 습관처럼 붙여 치는 사람은 생각보다 많아서 이 상태로 두면 그 사람들은 그냥 돌아갑니다.

문패는 걸려 있는데 바로 옆 쪽문에는 손잡이조차 없는 돌담 집 앞에서 망설이는 방문객

고치는 방법 자체는 짧습니다. 그런데 중간에 한 단계를 빼먹으면 522가 납니다. DNS 레코드만 추가한 상태, Pages에 도메인까지 등록한 상태, 그리고 리다이렉트를 거는 단계를 하나씩 나눠서 재봤습니다. 측정은 2026-10-04 새벽에, 리다이렉트는 같은 날 밤에 했습니다.

지금 www는 어떤 상태였나

지도에는 길이 그려져 있는데 실제로는 풀밭만 펼쳐진 자리에 서서 지도를 뒤집어 보는 산책자

“응답이 없다"는 말은 여러 가지를 뜻할 수 있어서 먼저 그것부터 갈랐습니다. 서버가 대답을 안 하는 건지, 아예 주소를 못 찾는 건지요.

$ dig www.birchholt.com @1.1.1.1
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN
;; AUTHORITY SECTION:
birchholt.com.  1800  IN  SOA  colette.ns.cloudflare.com. dns.cloudflare.com. ...

$ curl -sSI https://www.birchholt.com/
curl: (6) Could not resolve host: www.birchholt.com

NXDOMAIN은 DNS(도메인 이름을 서버 주소로 바꿔주는 체계)가 돌려주는 **“그런 이름 없음”**입니다. curl의 종료 코드 6도 같은 뜻으로, 이름을 주소로 못 바꿨다는 겁니다. 서버까지 가보지도 못한 상태였습니다.

Cloudflare API로 DNS 레코드 목록을 받아 보니 이유가 단순했습니다. www 레코드가 아예 없었습니다. 웹사이트용으로는 apex(www 같은 앞머리가 없는 맨 도메인, 여기서는 birchholt.com)에 걸린 CNAME 하나뿐이었습니다. Pages 프로젝트의 커스텀 도메인 목록에도 birchholt.com 하나뿐이었습니다.

DNS에 CNAME만 추가하면 522가 납니다

전화선은 이어 놓았는데 교환대에 번호가 등록되지 않아 신호음만 짧게 끊기는 수화기를 든 사람

가장 먼저 떠오르는 방법은 apex와 똑같이 www도 Pages 주소로 보내는 겁니다. 그래서 DNS에만 레코드를 하나 추가했습니다. Pages 쪽은 건드리지 않았습니다.

CNAME  www  →  birchholt.pages.dev   (프록시 켬)

CNAME은 “이 이름은 저 이름과 같다"고 적는 별칭 레코드입니다. 프록시를 켰다는 건 요청이 Cloudflare 서버를 한 번 거쳐 간다는 뜻입니다.

레코드는 바로 퍼졌고 결과는 이랬습니다.

$ curl -sI https://www.birchholt.com/
HTTP/2 522
content-type: text/plain; charset=UTF-8
content-length: 16
server: cloudflare

$ curl -s https://www.birchholt.com/
error code: 522

522가 났습니다. 522는 Cloudflare가 뒤쪽 서버(origin)와 연결하지 못했을 때 돌려주는 번호입니다. 문서 제목부터 「connection timed out」이고 TCP 연결 응답(SYN+ACK)을 19초 안에 못 받거나, 연결 뒤 보낸 요청의 확인(ACK)을 90초 안에 못 받을 때 난다고 적혀 있습니다. 그런데 이번에는 세 번 찍어서 0.23초, 0.17초, 0.16초 만에 돌아왔습니다. 시간초과를 기다린 끝에 나온 522로 보기는 어렵습니다. 제 해석으로는 연결할 곳이 없다고 곧바로 판정한 쪽에 가깝습니다. 글 주소를 찍어도, http://로 찍어도 똑같이 522였습니다.

Cloudflare Pages 문서에 이 상황이 그대로 적혀 있습니다.

「Manually adding a custom CNAME record pointing to your Cloudflare Pages site - without first associating the domain (or subdomains) in the Cloudflare Pages dashboard - will result in your domain failing to resolve at the CNAME record address, and display a 522 error.」 — Cloudflare Pages · Custom domains, 2026-10-04에 확인했습니다. 19초·90초 기준과 함께 522 문서에도 Pages를 쓴다면 커스텀 도메인을 등록했는지 확인하라는 항목이 따로 있습니다 — Error 522.

DNS가 birchholt.pages.dev를 가리킨다고 Pages가 그 이름을 받아주지는 않습니다. Pages는 자기 프로젝트에 등록된 이름만 받습니다. DNS는 길을 알려줄 뿐이고, 문을 열어줄지는 Pages가 정합니다.

Pages에 도메인을 등록하면 2분 안에 열립니다

새로 단 쪽문 옆에서 열쇠공이 자물쇠를 조이는 동안 이미 문이 살짝 열려 손님이 들어서는 장면

대시보드에서는 Workers & Pages → 프로젝트 → Custom domains → Set up a domain입니다. 저는 같은 일을 API로 했습니다.

curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/birchholt/domains" \
  --data '{"name":"www.birchholt.com"}'

처음 응답의 상태는 initializing이었습니다. 인증서 발급처는 google, 검증 방식은 http로 나왔습니다. 그다음 15초마다 상태와 www 응답을 같이 찍었습니다.

시각(KST)  상태      소유 확인   인증서 검증   www 응답
01:17:45   pending   active     pending      200
01:18:01   pending   active     pending      200
01:18:33   pending   active     pending      200
01:19:04   pending   active     pending      200
01:19:20   active    active     active       200

01:18:17, 01:18:48 줄도 같은 값이라 생략했습니다. 등록 요청은 01:17:39에 보냈습니다.

두 가지가 눈에 띄었습니다.

하나, 상태가 active가 되기까지 약 1분 41초 걸렸습니다. DNS 레코드가 이미 있었고 같은 존이라 따로 레코드를 추가할 일이 없었습니다. 등록 후 DNS 목록을 다시 봤는데, Pages가 새 레코드를 만들지 않았고 제가 넣은 CNAME 하나 그대로였습니다.

둘, 상태가 pending인 동안에도 www는 이미 200이었습니다. 등록하고 6초 뒤 첫 측정부터 열렸습니다. API 상태는 대기 중이었지만 이번에는 프록시가 켜진 레코드로 들어온 요청이 이미 페이지를 받고 있었습니다. “아직 pending이니 괜찮겠지” 하고 다음 단계를 미루면, 적어도 이번처럼 그 사이에 www가 열린 채로 노출될 수 있습니다.

활성 후에 몇 가지를 더 찍었습니다.

https://www.birchholt.com/                      → 200
https://www.birchholt.com/__nope__/             → 404
https://www.birchholt.com/posts/soft-404-on-pages → 308  location: /posts/soft-404-on-pages/
http://www.birchholt.com/                       → 301  Location: https://www.birchholt.com/
인증서: CN=www.birchholt.com, 발급자 Google Trust Services

없는 주소는 404, 끝 슬래시가 빠진 주소는 308로 정리됩니다. 같은 Pages 프로젝트가 그대로 응답하고 있다는 뜻입니다. http://는 https://로 넘어가는데, 넘어가는 곳이 apex가 아니라 www입니다. 여기서 다음 문제가 보입니다.

내 컴퓨터에서만 안 열리는 www

이웃들은 새로 생긴 다리를 건너는데 혼자 낡은 수첩 속 옛 지도를 보며 강가에 서 있는 사람

위 측정을 하는 동안 제 컴퓨터에서는 www가 계속 안 열렸습니다.

$ curl -sSI https://www.birchholt.com/
curl: (6) Could not resolve host: www.birchholt.com

$ dig www.birchholt.com @1.1.1.1 +noall +answer
www.birchholt.com.  300  IN  A  172.67.202.244
www.birchholt.com.  300  IN  A  104.21.22.53

공용 DNS(1.1.1.1, 8.8.8.8)는 이미 주소를 돌려주는데 제 컴퓨터만 이름을 못 찾습니다. 레코드를 만들고 3분 45초가 지나서도 그랬습니다.

맨 처음 상태를 확인하려고 www를 찍었을 때 받은 답이 NXDOMAIN이었습니다. 그 응답에 붙은 SOA(그 도메인의 DNS 기본 정보 레코드)의 마지막 값이 1800, 즉 30분입니다. “없음"이라는 답도 캐시됩니다. 제 컴퓨터가 그 “없음"을 들고 있는 것으로 보입니다. macOS 리졸버 안쪽을 직접 들여다본 건 아니라서 추정입니다.

그래서 이 글의 www 측정은 전부 IP를 고정해서 했습니다. 위 출력들에서는 이 옵션을 줄여 적었습니다.

curl -sI --resolve www.birchholt.com:443:104.21.22.53 https://www.birchholt.com/

--resolve는 이 이름은 이 IP로 가라고 curl에게 직접 알려주는 옵션입니다. DNS 캐시를 건너뛰므로 “내 컴퓨터만 안 되는 건지"를 바로 가를 수 있습니다. 설정을 막 바꿨는데 안 열리면, 설정을 다시 뒤집기 전에 이것부터 찍어보시길 권합니다. 되돌리고 다시 거는 사이에 진짜로 깨진 상태를 만들 수 있습니다.

열린 www는 apex의 사본입니다

똑같은 그림 두 장이 나란히 걸려 있고 한쪽 액자 아래에만 원본을 가리키는 작은 화살표 카드가 붙은 화랑 벽

www가 열렸으니 끝일까요. 받은 내용을 apex와 비교했습니다.

$ curl -s https://www.birchholt.com/ | md5
8d1b8e4ddf12070863f2ab8e397db6c2
$ curl -s https://birchholt.com/ | md5
8d1b8e4ddf12070863f2ab8e397db6c2

바이트 하나 다르지 않습니다. 같은 Pages 프로젝트가 같은 파일을 내주니 당연합니다. 검색엔진 입장에서는 주소 두 개에 같은 페이지가 있는 셈입니다.

다행히 canonical(이 주소가 원본이라고 검색엔진에 알려주는 표시)은 apex를 가리키고 있었습니다.

www  /                          → <link rel=canonical href=https://birchholt.com/>
www  /posts/soft-404-on-pages/  → <link rel=canonical href=https://birchholt.com/posts/soft-404-on-pages/>

이 값은 요청한 주소에서 오는 게 아니라 빌드할 때 박힙니다. 이 사이트의 Hugo 템플릿은 canonical에 .Permalink(페이지의 전체 주소)를 쓰고 그 앞부분은 설정의 baseURL에서 옵니다. 저장소를 복사해 두 번 빌드해 보면 바로 보입니다.

$ hugo                                  # baseURL = 'https://birchholt.com/'
<link rel="canonical" href="https://birchholt.com/">

$ hugo -b https://www.birchholt.com/
<link rel="canonical" href="https://www.birchholt.com/">

2026-10-04, hugo v0.165.0+extended. 저장소 복사본이라 .git이 없어 --enableGitInfo=false를 붙여 빌드했습니다. -b로 바꾼 빌드에서는 sitemap 안의 주소도 전부 www로 바뀌었습니다.

baseURL을 apex로 적어뒀다면 www로 열려도 canonical은 apex를 가리킵니다. 그래도 이것만으로 두지는 않으려 합니다. Google 문서는 정본을 알리는 방법으로 리다이렉트와 canonical을 둘 다 강한 신호로 꼽고 여러 방법을 겹치면 더 효과적이라고 적고 있습니다. 사람에게도 주소창에 www가 남아 있는 것보다 apex로 넘어가는 편이 헷갈리지 않습니다.

「Redirects: A strong signal that the target of the redirect should become canonical. rel=“canonical” link annotations: A strong signal that the specified URL should become canonical.」, 「these methods can stack and thus become more effective when combined」 — Google 검색 센터 · Consolidate duplicate URLs, 2026-10-04에 확인했습니다.

마지막 한 단계는 Pages 밖에서 합니다

정원 입구 옆 작은 쪽문에 큰 대문 쪽을 가리키는 나무 팻말을 박아 넣는 정원사

처음엔 Pages의 _redirects 파일(빌드 결과물에 넣는 리다이렉트 규칙 목록)로 하면 되겠다고 생각했습니다. 안 됩니다. 문서의 지원 표에서 도메인 단위 리다이렉트가 빠져 있습니다.

지원 표에 「Domain-level redirects ❌」로 적혀 있습니다 — Cloudflare Pages · Redirects, 2026-10-04 확인.

그래서 Pages 앞단, Cloudflare의 규칙으로 걸었습니다. 대시보드 Rules → Overview → Rule templates에 **「Redirect from WWW to root」**라는 템플릿이 있습니다. Redirect Rules(그중 Single Redirect, 조건에 맞는 요청 하나하나를 다른 주소로 보내는 규칙)를 미리 채워 주는 틀입니다. 이걸 골라 규칙을 만들었습니다.

배포 버튼을 누르기 직전에 경고가 하나 떴습니다.

This rule may not apply to your traffic
Your DNS configuration may not be proxying traffic for www

www가 프록시를 안 거친다는 말인데, API로 레코드를 다시 보니 www는 birchholt.pages.dev를 가리키는 CNAME이고 프록시도 켜져 있었습니다. 그래서 「Ignore and deploy rule anyway」로 그냥 배포했습니다. 경고가 왜 떴는지는 확인하지 못했습니다. 결과는 아래처럼 바로 적용됐습니다.

$ curl -sI https://www.birchholt.com/
HTTP/2 301
location: https://birchholt.com/

$ curl -sI "https://www.birchholt.com/posts/soft-404-on-pages/?a=1"
HTTP/2 301
location: https://birchholt.com/posts/soft-404-on-pages/?a=1

$ curl -sI https://birchholt.com/
HTTP/2 200

2026-10-04 22:59 KST, www의 IP 두 개 각각에서 같은 결과였습니다. 앞에서처럼 --resolve로 IP를 고정했고 명령에서는 뺐습니다. 이 무렵에는 제 컴퓨터도 www를 찾았고, 고정 없이 찍어도 같은 301이 나왔습니다.

경로와 쿼리 문자열이 그대로 apex로 넘어갑니다. 301을 따라가면 apex에서 200으로 끝납니다. 앞 절에서 200으로 열리던 사본은 이제 안 나옵니다. 없는 주소(/__nope__/)도 404 대신 301로 apex에 넘겨지고 apex에서 404가 납니다. 받은 본문도 Pages 페이지가 아니라 Cloudflare가 만든 「301 Moved Permanently」 짧은 HTML이었습니다. www를 Pages에 등록해 둔 상태 그대로인데도 규칙이 Pages보다 먼저 응답했습니다.

http://로 들어오면 두 번 넘어갑니다.

$ curl -sIL http://www.birchholt.com/
HTTP/1.1 301 Moved Permanently
Location: https://www.birchholt.com/
HTTP/2 301
location: https://birchholt.com/
HTTP/2 200

HTTPS로 먼저 올리고, 그다음에 apex로 보냅니다. Pages 등록 직후에 본 http:// → https://www 처리가 규칙보다 먼저 일어나는 것으로 보입니다. 끝 슬래시가 빠진 주소도 두 번입니다. www에서 301로 apex에 간 뒤, apex의 Pages가 308로 슬래시를 붙입니다. 도착지는 맞으니 이번에는 그대로 뒀습니다.

Pages 문서가 권하는 방법은 따로 있습니다. Bulk Redirects(계정 단위로 주소→주소 목록을 거는 기능)에 www.birchholt.com → https://birchholt.com, 301을 넣고 경로·쿼리 보존 옵션을 켠 뒤, www DNS를 A www 192.0.2.1(프록시 켬)로 두는 방식입니다. 주소를 여러 개 한꺼번에 옮길 일이 있다면 그쪽이 맞을 수 있습니다. 저는 써보지 않았습니다 — Cloudflare Pages · Redirecting www to domain apex, 2026-10-04 확인.

순서만 다시 적어두면, DNS 레코드만으로는 522, Pages에 등록하면 사본, 리다이렉트까지 걸어야 apex 하나로 모입니다. 세 단계 중 어디에 서 있는지는 --resolve를 붙인 curl 한 줄이면 알 수 있습니다.