시리즈: AI 팀과 함께 기술블로그 만들기 | Episode 3
AI 에이전트 3명이 설계해준 아키텍처를 실제로 구현하기 시작했습니다. docker-compose.yml을 다시 쓰고, 보안 헤더를 추가하고, Redis를 연결하고. 대부분은 순조로웠습니다.
Caddy를 건드리기 전까지는요.
이 글은 Caddy Docker Proxy에서 발생한 3가지 장애와 디버깅 과정을 기록합니다. 모두 같은 날, 같은 서비스에서, 연쇄적으로 터졌습니다.
아키텍처 배경
먼저 우리 블로그의 요청 흐름을 이해해야 합니다.
[사용자] → [Cloudflare CDN/Proxy] → [Caddy Reverse Proxy] → [WordPress]
(HTTPS) (HTTP) (HTTP)
핵심: Cloudflare가 SSL을 처리합니다. 사용자와 Cloudflare 사이는 HTTPS, Cloudflare에서 서버까지는 HTTP. Caddy는 리버스 프록시 역할만 합니다.
Caddy 설정은 docker-compose.yml의 labels로 관리합니다. Caddy Docker Proxy가 이 labels를 읽어서 자동으로 Caddyfile을 생성하는 구조입니다.
장애 1: 308 무한 리다이렉트
증상
https://yndl.dev 접속 시 “이 페이지는 작동하지 않습니다. ERR_TOO_MANY_REDIRECTS”. curl로 확인하면 308 Permanent Redirect가 무한 반복됩니다.
원인
AI 에이전트 2명이 동시에 “Caddy 레이블에서 http://를 제거하라”고 권장했습니다. auto-HTTPS를 활성화해야 한다면서요.
# AI 권장대로 변경
labels:
caddy: "yndl.dev" # http:// 제거 → auto-HTTPS 활성화
이 변경이 트리거한 연쇄 반응:
- Caddy가 auto-HTTPS 활성화
- Let’s Encrypt에 TLS 인증서 발급 요청
- HTTP-01 챌린지 시도
- DNS가 Cloudflare Proxy를 가리키고 있어서 챌린지 실패
- Caddy가 그래도 HTTPS로 리다이렉트 시도
- Cloudflare도 HTTPS로 리다이렉트
- 무한 루프: Cloudflare(308) → Caddy(308) → Cloudflare(308) → …
해결
labels:
caddy: "http://yndl.dev" # http:// 명시 → auto-HTTPS 비활성화
원칙: Cloudflare Proxy 뒤에서는 Caddy의 auto-HTTPS를 끄세요. SSL은 Cloudflare가 처리합니다.
장애 2: wp-login.php 무한 리다이렉트
증상
장애 1을 고친 후, 메인 페이지는 잘 뜹니다. 하지만 https://yndl.dev/wp-login.php에 접속하면 다시 “너무 많은 재이동” 에러가 발생합니다.
원인
WordPress wp-config.php에 FORCE_SSL_ADMIN = true가 설정되어 있습니다. 관리자 페이지는 반드시 HTTPS로 접속해야 한다는 뜻이죠.
그런데 요청 흐름을 보면:
[사용자] --HTTPS--> [Cloudflare] --HTTP--> [Caddy] --HTTP--> [WordPress]
WordPress: "HTTP네? HTTPS로 가!"
→ 브라우저: HTTPS로 재요청
→ Cloudflare → Caddy → WordPress: "또 HTTP네?"
→ 무한 반복
WordPress가 받는 요청은 항상 HTTP입니다. Caddy가 HTTP로 전달하니까요. WordPress는 매번 “HTTPS가 아니네?”라고 판단해서 리다이렉트합니다.
해결
Caddy가 WordPress에 “원래 이 요청은 HTTPS였어”라고 알려줘야 합니다.
labels:
caddy.reverse_proxy.header_up: "X-Forwarded-Proto https"
WordPress의 wp-config.php가 이 헤더를 감지합니다:
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
원칙: 리버스 프록시 뒤의 WordPress에는 반드시 X-Forwarded-Proto를 전달하세요. Caddy, Nginx, Apache 모두 해당됩니다.
장애 3: 사이트가 증발했다
증상
장애 1, 2를 모두 고치고 보안 헤더와 xmlrpc.php 차단을 추가합니다. 사이트에 접속하면… 빈 페이지. HTTP 200 OK인데 content-length: 0. 아무것도 없습니다.
원인
Caddy 로그에서 범인을 찾았습니다:
$ docker logs caddy | grep ERROR
[ERROR] ... Removing invalid block for "yndl.dev"
“Removing invalid block” — Caddy가 yndl.dev 사이트 블록 자체를 제거해버렸습니다. 원인은 레이블 문법:
# 잘못된 문법
caddy.respond.@xmlrpc: "403"
# → Caddyfile: respond { @xmlrpc 403 } ← 파싱 오류!
# 올바른 문법
caddy.respond: "@xmlrpc 403"
# → Caddyfile: respond @xmlrpc 403 ← 정상
dot notation에서 @xmlrpc를 키의 일부가 아니라 값의 일부로 넘겨야 합니다. Caddy Docker Proxy는 파싱에 실패하면 해당 사이트 블록 전체를 조용히 제거합니다.
원칙: Caddy Docker Proxy 레이블 변경 후에는 반드시 docker logs caddy | grep ERROR로 파싱 오류를 확인하세요. 오류가 있으면 사이트가 통째로 사라집니다.
최종 동작하는 설정
3번의 장애를 거쳐 완성된 Caddy 레이블:
labels:
caddy: "http://yndl.dev"
caddy.reverse_proxy: "{{upstreams 80}}"
caddy.reverse_proxy.header_up: "X-Forwarded-Proto https"
caddy.header.X-Content-Type-Options: "nosniff"
caddy.header.X-Frame-Options: "SAMEORIGIN"
caddy.header.Referrer-Policy: "strict-origin-when-cross-origin"
caddy.header.-Server: ""
caddy.header.-X-Powered-By: ""
[email protected]: "/xmlrpc.php"
caddy.respond: "@xmlrpc 403"
| 레이블 | 역할 |
|---|---|
caddy: "http://yndl.dev" | HTTP 모드 (Cloudflare가 SSL 처리) |
caddy.reverse_proxy.header_up | HTTPS 원본 프로토콜 전달 |
caddy.header.-Server | 서버 정보 헤더 제거 |
[email protected] | xmlrpc.php 매처 정의 |
caddy.respond: "@xmlrpc 403" | xmlrpc.php에 403 응답 |
디버깅 치트시트
1단계: Caddy 로그 확인
docker logs caddy 2>&1 | grep -E "ERROR|WARN|invalid"
2단계: 응답 헤더 확인
curl -sI https://yndl.dev
# server: Caddy → Caddy가 응답 (WordPress까지 안 감)
# link: wp-json → 정상 프록시
# content-length: 0 → 사이트 블록 제거됨
3단계: 프록시 체인 단계별 테스트
# WordPress 직접 테스트
docker exec wordpress curl -sI http://localhost:80/
# Caddy 설정 확인
curl -s localhost:2019/config/ | python3 -m json.tool
세 장애의 공통 패턴
세 장애 모두 “프록시 체인에서 각 노드가 서로의 존재를 모른다”는 같은 근본 원인을 가지고 있습니다.
| 장애 | 모르는 관계 | 결과 |
|---|---|---|
| 308 루프 | Caddy가 Cloudflare를 모름 | 이중 SSL 시도 |
| 로그인 루프 | WordPress가 Cloudflare를 모름 | HTTP로 착각 |
| 사이트 증발 | Caddy가 문법 오류를 조용히 처리 | 블록 삭제 |
리버스 프록시 체인을 설정할 때는 항상 “각 노드가 앞/뒤 노드에 대해 알아야 할 것이 무엇인가?”를 질문해야 합니다.
- WordPress는
X-Forwarded-Proto로 원본 프로토콜을 알아야 합니다. - Caddy는
http://접두사로 SSL을 자기가 처리하지 않아야 함을 알아야 합니다. - 운영자는 Caddy 로그로 설정 파싱 상태를 알아야 합니다.
이 경험이 AI 보고서에 없었던 이유
Episode 2에서 AI 에이전트의 채택률이 86%였다고 했습니다. 그 14%의 미채택 중 하나가 바로 Caddy HTTPS 설정이었습니다. AI는 Caddy 공식 문서의 Best Practice를 정확히 알고 있었지만, Cloudflare → Caddy → WordPress라는 실제 인프라 체인을 모른 채 권장했습니다.
장애 2(X-Forwarded-Proto)와 장애 3(respond 레이블 문법)은 AI 보고서에 아예 언급되지 않았습니다. 이런 종류의 문제는 실제로 부딪혀봐야 알 수 있습니다. 문서에 적혀있지 않고, 에러 메시지도 직관적이지 않습니다.
AI의 설계도는 출발점으로서 훌륭합니다. 하지만 프로덕션 환경의 디버깅은 여전히 사람의 영역입니다.
다음 에피소드 예고
Episode 4 — Docker + WordPress + Redis: AI가 설계한 인프라 적용기
시리즈 안내: AI 팀과 함께 기술블로그 만들기
| 회차 | 제목 | 상태 |
|---|---|---|
| 1 | Agent Teams란? 3인 AI 팀 구성기 | 발행 |
| 2 | AI 3인이 내 블로그를 진단했다 | 발행 |
| 3 | Caddy와 싸운 하루: 리다이렉트 루프 3연전 | 현재 글 |
| 4 | Docker + WordPress + Redis: AI가 설계한 인프라 적용기 | 발행 |
| 5 | 기술블로그 UX: 코드가 읽히는 블로그 | 발행 |
| 6 | AI 코드 리뷰어가 찾아낸 22개 이슈 | 발행 |
| 7 | 마크다운으로 글 쓰고 자동 발행하기 | 예정 |
| 8 | AI 팀 vs 혼자 코딩: 비용 대비 효과 분석 | 예정 |
이 글은 Claude Code를 사용하여 작성되었습니다.
프로젝트: yndl.dev