배포와 인프라10 분 읽기

리다이렉트 한 줄이 구글에 대한 소유권을 지웠습니다

사이트 7개를 한 경로로 합치고 서브도메인은 301만 남겼습니다. 배포도 검증도 깨끗했는데, 며칠 뒤 Search Console이 '소유권 확인 실패'를 띄웠습니다. 확인 메타태그가 제가 리다이렉트해 버린 그 페이지에 살고 있었습니다.

#gotchas#seo#search-console#dns#verification
개념 도식: 왼쪽은 서브도메인 7개를 한 경로로 합친 정리 작업, 오른쪽은 그 때문에 조용히 깨진 소유권 확인 3건
배포는 성공했고, 배포가 깨뜨린 건 배포 밖에 있었습니다.

정적 콘텐츠 사이트 7개가 서브도메인마다 흩어져 있었습니다. 검색 권위를 한 도메인에 모으려고 전부 example.com/<이름>/ 형태의 서브폴더로 옮기고, 기존 서브도메인에는 301 리다이렉트만 남겼습니다.

배포는 깔끔했습니다. 7개 서브도메인이 전부 정확한 목적지로 301되는 걸 확인했고, 링크·canonical·사이트맵이 새 경로로 재작성된 것도 파일 단위로 검증했습니다.

며칠 뒤 Search Console을 열었더니 이렇게 적혀 있었습니다.

소유권 확인 실패
확인 방법: HTML 태그
실패 이유: 확인 메타 태그를 찾을 수 없습니다.

질문 하나 드립니다. 당신의 배포 검증은 그 배포가 건드리지 않은 것까지 보고 있습니까?

원인은 1초 만에 보였다

Search Console의 HTML 태그 확인은 사이트 **루트 문서의 <head>**에 있는 메타태그를 읽습니다. 제가 그 루트를 301로 바꿨으니, 읽을 문서 자체가 없어진 겁니다.

mindshift.example.com/  →  301  →  example.com/mindshift/

                    메타태그가 여기 살고 있었다

리다이렉트는 의도한 대로 정확히 동작했습니다. 의도하지 않은 건 그 페이지가 다른 역할도 겸하고 있었다는 사실입니다. 저는 그 페이지를 "콘텐츠"로만 보고 있었고, 구글은 "소유권 증명"으로도 보고 있었습니다.

API로 확인해 보니 property 3개가 영향을 받았고, 아직 소유자 권한은 붙어 있지만 재확인이 실패한 상태였습니다. 방치하면 소멸합니다.

1차 수정: 확인 파일만 리다이렉트에서 빼기

다행히 각 서버 디렉터리에 예전 확인용 HTML 파일이 남아 있었습니다. 그래서 .well-known 예외 바로 옆에 한 줄 더 넣었습니다.

RewriteEngine On
RewriteRule ^\.well-known/ - [L]
RewriteRule ^google[a-z0-9]+\.html$ - [L]
RewriteRule ^(.*)$ https://example.com/mindshift/$1 [R=301,L]

이제 확인 파일만 리다이렉트를 통과하지 않고 그대로 서빙됩니다. 7개 서브도메인 전부 200으로 파일이 나오고, 루트 301은 그대로입니다.

그런데 이건 증상 치료입니다. 다음에 구조를 또 바꾸면 또 깨집니다.

근본 수정: 확인을 파일이 아니라 DNS에 두기

Search Console에는 두 종류의 속성이 있습니다. URL 접두어 방식은 사이트가 서빙하는 파일이나 태그로 소유권을 증명합니다. 도메인 방식은 DNS TXT 레코드로 증명합니다.

DNS는 웹 서버 구조와 무관합니다. 리다이렉트를 걸든, 호스팅을 옮기든, 루트를 없애든 소유권은 유지됩니다. 게다가 도메인 속성 하나가 apex와 전 서브도메인을 한 번에 커버합니다.

그래서 TXT 레코드를 넣었습니다. 호스팅이 cPanel이라 API로 바로 됩니다.

cpapi2 ZoneEdit add_zone_record domain=example.com \
  name=example.com. type=TXT \
  txtdata=google-site-verification=...

여기서 함정 하나. apex를 가리킬 때 name=@는 거부됩니다.

Invalid DNS record: Invalid name provided.

name=example.com.처럼 FQDN에 마침표까지 붙여야 통과합니다. 흔한 DNS 표기 습관(@=영역 정점)이 여기선 안 통합니다.

넣은 뒤 공개 리졸버에는 아직 안 보였지만 권위 네임서버에는 즉시 반영돼 있었고, 구글은 권위 서버를 직접 조회하므로 바로 통과했습니다. 확인은 이렇게 합니다.

host -t TXT example.com ns1.your-dns-host.com

뜻밖의 수확

도메인 속성이 확인되자, 그동안 미확인 상태로 방치돼 있던 property들이 전부 소유자로 승격됐습니다. 읽을 수 있는 속성이 25개에서 47개로 늘었습니다. 앞으로 만드는 서브도메인도 소유확인 절차 없이 바로 잡힙니다.

여기서 두 번째 함정이 나왔다

이제 리포트 도구를 도메인 속성으로 갈아타면 끝이라고 생각했습니다. 갈아탔더니 이렇게 나왔습니다.

GSC 리포트 · 최근 28일 · 속성 1개
d:example.com     노출 0    클릭 0

Search Console은 속성을 만든 시점부터만 데이터를 수집합니다. 과거를 소급해 주지 않습니다. 오늘 만든 속성이니 0인 게 맞습니다.

그렇다고 개별 속성과 도메인 속성을 같이 합산하면 같은 트래픽을 두 번 셉니다.

당신이라면? 데이터가 쌓일 때까지 몇 주를 기다리겠습니까, 아니면 사람이 나중에 스위치를 켜기로 하고 넘어가겠습니까?

저는 셋 다 안 골랐습니다. 도구가 스스로 판단하게 했습니다.

mode = "domain"
if totals(query(sc, DOMAIN_PROP, s, e))["impressions"] == 0:
    sites = [개별 속성들]      # 아직 비어 있으면 옛 방식으로
    mode = "fallback"

도메인 속성에 데이터가 없으면 개별 속성 합산으로 폴백하고, 데이터가 생기는 순간 자동으로 갈아탑니다. "나중에 켜기"는 사람이 잊어버리는 대표적인 항목이라, 조건을 코드에 심는 편이 쌉니다.

앱별 비교는 페이지 차원을 호스트로 접어서 유지했습니다. 덤으로 개별 property가 아예 없던 앱들까지 처음으로 집계에 잡혔습니다.

자가진단 3개

  1. 당신이 리다이렉트한 페이지가 겸하던 역할이 있습니까? 소유권 확인, 광고 네트워크 검증, 앱스토어 연결 파일 같은 것들은 조용히 루트에 얹혀 있습니다.
  2. 소유권 증명이 파일에 묶여 있습니까, DNS에 묶여 있습니까? 구조를 바꿀 계획이 있다면 DNS가 안전합니다.
  3. "나중에 스위치 켜기"로 남긴 항목이 있습니까? 조건이 명확하다면 사람이 아니라 코드가 켜게 하세요.

솔직한 부분

이 사고는 배포 검증으로는 못 잡습니다. 제가 확인한 건 전부 "리다이렉트가 의도대로 되는가"였고, 실제로 의도대로 됐습니다. 깨진 건 그 페이지가 겸하고 있던 다른 계약이었고, 그 계약은 제 저장소 어디에도 적혀 있지 않았습니다.

교훈을 굳이 뽑자면, 사이트 루트는 생각보다 많은 것의 앵커라는 겁니다. 옮기기 전에 "이 URL을 누가 또 보고 있나"를 한 번 세어 보는 게 맞았습니다.

같은 날 정리 작업 중 스스로 중복을 만들었던 이야기는 중복을 지우러 갔다가 중복을 하나 더 만들었습니다에, 검증을 넣었다가 그 검증이 오탐을 내던 이야기는 검증을 붙였더니, 그 검증이 거짓말을 했다에 적어 뒀습니다.

지금 리다이렉트 뒤에 숨어 버린 확인 파일이 하나쯤 있을 겁니다. curl 한 번이면 압니다.

관련 글