usefulparadigm.

기술 노트Solution Guide

ChatGPT Sites로 WordPress 모니터링 대시보드 만들기

관리하는 WordPress 사이트가 늘어나면 월요일 아침마다 같은 일을 반복하게 됩니다. 사이트마다 관리자 화면에 로그인해서 업데이트 알림을 확인하고, 사이트 상태(Site Health)를 열어 보고, 느려진 곳은 없는지 살피는 일이죠. 한 화면에서 모아 보면 좋겠지만, 그렇다고 사내 대시보드를 따로 개발하고 서버까지 운영할 여유는 없고. 무언가 간단하게 만들어 쓸 수 있는 도구가 있으면 좋을텐데..

싶던 차에 OpenAI의 ChatGPT Sites를 한번 써 보기로 했습니다. OpenAI가 공개 베타로 내놓은 ChatGPT Sites는 AI 대화로 웹앱을 만들고, 호스팅과 데이터베이스, 로그인까지 한 번에 처리해 줍니다.

오늘은 바로 그 ChatGPT Sites와 작은 WordPress mu-plugin을 조합해서, 관리 중인 WordPress 사이트들의 상태를 한눈에 보는 사내 대시보드를 한번 만들어 보겠습니다. 만드는 과정에서 겪은 시행착오와, 실제로 도입할 만한지에 대한 제 판단도 함께 정리했으니 끝까지 읽어 주세요.

ChatGPT Sites란?

ChatGPT Sites는 ChatGPT로 웹사이트, 웹앱, 게임을 만들고 호스팅하고 공유하는 기능입니다. 프롬프트나 기존 프로젝트를 별도의 배포 과정 없이 바로 접속 가능한 사이트로 바꿔 준다는 점이 핵심. 코드를 생성해 주는 도구는 이미 많고 또 완성된 코드를 클라우드 상에 쉽게 호스팅할 수 있는 서비스들도 이미 많지만, 코드 생성부터 호스팅과 저장소, 접근 제어까지 한곳에서 끝난다는 점이 다릅니다.

이 글을 쓰는 2026년 10월 현재 공개 베타이며, Plus, Pro, Business, Enterprise, Edu 요금제에서 쓸 수 있습니다. Free와 Go 요금제는 지원하지 않습니다. 베타 기간에는 요금제별 사용 한도가 있지만 구체적인 수치는 공개되어 있지 않습니다.

시작은 ChatGPT 웹의 Work 화면이나 데스크톱 앱의 Work, Codex 화면에서 합니다. 프롬프트에 “website”라는 단어를 넣거나 @Sites를 멘션하면 Sites 워크플로가 시작됩니다. 저는 데스크톱 앱에서 Sites 플러그인을 멘션해서 시작했습니다.

데스크톱 앱에서 Sites 플러그인을 멘션해 시작하는 화면

핵심 기능

ChatGPT Learn의 Sites 문서를 기준으로 이번 실습에 관련된 기능만 추리면 다음과 같습니다:

  • 접근 제어: 새 사이트는 소유자만 볼 수 있고, 이메일로 초대한 사람, 워크스페이스 전체, 인터넷 전체 중에서 공개 범위를 고릅니다.
  • 저장소: 구조화 데이터는 D1(관계형 데이터베이스, 사이트당 10GB), 파일은 R2(오브젝트 스토리지)에 저장합니다.
  • Sign in with ChatGPT: 방문자가 ChatGPT 계정으로 로그인하면, 서버가 요청 헤더로 사용자 이메일을 받습니다.
  • 버전과 배포: 버전 저장과 배포가 분리되어 있고, 배포하는 순간 바로 운영 URL이 됩니다.
  • 시크릿 관리: API 키 같은 값은 사이트 설정의 환경 변수와 시크릿에 따로 보관합니다.

Cloudflare Workers 위에서 돈다

공식 문서에는 런타임이 명시되어 있지 않습니다. 그런데 이번에 생성된 소스를 내려받아 보니 서버 코드가 cloudflare:workers 모듈을 불러오고, 로컬 실행에는 Cloudflare의 개발 도구인 wrangler를 씁니다. D1과 R2라는 이름도 Cloudflare 제품명 그대로입니다. 즉 ChatGPT Sites는 Cloudflare Workers 위에서 동작합니다(지난번 EmDash 글에 이어 Cloudflare가 또 등장하네요).

이 사실은 뒤에서 만나는 문제를 이해하는 데 중요합니다. Workers는 Node.js와 비슷해 보이지만 다른 런타임이라서, Node.js에서 잘 돌던 코드가 그대로 실패할 수 있거든요.

왜 직접 만드나요?

여러 WordPress 사이트를 관리하는 도구는 이미 있습니다. ManageWP나 MainWP 같은 도구는 업데이트 실행, 백업, 가동 상태 감시까지 폭넓게 지원하죠. 그런데 필요했던 건 훨씬 좁은 것이었습니다. 월요일 점검 회의에서 “어느 사이트에 문제가 있나”를 한눈에 보는 읽기 전용 현황판이면 충분했거든요.

비교 항목기성 관리 도구ChatGPT Sites로 직접 만들기
기능 범위업데이트 실행, 백업 등 폭넓음필요한 상태 조회만
사이트 쪽 설치도구 전용 연결 플러그인직접 만든 mu-plugin 하나
사이트에 주는 권한관리 작업을 위한 높은 권한읽기 전용 요약만
화면과 판정 기준제품이 정한 방식대화로 수정
운영 부담서비스 가입 또는 별도 설치Sites가 호스팅

가장 큰 차이는 권한입니다. 기성 도구는 사이트를 원격으로 관리해야 하니 높은 권한이 필요합니다. 반면 현황판은 몇 가지 숫자만 읽으면 되니, 고객사 사이트에 관리자 권한을 열어 둘 이유가 없습니다.

데이터를 어떻게 가져올까

처음 고민한 건 수집 방식이었습니다. WordPress 5.6부터 지원하는 애플리케이션 비밀번호(Application Passwords)로 REST API를 직접 호출하는 방법이 가장 간단해 보입니다. 하지만 플러그인 목록이나 사이트 상태 정보는 관리자 권한이 있어야 읽을 수 있어서, 결국 고객사 사이트의 관리자 자격 증명을 외부 호스팅에 맡기게 됩니다.

그래서 각 사이트에 작은 mu-plugin을 올려, 토큰으로 보호되는 읽기 전용 엔드포인트를 두는 방식을 택했습니다. 역할을 나누면 이렇습니다.

  • mu-plugin = 사이트 상태 요약 제공
  • ChatGPT Sites = 수집, 저장, 화면

토큰이 유출되어도 요약 정보만 노출될 뿐 사이트를 바꿀 수는 없는 구조죠.

실전 워크플로: WordPress 상태 대시보드 만들기

Step 1: WordPress에 상태 엔드포인트 만들기

먼저 각 WordPress 사이트에 /wp-json/up-status/v1/summary 엔드포인트를 만드는 mu-plugin을 올립니다. 요청 헤더의 토큰이 맞을 때만 코어·플러그인·테마 업데이트 현황, PHP와 DB 버전, Site Health 요약을 JSON으로 돌려줍니다. 핵심은 토큰 검사 부분입니다.

// up-status.php (간략화)
function up_status_check_token( WP_REST_Request $request ) {
	// 토큰이 없거나 너무 짧으면 엔드포인트 자체를 닫는다
	if ( ! defined( 'UP_STATUS_TOKEN' ) || strlen( (string) UP_STATUS_TOKEN ) < 32 ) {
		return new WP_Error( 'up_status_not_configured', 'Status endpoint is not configured.', array( 'status' => 503 ) );
	}

	$token = (string) $request->get_header( 'x-up-status-token' );

	// 타이밍 공격을 피하기 위해 hash_equals로 비교
	if ( '' === $token || ! hash_equals( (string) UP_STATUS_TOKEN, $token ) ) {
		return new WP_Error( 'up_status_forbidden', 'Invalid token.', array( 'status' => 403 ) );
	}

	return true;
}

※ 전체 코드는 GitHub Gist에 올려 두었습니다.

설계에서 신경 쓴 점은 두 가지입니다. 하나는 토큰을 플러그인 파일이 아니라 wp-config.php에 두는 것입니다. 모든 사이트에 같은 파일을 올리고 토큰만 사이트별로 다르게 둘 수 있죠. 다른 하나는 업데이트 정보를 새로 조회하지 않고 WordPress가 이미 저장해 둔 값만 읽는 것입니다. 대신 마지막 확인 시각(last_checked)을 함께 내려 주니, 이 값이 오래된 사이트는 크론(cron)이 멈췄다고 의심할 수 있습니다.

설치는 세 단계입니다. 사이트마다 토큰을 만들고,

openssl rand -hex 32

wp-config.php에 등록한 뒤,

define( 'UP_STATUS_TOKEN', '생성한_토큰' );

wp-content/mu-plugins/ 폴더에 up-status.php를 업로드합니다. mu-plugin은 따로 활성화할 필요가 없습니다.

플러그인 화면의 “필수 사용” 탭에 등록된 UP Status Endpoint

마지막으로 curl로 확인합니다.

curl -H "X-UP-Status-Token: 생성한_토큰" https://example.com/wp-json/up-status/v1/summary
{
  "schema": 1,
  "core": { "version": "7.1.2", "latest": "7.1.2", "needs_update": false },
  "plugins": { "total": 17, "active": 8, "updates": [] },
  "server": { "php": "8.1.13", "db": "5.7.29", "debug": false },
  "health": { "good": 20, "recommended": 6, "critical": 0 }
}

응답 예시(간략화)

Step 2: Sites에 대시보드 요청하기

이제 ChatGPT에 대시보드를 요청합니다. 누가, 언제, 무엇을 보려고 쓰는지를 구체적으로 적고, 만들기 전에 질문해 달라고 덧붙였습니다.

@Sites 우리 회사가 관리하는 WordPress 사이트들의 상태를 한눈에 보는 사내 대시보드를 만들어 줘.

- 사용자: 사내 개발자 3~5명. 매주 월요일 점검 회의에서 사용
- 사이트 목록: 이름, URL, 담당자, 고객사를 등록·수정·삭제할 수 있게 하고 D1에 저장
- 상태 수집: 각 사이트의 /wp-json/up-status/v1/summary 를 호출해 응답 여부와 시간,
  코어 버전과 업데이트 여부, 업데이트 대기 플러그인 수, PHP 버전, Site Health 요약을 표시
- 문제가 있는 사이트(응답 없음, 업데이트 대기 많음)를 맨 위에 빨간색으로 표시
- 수집 결과를 D1에 기록해 사이트별 최근 이력을 볼 수 있게
- 만들기 전에 페이지 구조를 먼저 제안하고, 필요한 질문을 해 줘

ChatGPT는 바로 코드를 쓰지 않고 페이지 구조(전체 현황, 사이트 목록, 상세, 관리)를 제안한 뒤 다섯 가지를 물었습니다. API 헤더 형식과 응답 예시, 문제 판정 기준, 수집 주기와 이력 보관 기간, 접근 방식, 토큰 등록 방식입니다. 모두 실제로 결정이 필요한 질문이었어요.

답변에서 중요했던 결정은 두 가지입니다. 판정 기준은 “10초 내 무응답이나 플러그인 업데이트 5개 이상이면 문제, 코어 업데이트나 응답 3초 이상이면 주의”처럼 숫자로 정했습니다. 그리고 토큰은 사이트별 시크릿 대신 마스터 키 하나로 암호화해 D1에 저장하도록 바꿨습니다. Sites는 시크릿을 바꾸면 재배포해야 해서, 사이트를 추가할 때마다 재배포하는 번거로움을 피하려는 선택입니다.

Step 3: 생성된 소스 검토하기

답변을 보내자 ChatGPT가 대시보드를 만들고 버전 1로 저장했습니다. 데스크톱 앱 안의 브라우저에서 로컬 미리보기를 바로 열어 볼 수 있습니다.

데스크톱 앱에서 연 로컬 미리보기. 하단의 Seedy는 미리보기용 테스트 계정이다


저는 배포 전에 소스를 내려받아 검토했습니다. 구성은 Next.js 16, Drizzle ORM, D1이고 앞서 말한 대로 Cloudflare Workers 위에서 동작합니다. 보안 설계는 기대 이상이었습니다:

  • 서버 측 권한 검사: 사이트 등록·수정·삭제 API마다 서버에서 로그인 이메일을 관리자 목록과 비교합니다. 화면에서 버튼만 숨기는 방식이 아닙니다.
  • 토큰 암호화: Web Crypto의 AES-GCM을 쓰고, 토큰마다 새 IV를 만들며, 사이트 ID를 추가 인증 데이터로 묶었습니다. 암호문을 다른 사이트 행에 옮겨 붙여도 복호화되지 않습니다.
  • 내부망 공격(SSRF) 방어: https 주소만 받고, 호출 직전에 DNS를 조회해 사설 IP로 풀리는 도메인을 막으며, 리디렉션을 따라가지 않습니다.

로그인은 Sites가 넣어 주는 요청 헤더를 읽는 방식입니다.

// app/chatgpt-auth.ts (간략화)
export async function getChatGPTUser() {
  const requestHeaders = await headers();
  // Sites 플랫폼이 로그인한 방문자 정보를 헤더로 전달한다
  const userId = requestHeaders.get("oai-authenticated-user-id");
  const email = requestHeaders.get("oai-authenticated-user-email");
  if (!userId || !email) return null;
  return { userId, email };
}

검토하면서 고칠 점도 찾았습니다. mu-plugin은 값을 모를 때 null을 보내는데 대시보드는 이를 형식 오류로 처리하고 있었고, 관리자가 아닌 사용자는 “로그인했는지”만 확인하고 있었습니다. Sites 공유 설정이 실수로 공개가 되면 ChatGPT 계정이 있는 누구나 들어올 수 있는 구조였죠. 그래서 null을 “정보 없음”으로 표시하고, 조회자 이메일 목록(VIEWER_EMAILS)을 추가해 목록 밖의 사용자는 막도록 요청했습니다.

Step 4: 미리보기에서 만난 문제

사이트를 등록하자 “연결 실패 · 네트워크 또는 인증서 확인 필요”라는 오류가 떴습니다. 같은 토큰으로 curl은 정상이었고, ChatGPT에 미리보기와 같은 환경에서 curl을 실행해 달라고 해도 정상이었습니다. 네트워크도, 토큰도 문제가 아니었던 거죠.

원인은 대시보드가 실제 오류를 버리고 뭉뚱그린 메시지만 보여 주는 데 있었습니다. 실제 오류를 출력해 달라고 하자 이런 메시지가 나왔습니다.

TypeError: Invalid redirect value, must be one of "follow" or "manual"
("error" won't be implemented since it does not make sense at the edge; ...)

DNS 사전 확인 코드가 fetch에 redirect: 'error' 옵션을 쓰고 있었는데, Cloudflare Workers는 이 옵션을 구현하지 않습니다. Node.js에서는 정상 동작하는 옵션이고요. 테스트 14개가 모두 통과했던 이유도 여기 있었습니다. 테스트는 Node.js에서 fetch를 가짜 함수로 바꿔 돌렸기 때문에 런타임의 실제 fetch를 한 번도 거치지 않았습니다.

ChatGPT는 옵션을 'manual'로 바꾸고, 함께 의심되던 cache 옵션을 제거하고, Workers의 호환성 날짜(compatibility date)를 배포 환경과 맞췄습니다. 그리고 미리보기 런타임의 실제 fetch로 공개 URL을 호출하는 확인 절차를 테스트에 추가했습니다. 그제서야 수집이 정상으로 나왔습니다.

Step 5: 배포하고 확인하기

배포는 “이 사이트 게시해줘” 한 마디면 됩니다. 그 전에 사이트 설정에 마스터 키(openssl rand -base64 32로 생성)와 관리자 이메일을 넣어 둬야 합니다. 관리자 목록이 비어 있으면 소유자도 들어갈 수 없습니다.

배포 완료 화면. 처음에는 “나만 볼 수 있음” 상태로 게시된다

배포하면 wp-ops-desk.you.chatgpt.site처럼 [사이트 이름].[사용자 이름].chatgpt.site 형태의 주소가 발급됩니다. 미리보기와 운영 데이터베이스는 별개라서 사이트를 다시 등록해야 합니다. 다시 등록하고 수집하자 HTTP 200, 응답 0.55초, WordPress 7.1.2, Site Health 심각 0개로 정상 표시되었습니다.

배포한 대시보드에서 수집한 결과. 오른쪽 아래 “사이트 편집” 버튼으로 게시된 화면에서 바로 수정을 시작할 수 있다

더 나은 방법: 예약 수집으로 확장하기

지금 대시보드는 새로고침 버튼을 눌러야 수집합니다. 월요일 회의 직전에 한 번 누르는 용도로는 충분하지만, 매일 아침 자동으로 수집해 두면 이력이 훨씬 쓸모 있어지겠죠.

Sites에는 예약 작업(Automations) 기능이 있어서, 게시한 사이트에 클라우드에서 도는 반복 작업을 연결할 수 있습니다. 다만 현재 수집 API는 로그인한 사용자만 호출할 수 있어서 그대로는 쓸 수 없습니다. 사람 없이 호출할 수 있는 별도의 수집 경로를 만들거나, Sites가 지원하는 MCP 서버 호스팅으로 수집 기능을 도구로 노출해야 합니다. MCP로 노출하면 대화창에서 “업데이트가 필요한 사이트 알려줘”라고 묻는 것도 가능해집니다.

솔직히 말하면 이 부분은 아직 확인하지 못했습니다. ChatGPT도 Plus 계정에서 예약 작업이 실제로 돌아가 D1에 저장되는지는 검증하지 않았다고 밝혔습니다. 그 밖에 팀원 초대와 커스텀 도메인 연결 부분도 있는데, 이 글에서는 다루지 않습니다.

팁과 주의사항

프롬프트 작성 팁

이번 작업에서 효과가 컸던 요청 방식은 이렇습니다:

  • 사용자와 용도를 밝힌다: “개발자 3~5명이 월요일 회의에서 쓴다”는 한 줄이 화면 구성과 정렬 방식을 결정합니다.
  • 만들기 전에 질문하게 한다: 판정 기준이나 토큰 관리처럼 숨은 결정 사항을 미리 드러내 줍니다.
  • 버전 저장과 배포를 구분한다: 매번 “버전으로 저장만 하고 배포는 하지 마세요”를 붙였습니다. Sites는 배포가 곧 운영 반영이기 때문입니다.
  • 실제 오류를 보여 달라고 한다: 뭉뚱그린 오류 메시지로는 원인을 찾을 수 없습니다. 이번에도 실제 오류를 보는 순간 문제가 풀렸습니다.

알아둘 한계점

직접 만들고 배포하면서 확인한 한계는 다음과 같습니다.

모든 배포가 운영 배포입니다. 스테이징 URL이 따로 없습니다. 검토가 필요하면 버전만 저장해 두고 미리보기에서 확인한 뒤 배포해야 합니다.

미리보기와 운영 런타임이 다를 수 있습니다. 이번 문제처럼 Node.js 기반 테스트를 통과한 코드가 Workers에서 실패할 수 있습니다. “테스트 통과”라는 보고만 믿지 말고, 배포 전에 실제 사이트로 한 번은 수집해 봐야 합니다.

데이터 레지던시를 지원하지 않습니다. 공식 도움말에 따르면 사이트 코드, D1 데이터, 로그의 저장 위치를 지정할 수 없습니다. 고객사 정보를 다룬다면 사전에 동의 여부를 검토해야 합니다.

사내망에는 접근할 수 없습니다. 사설 네트워크를 지원하지 않아서, VPN이나 IP 허용 목록 뒤에 있는 스테이징 서버는 대시보드에서 볼 수 없습니다. 국내 호스팅의 해외 접속 차단 옵션이 켜진 사이트도 수집이 막힐 수 있습니다.

플랫폼에 묶입니다. 생성된 앱은 Sites가 넣어 주는 oai-authenticated-user-* 헤더를 믿고 사용자를 판단합니다. ChatGPT가 만든 운영 안내 문서에도 “외부 프록시로 헤더를 직접 전달해 배포하지 말라”고 적혀 있습니다. Sites 밖으로 옮기려면 인증부터 다시 짜야 한다는 뜻입니다.

베타 한도와 문서가 아직 유동적입니다. 요금제별 사용 한도는 숫자로 공개되지 않았습니다. 공식 문서끼리도 내용이 어긋납니다. 예컨대 2026년 6월에 나온 OpenAI Academy 글은 실시간 데이터 연결이 안 된다고 하지만, 최신 도움말은 Business 이상 워크스페이스에서 연결된 앱을 읽을 수 있다고 설명합니다. 날짜가 최신인 문서를 기준으로 보는 게 안전합니다.

결론

ChatGPT Sites로 사내 도구를 만들어 보며 확인한 장점은 세 가지입니다:

  • 속도: 인증, 데이터베이스, 호스팅을 직접 구성하지 않고 대화 몇 번으로 운영 가능한 대시보드까지 갔습니다.
  • 맞춤: 판정 기준과 화면을 우리 회의 방식에 맞춰 대화로 고칠 수 있습니다.
  • 검토 가능성: 소스를 내려받아 직접 검토할 수 있고, 생성된 운영 안내 문서가 스스로 한계를 밝혀 둡니다.

Sites는 소규모 팀이 쓰는 사내 현황판, 점검 도구, 프로토타입처럼 “작고 분명한 문제 하나”를 푸는 데 잘 맞습니다. 반면 개인정보를 다루거나, 사내망 자원에 붙어야 하거나, 오래 운영할 핵심 시스템이라면 아직 기본 선택지로 보기 어렵습니다. 데이터 위치를 지정할 수 없고 플랫폼 종속이 크기 때문입니다.

솔직히 이번 실험에서 가장 인상적이었던 건 코드의 품질보다 과정이었습니다. ChatGPT는 만들기 전에 질문했고, 보안 설계도 꼼꼼했습니다. 그런데 정작 가장 기본적인 외부 호출이 실제 런타임에서 실패했고, 테스트는 그걸 잡지 못했습니다. AI가 만든 코드를 쓸 때 사람이 해야 할 일이 무엇인지 분명히 보여 준 장면이라 생각합니다.

관리하는 WordPress 사이트가 여럿이라면 Gist의 mu-plugin을 올리고 Sites에 직접 한번 요청해 보세요. 결국 AI는 코드를 빨리 써 줄 뿐, 그 코드가 어디서 어떻게 돌아가는지까지 확인하는 건 여전히 사람 몫이랍니다. 😄

참고 자료

⚠️ 이 글은 AI를 활용해 초안을 작성하고 필자가 검토 및 수정하는 방식으로 작성하였습니다.

댓글 남기기