Ghost 6.0 ActivityPub 설정 완벽 가이드 - 셀프호스팅 블로그를 Fediverse에 연결하기

Ghost 6.0 셀프호스팅 블로그를 Fediverse(Mastodon, Threads)에 연결하는 방법. NPM 프록시 설정부터 트러블슈팅까지.

1. ActivityPub이란?

ActivityPub은 분산형 소셜 네트워크를 위한 프로토콜입니다. Ghost 6.0부터 이 기능을 지원하여 블로그를 Fediverse에 연결할 수 있습니다.

1.1 ActivityPub으로 할 수 있는 것

Ghost 블로그 + ActivityPub:
├── Mastodon에서 블로그 팔로우 가능
├── Threads에서 블로그 검색/팔로우
├── 새 글 발행 시 팔로워 피드에 자동 게시
├── Fediverse 댓글/좋아요 Ghost Admin에서 확인
└── @[email protected] 형태로 검색 가능

1.2 Ghost Pro vs 셀프호스팅

항목 Ghost Pro 셀프호스팅 (ap.ghost.org) 셀프호스팅 (자체 컨테이너)
설정 난이도 자동 중간 높음
팔로워 제한 무제한 2,000명 무제한
일일 상호작용 무제한 100회 무제한
추가 비용 Ghost Pro 요금 무료 무료 (서버 리소스)
데이터 저장 Ghost 서버 Ghost 서버 내 서버

이 글에서는 셀프호스팅 + ap.ghost.org 방식을 다룹니다. 대부분의 개인 블로그에 충분한 방식입니다.


2. 전제 조건

이 가이드는 다음 환경을 기준으로 합니다:

항목 버전/설정
Ghost 6.0 이상 (6.13.2 테스트)
리버스 프록시 Nginx Proxy Manager
CDN/DNS Cloudflare (Proxy ON)
SSL Full (Strict) 모드

다른 환경(Traefik, Caddy, 직접 Nginx)이라면 프록시 설정 부분만 해당 환경에 맞게 조정하면 됩니다.


3. 아키텍처 이해

ActivityPub이 작동하려면 특정 경로의 요청을 Ghost가 아닌 ap.ghost.org로 프록시해야 합니다.

3.1 요청 흐름

사용자/Fediverse 서버
        │
        ▼
   Cloudflare (CDN)
        │
        ▼
   Nginx Proxy Manager
        │
        ├── /.ghost/activitypub/* ──▶ ap.ghost.org (ActivityPub 서비스)
        ├── /.well-known/webfinger ──▶ ap.ghost.org
        ├── /.well-known/nodeinfo ──▶ ap.ghost.org
        │
        └── 그 외 모든 요청 ──▶ Ghost 컨테이너 (localhost:2368)

3.2 필요한 경로

경로 용도 프록시 대상
/.ghost/activitypub/* ActivityPub API ap.ghost.org
/.well-known/webfinger 계정 검색 (Fediverse) ap.ghost.org
/.well-known/nodeinfo 서버 정보 ap.ghost.org

4. Nginx Proxy Manager 설정

4.1 Advanced 탭 열기

  1. NPM 관리 페이지 접속
  2. blog.example.com 호스트 Edit 클릭
  3. Advanced 탭 선택 (Custom Locations가 아님!)

4.2 Custom Nginx Configuration 입력

아래 설정을 그대로 복사하여 붙여넣기:

# ActivityPub 프록시 설정
# Ghost 6.0+ 셀프호스팅용

# 1. ActivityPub API 엔드포인트
location /.ghost/activitypub {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_ssl_server_name on;
    proxy_pass https://ap.ghost.org;
}

# 2. WebFinger (Fediverse 계정 검색용)
location /.well-known/webfinger {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_ssl_server_name on;
    proxy_pass https://ap.ghost.org;
}

# 3. NodeInfo (서버 정보)
location /.well-known/nodeinfo {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_ssl_server_name on;
    proxy_pass https://ap.ghost.org;
}

4.3 설정 저장

Save 버튼 클릭. NPM이 자동으로 nginx를 reload합니다.

4.4 설정 확인 (선택)

서버에서 생성된 nginx 설정 확인:

docker exec nginx-proxy-manager cat /data/nginx/proxy_host/1.conf | grep -A5 "activitypub"

정상 출력:

location /.ghost/activitypub {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    ...

5. Cloudflare Cache Rules 설정

Cloudflare가 ActivityPub 요청을 캐시하면 문제가 발생합니다. 해당 경로를 캐시에서 제외해야 합니다.

5.1 Cache Rules 생성

  1. Cloudflare Dashboard → 도메인 선택
  2. RulesCache Rules
  3. Create rule 클릭

5.2 규칙 설정

Rule name:

Ghost ActivityPub Bypass

When incoming requests match... (Custom filter expression):

Field Operator Value
URI Path starts with /.ghost/activitypub
Or
URI Path starts with /.well-known/webfinger
Or
URI Path starts with /.well-known/nodeinfo

Then... (Cache eligibility):

  • Bypass cache 선택

5.3 규칙 배포

Deploy 클릭


6. Ghost Admin에서 활성화

6.1 Network 기능 활성화

  1. Ghost Admin 접속: https://blog.example.com/ghost
  2. SettingsGrowthNetwork
  3. Enable 토글 ON

6.2 캐시 문제 해결 (중요!)

Network를 켰는데 "Loading interrupted" 에러가 나타나면:

  1. 시크릿/프라이빗 창 열기 (Cmd+Shift+N / Ctrl+Shift+N)
  2. Ghost Admin 접속
  3. SettingsGrowthNetwork → OFF
  4. 잠시 대기 (5초)
  5. Network → ON
  6. 페이지 새로고침

왜 시크릿 창인가?

  • 일반 창의 브라우저 캐시가 이전 에러 응답을 저장
  • 시크릿 창은 캐시 없이 깨끗한 상태로 요청
  • Ghost가 ap.ghost.org에 새로 등록됨

7. 연결 테스트

7.1 WebFinger 테스트 (가장 확실한 방법)

curl -s "https://blog.example.com/.well-known/webfinger?resource=acct:[email protected]"

정상 응답:

{
  "subject": "acct:[email protected]",
  "links": [
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://blog.example.com/.ghost/activitypub/users/index"
    }
  ]
}

7.2 Mastodon에서 검색

Mastodon 계정이 필요합니다. 무료 가입 가능한 인스턴스:

가입 후:

  1. 검색창에 @[email protected] 입력
  2. 블로그 프로필이 나타나면 성공

7.3 Ghost Admin에서 확인 (가장 간단)

  1. Ghost Admin → Settings → Growth → Network
  2. "Loading interrupted" 없이 Network 탭이 정상 로딩되면 성공
  3. 프로필, 팔로워, 피드 등이 표시됨

8. 트러블슈팅

8.1 "Loading interrupted" 에러

증상:
Ghost Admin의 Network 탭에서 "Loading interrupted" 메시지

원인:

  • NPM 프록시 설정 누락
  • 브라우저 캐시에 에러 응답 저장됨

해결:

  1. NPM Advanced 설정 확인 (섹션 4 참조)
  2. 시크릿 창에서 Network 토글 OFF → ON

8.2 ROLE_MISSING 에러

증상:

curl -s https://blog.example.com/.ghost/activitypub/v1/site
# {"error":"Forbidden","code":"ROLE_MISSING"}

원인:

  • Ghost가 ap.ghost.org에 등록되지 않음
  • 이전 등록이 손상됨

해결:

  1. Ghost Admin → Settings → Growth → Network → OFF
  2. 브라우저 캐시 완전 삭제 또는 시크릿 창 사용
  3. Network → ON
  4. 재테스트

8.3 SITE_MISSING 에러

증상:

{"error":"Forbidden","code":"SITE_MISSING"}

원인:

  • 이전에 같은 도메인으로 다른 Ghost 인스턴스가 등록됨
  • ap.ghost.org에 오래된 데이터 존재

해결:

  1. Ghost 포럼에서 지원 요청
  2. 또는 시크릿 창에서 Network 토글 재시도
  3. 최후 수단: Ghost 지원팀에 도메인 초기화 요청

8.4 404 에러 (Ghost 로그)

증상:
Ghost 로그에 /.ghost/activitypub/* 경로가 404로 기록됨

[INFO] "GET /.ghost/activitypub/v1/site" 404 5ms

원인:

  • NPM 프록시가 ap.ghost.org가 아닌 Ghost로 요청 전달

해결:

  1. NPM Advanced 설정이 Custom Locations가 아닌 Advanced 탭에 있는지 확인
  2. location 블록이 location / 보다 먼저 매칭되는지 확인
  3. NPM 재시작: docker restart nginx-proxy-manager

8.5 301 Redirect Loop

증상:
브라우저에서 무한 리다이렉트

원인:

  • Cloudflare SSL 설정과 NPM SSL 설정 충돌

해결:

  1. Cloudflare SSL/TLS → Full (Strict) 확인
  2. NPM에서 Force SSL 활성화 확인
  3. Cloudflare에서 해당 경로 캐시 바이패스 확인

9. ARM64 서버 사용자 참고

Oracle Cloud Free Tier 등 ARM64(aarch64) 서버를 사용하는 경우:

9.1 제한 사항

Ghost의 ActivityPub 컨테이너(ghcr.io/tryghost/activitypub)는 AMD64만 지원합니다.

ghcr.io/tryghost/activitypub:edge → AMD64 only ❌

9.2 대안

옵션 설명
ap.ghost.org 사용 (권장) 이 글의 방법, 제한 있지만 간편
소스에서 빌드 ARM64용으로 직접 빌드 (복잡)
AMD64 서버 사용 별도 서버에 ActivityPub 컨테이너 배포

대부분의 개인 블로그는 ap.ghost.org의 제한(2,000 팔로워, 100 상호작용/일) 내에서 충분히 운영 가능합니다.


10. 핵심 개념 정리

개념 설명
ActivityPub 분산형 소셜 네트워크 프로토콜
Fediverse ActivityPub으로 연결된 서버들의 네트워크
ap.ghost.org Ghost가 운영하는 ActivityPub 게이트웨이
WebFinger Fediverse에서 사용자 검색용 프로토콜
NodeInfo 서버 정보 공개용 표준
ROLE_MISSING Ghost가 ap.ghost.org에 미등록 상태

11. 베스트 프랙티스

체크리스트

  • [ ] NPM Advanced 탭에 프록시 설정 추가
  • [ ] Cloudflare Cache Rules로 ActivityPub 경로 바이패스
  • [ ] 시크릿 창에서 Network 활성화
  • [ ] curl로 API 응답 테스트
  • [ ] Mastodon에서 블로그 검색 테스트

권장 설정

항목 권장값
Cloudflare SSL Full (Strict)
NPM Force SSL ON
NPM HTTP/2 ON
NPM Websockets ON

12. FAQ

Q: ActivityPub을 활성화하면 성능에 영향이 있나요?
A: 거의 없습니다. ActivityPub 요청은 ap.ghost.org로 프록시되므로 Ghost 서버 부하는 증가하지 않습니다.

Q: 팔로워 2,000명 제한에 도달하면 어떻게 되나요?
A: 더 이상 새 팔로워를 받을 수 없습니다. 자체 ActivityPub 컨테이너를 호스팅하면 제한이 없습니다.

Q: Threads에서도 검색되나요?
A: Threads는 아직 ActivityPub을 제한적으로 지원합니다. Mastodon에서는 확실히 작동합니다.

Q: 기존 글도 Fediverse에 보이나요?
A: 새로 발행하는 글만 팔로워 피드에 나타납니다. 기존 글은 블로그 프로필에서 볼 수 있습니다.

Q: ActivityPub을 비활성화하면 팔로워 데이터는 어떻게 되나요?
A: ap.ghost.org에 보관됩니다. 다시 활성화하면 복구됩니다.


13. 다음 단계

ActivityPub 설정이 완료되었습니다! 이제 블로그가 Fediverse의 일부가 되었습니다.

시리즈 목차:

  1. Oracle Cloud 무료 서버 세팅
  2. Ghost 블로그 Docker 설치
  3. Ghost 블로그 백업 자동화
  4. 검색엔진 등록 (Google/Naver)
  5. Ghost 6.0 업그레이드
  6. Ghost ActivityPub 설정 (Fediverse) ← 현재 글
  7. Ghost Analytics 설정 (Tinybird)

14. 참고 자료