루트 도메인 항구에서 승인된 광고 판매자만 서버 등대로 안내하는 흰색 유령 등대지기

Ghost ads.txt 404 해결: Nginx Proxy Manager로 루트 파일 제공하기

블로그 운영 Ghost 2026년 8월 28일

Ghost를 Docker로 운영하면서 Google AdSense를 연결하면 ads.txt를 찾을 수 없음 경고를 만날 수 있다. 브라우저에서 다음 주소를 열었을 때 Ghost의 404 페이지가 나타나는 경우다.

https://example.com/ads.txt

핵심은 Ghost 글이나 테마 안에 임의의 ads.txt를 넣는 것이 아니라, 루트 도메인의 정확한 /ads.txt 요청이 일반 텍스트와 HTTP 200으로 응답하도록 앞단 웹 서버에서 경로를 제공하는 것이다.

이 글은 Docker 기반 Ghost 앞에 Nginx Proxy Manager를 둔 셀프호스팅 구성을 기준으로 한다. 2026년 8월 18일에는 실제 사이트의 /ads.txt가 404였고, 8월 28일에는 200 OK, Content-Type: text/plain과 올바른 AdSense 게시자 행을 공개 경로에서 확인했다.

발행 전 확인: 현재 NPM의 실제 Advanced 설정 원문과 백업 위치는 공개 점검만으로 확인하지 않았다. 아래 구성은 공식 NGINX 지시어에 근거한 재현 예시다. 운영 설정과 대조하고 nginx -t를 통과한 뒤 사용해야 한다.

3줄 요약

  • ads.txt는 광고 인벤토리를 판매할 수 있는 사업자를 공개하는 일반 텍스트 파일이다. Google은 사용을 적극 권장하지만, 파일 하나가 AdSense 승인을 보장하지는 않는다.
  • Ghost 테마에 파일을 넣는 것만으로 https://도메인/ads.txt가 자동 생성된다고 가정하면 안 된다. Ghost 앞단의 Nginx Proxy Manager에서 정확한 루트 경로를 응답하게 할 수 있다.
  • 설정 뒤에는 화면에 한 줄이 보이는지만 확인하지 말고 HTTP 200, text/plain, 게시자 ID, robots.txt, HTTP→HTTPS 경로를 함께 검사한다.

ads.txt는 무엇이고 무엇이 아닌가

IAB Tech Lab은 ads.txt를 사이트 소유자가 광고 인벤토리를 판매하도록 허가한 사업자를 공개하는 표준으로 설명한다. Google AdSense도 ads.txt를 통해 승인된 판매자를 식별하고 가짜 인벤토리 판매를 줄일 수 있다고 안내한다.

AdSense 한 줄은 일반적으로 다음 형식이다.

google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0

각 필드의 의미는 다음과 같다.

  • google.com: 광고 시스템 도메인
  • pub-...: 자신의 AdSense 게시자 ID
  • DIRECT: 게시자가 해당 광고 시스템 계정을 직접 관리한다는 관계 표시
  • f08c47fec0942fa0: Google의 인증 기관 ID

pub-0000000000000000은 예시다. 다른 블로그의 게시자 ID를 복사하면 안 된다. 반드시 자신의 AdSense 계정에서 제공되는 행을 복사한다.

ads.txt는 AdSense 심사를 우회하는 파일도 아니고 승인 보장 장치도 아니다. 콘텐츠 품질, 정책 준수, 사이트 접근성 같은 심사 요건은 별개다. Google 공식 안내상 ads.txt 사용은 의무가 아니라 권장 사항이지만, 계정에 문제가 표시되면 올바르게 수정하는 편이 좋다.

Ghost에서 /ads.txt가 404가 되는 이유

Ghost 공식 문서의 테마 구조는 템플릿과 /assets 아래의 CSS·JavaScript·이미지를 중심으로 설명한다. 테마 ZIP의 최상위에 임의 파일을 넣었다고 해서 그 파일이 도메인 루트 URL로 그대로 노출된다고 보장되지 않는다.

또한 Ghost는 앞단에 리버스 프록시를 두는 구성을 공식적으로 지원한다. 일반 요청은 NGINX의 location /에서 Ghost 애플리케이션으로 전달된다. 이때 /ads.txt에 더 구체적인 경로 규칙이 없다면 요청도 Ghost로 넘어가고, Ghost에 해당 라우트가 없으면 404가 발생할 수 있다.

따라서 다음 세 가지를 구분해야 한다.

  1. Ghost 테마 안의 파일 위치
  2. Ghost 애플리케이션이 처리하는 URL 라우트
  3. Nginx Proxy Manager가 도메인 앞단에서 처리하는 정확한 /ads.txt 경로

이 글에서는 세 번째 계층에서 문제를 해결한다. Ghost 코어 파일을 수정하지 않으므로 Ghost 업데이트 때 변경이 덮어써질 위험도 줄일 수 있다.

변경 전 확인과 백업

먼저 현재 상태를 기록한다.

curl -sS -D - -o /tmp/ads-before.txt https://example.com/ads.txt

헤더에서 상태 코드와 콘텐츠 형식을 보고, 본문이 Ghost의 404 HTML인지 확인한다. /tmp/ads-before.txt에는 공개 응답만 저장되지만 외부에 공유하기 전 도메인과 운영 정보를 점검한다.

NPM 설정을 바꾸기 전에는 다음을 준비한다.

  • 대상 Proxy Host가 정확한 도메인인지 확인
  • NPM의 /data 구성 백업 또는 호스트 데이터 디렉터리 백업
  • 현재 Proxy Host 설정 화면 캡처
  • Docker Compose 파일과 환경파일의 별도 안전한 백업
  • 변경 뒤 즉시 되돌릴 이전 설정

단순한 ads.txt 문제 때문에 다음 명령을 실행할 이유는 없다.

docker compose down -v
docker volume rm <volume_name>

-v는 데이터 볼륨을 제거할 수 있다. ads.txt는 프록시의 한 경로를 제공하는 문제이므로 Ghost·MySQL 볼륨 삭제나 재설치와 관련이 없다.

방법 1: NPM에서 정확한 /ads.txt 응답 만들기

AdSense 한 줄만 제공한다면 Nginx Proxy Manager의 대상 Proxy Host에서 Advanced → Custom Nginx Configuration에 정확 일치 location을 추가하는 방식이 단순하다.

location = /ads.txt {
    default_type text/plain;
    add_header Cache-Control "public, max-age=3600";
    return 200 "google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0\n";
}

적용 전 pub-0000000000000000을 AdSense 계정이 제공한 자신의 게시자 ID로 바꾼다.

NGINX 공식 문서에서 location = /ads.txt=는 URI 정확 일치를 뜻한다. 따라서 다른 Ghost 글과 이미지 요청은 기존 location / 프록시 규칙을 계속 사용하고 /ads.txt만 별도로 처리한다. default_type text/plain은 응답의 기본 MIME 형식을 일반 텍스트로 지정하며, return 200은 지정한 본문과 성공 상태를 반환한다.

저장 전에 꼭 볼 것

  • 기존 Advanced 설정에 같은 location = /ads.txt가 이미 있는지 확인한다.
  • 중괄호와 세미콜론을 빠뜨리지 않는다.
  • 게시자 ID 앞뒤에 스마트 따옴표나 보이지 않는 서식 문자가 들어가지 않았는지 확인한다.
  • 여러 광고 네트워크를 쓰면 각 네트워크가 제공한 행만 추가한다. 출처가 불분명한 판매자 행은 넣지 않는다.
  • NPM UI 저장이 성공해도 실제 NGINX 구성이 유효한지 별도로 검사한다.

NPM 컨테이너 이름은 환경마다 다르므로 먼저 Compose 서비스명을 확인한다.

docker compose config --services
docker compose ps

그다음 실제 NPM 서비스 또는 컨테이너에서 설정 검사를 실행한다.

docker compose exec <npm_service> nginx -t

syntax is oktest is successful을 확인한다. 실패하면 재시작을 반복하지 말고 방금 추가한 블록을 이전 상태로 되돌린 뒤 오류 행을 확인한다.

방법 2: 여러 판매자 행은 읽기 전용 파일로 관리하기

판매자 행이 여러 개이거나 변경 이력을 파일로 관리하려면 응답 본문을 Advanced 설정에 직접 넣는 대신 NPM 컨테이너에 읽기 전용 파일을 마운트할 수 있다.

호스트에 일반 텍스트 파일을 만든다.

google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0

NPM Compose 서비스에 운영 환경에 맞는 경로를 읽기 전용으로 연결한다.

services:
  <npm_service>:
    volumes:
      - /safe/host/path/ads.txt:/data/static/ads.txt:ro

Proxy Host의 Advanced 설정에서는 정확 경로만 해당 파일로 연결한다.

location = /ads.txt {
    root /data/static;
    default_type text/plain;
}

이 경우 /ads.txt/data/static/ads.txt를 읽는다. 호스트 경로와 서비스명은 예시이므로 그대로 복사하지 말고 현재 Compose의 기존 마운트 정책에 맞춰야 한다.

Compose 변경 전후에는 최소한 다음을 확인한다.

docker compose config -q
docker compose config --services
docker compose up -d <npm_service>
docker compose ps

NPM의 데이터 볼륨이나 인증서 볼륨을 새 빈 경로로 바꾸지 않는다. ads.txt 파일 하나를 추가하려다 기존 Proxy Host와 인증서 설정을 잃을 수 있다.

HTTP 상태와 본문을 검증하는 방법

브라우저에 한 줄이 보이는 것만으로는 충분하지 않다. Google은 본문이 있어도 응답 상태가 404면 파일을 존재하지 않는 것으로 처리한다고 안내한다.

1. HTTPS 응답 확인

curl -sS -D /tmp/ads-headers.txt \
  -o /tmp/ads-body.txt \
  https://example.com/ads.txt

sed -n '1,20p' /tmp/ads-headers.txt
cat /tmp/ads-body.txt

확인 기준:

  • 최종 상태가 200 OK
  • Content-Typetext/plain
  • 본문에 자신의 게시자 ID가 정확히 한 번 존재
  • Ghost 404 HTML, 로그인 페이지, 보안 인증 페이지가 섞이지 않음

2. 정확한 게시자 행 확인

grep -Fx \
  'google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0' \
  /tmp/ads-body.txt

일치 결과가 없으면 게시자 ID, 쉼표, 공백, 줄바꿈과 보이지 않는 문자를 확인한다. 서식 있는 문서 편집기보다 일반 텍스트 편집기를 사용한다.

3. HTTP에서 HTTPS로 이어지는지 확인

curl -I http://example.com/ads.txt
curl -I https://example.com/ads.txt

HTTP 요청은 HTTPS의 같은 경로로 안전하게 리디렉션되고, HTTPS 최종 응답은 200이어야 한다. www를 쓰는 사이트라면 루트 도메인과 www 경로 관계도 확인한다. Google 공식 문서는 ads.txt 탐색이 루트 도메인에서 시작한다고 설명한다.

4. robots.txt가 막지 않는지 확인

curl -sS https://example.com/robots.txt

/ads.txt 또는 그 상위 경로를 차단하는 Disallow 규칙이 없어야 한다. 광범위한 /ads 차단 규칙을 쓰고 있다면 /ads.txt에 대한 명시적 허용이 필요한지 검토한다.

5. 외부 네트워크에서도 확인

서버 내부에서만 성공하고 외부에서는 인증 페이지·403·404가 나타날 수 있다. 휴대전화 모바일 네트워크나 별도의 외부 점검 위치에서 https://example.com/ads.txt를 열어 같은 본문이 보이는지 확인한다.

실제 확인 결과

이 글의 사례 사이트에서는 2026년 8월 28일 공개 인터넷에서 다음을 확인했다.

  • HTTPS /ads.txt: 200 OK
  • 응답 형식: text/plain
  • 본문 크기: 58바이트
  • 게시자 행: AdSense 계정에 사용하는 pub- ID와 일치
  • robots.txt: 200 OK, 사이트맵 선언 확인
  • sitemap.xml: 200 OK

이 결과는 공개 파일 제공이 정상이라는 뜻이다. AdSense 대시보드의 상태 갱신이나 사이트 승인까지 완료됐다는 뜻은 아니다.

파일은 정상인데 AdSense에 계속 ‘찾을 수 없음’으로 보일 때

다음 순서로 점검한다.

  1. 브라우저가 아니라 curl로 최종 HTTP 상태를 확인한다.
  2. 루트 도메인, www, HTTP, HTTPS가 서로 다른 결과를 내는지 본다.
  3. robots.txt, WAF, Authelia, 접근 목록이 광고 크롤러를 막지 않는지 확인한다.
  4. 응답이 Ghost 404 HTML이나 로그인 페이지인데 상태만 200인 soft 404가 아닌지 본다.
  5. 게시자 ID와 AdSense 계정의 ID를 다시 비교한다.
  6. 파일을 계속 삭제·재생성하지 말고 Google이 다시 크롤링할 시간을 둔다.
  7. AdSense의 사이트 화면에서 업데이트 확인 기능이 제공되면 한 번 실행한다.

Google은 변경 반영에 며칠이 걸릴 수 있으며 광고 요청이 적은 사이트는 최대 한 달이 걸릴 수도 있다고 안내한다. 설정이 맞다면 매시간 파일을 바꾸거나 사이트를 삭제했다가 다시 추가하는 방식은 피한다.

자주 하는 실수

Ghost 테마 ZIP 최상위에만 파일 넣기

테마 파일 위치와 도메인 루트 URL은 같은 개념이 아니다. 반드시 공개 URL의 상태 코드와 본문으로 확인한다.

/ads.txt/처럼 뒤에 슬래시 붙이기

파일 URL은 /ads.txt다. /ads.txt/는 별도 경로로 처리돼 404가 날 수 있다.

게시자 ID를 다른 글에서 복사하기

예제의 pub- 값은 자리표시자다. 자신의 AdSense 계정에서 제공되는 행을 사용한다.

화면에 본문이 보이면 끝났다고 판단하기

404 응답 본문에도 텍스트를 표시할 수 있다. HTTP 상태가 200인지 반드시 확인한다.

Authelia나 Access List로 /ads.txt까지 보호하기

Google 크롤러가 공개 접근할 수 있어야 한다. 블로그 전체는 공개하면서 /ads.txt에만 로그인 인증을 요구하면 크롤링할 수 없다.

설정 오류 뒤 NPM 데이터 볼륨 삭제하기

경로 블록의 문법 문제를 볼륨 삭제로 해결할 수 없다. 설정을 되돌리고 nginx -t 결과부터 확인한다.

재발 방지 체크리스트

  • [ ] https://도메인/ads.txt가 200으로 응답하는가
  • [ ] Content-Typetext/plain인가
  • [ ] AdSense 계정의 게시자 ID와 정확히 일치하는가
  • [ ] 루트 도메인과 www가 올바른 최종 경로로 연결되는가
  • [ ] HTTP 요청이 HTTPS의 같은 /ads.txt로 연결되는가
  • [ ] robots.txt, WAF, Authelia, NPM Access List가 경로를 차단하지 않는가
  • [ ] NPM 설정 변경 뒤 nginx -t가 성공하는가
  • [ ] NPM 재시작 뒤 Ghost 홈·관리자·인증서가 정상인가
  • [ ] NPM 구성과 ads.txt 원본을 백업했는가
  • [ ] 광고 네트워크를 바꿀 때 오래된 판매자 행을 검토하는가

마무리

Ghost에서 발생하는 ads.txt 404는 블로그 글 작성 문제가 아니라 루트 URL을 누가 응답하느냐의 문제다. Docker 기반 Ghost 앞에 Nginx Proxy Manager가 있다면 /ads.txt만 정확히 일치시키고 일반 텍스트로 반환하는 방식이 단순하다.

가장 중요한 확인 순서는 다음과 같다.

  1. 자신의 AdSense 게시자 행을 준비한다.
  2. NPM 설정을 백업한다.
  3. 정확한 /ads.txt location을 추가한다.
  4. nginx -t로 문법을 확인한다.
  5. 외부에서 HTTP 200, text/plain, 게시자 ID, robots.txt를 검증한다.
  6. 설정을 반복 변경하지 않고 AdSense의 재크롤링을 기다린다.

이 과정을 마치면 Ghost나 MySQL 데이터를 건드리지 않고 ads.txt 공개 경로만 복구할 수 있다.

공식 문서

태그

mLog

8년 이상 풀스택 개발자로 일하고 있습니다. Spring Boot, PostgreSQL, Redis, Vue·TypeScript와 Docker·Linux 서버를 다루며, 직접 운영하고 해결한 내용을 공식 문서와 실행 결과를 바탕으로 정리합니다.