본문으로 건너뛰기
AICosmus

Where tech meets the everyday — AI, fintech, swimming, and cars.

AICosmus

Where tech meets the everyday — AI, fintech, swimming, and cars.

  • 홈
  • IT기술
    • RAG
    • GRPC
    • Kotlin
    • LLM
    • 금융 IT
    • 에이전트
    • 제로Trust
    • 자동화
  • About
    • Contact
    • Terms of Service
    • Disclaimer
    • Privacy – Policy
  • 홈
  • IT기술
    • RAG
    • GRPC
    • Kotlin
    • LLM
    • 금융 IT
    • 에이전트
    • 제로Trust
    • 자동화
  • About
    • Contact
    • Terms of Service
    • Disclaimer
    • Privacy – Policy
닫기

검색

Caddy 웹서버로 자동 HTTPS를 구성한 홈서버 환경
IT기술

Caddy 웹서버 입문 — 설정 3줄로 자동 HTTPS 완성

By AICosmus
2026년 08월 01일 12 Min Read
0

홈서버를 운영하거나 사이드 프로젝트를 외부에 공개할 때, HTTPS 인증서 설정은 매번 골치 아픈 관문입니다. Nginx에 Certbot을 붙이고 cron으로 갱신 스케줄을 걸어두고, 설정 파일이 꼬이면 502 에러와 씨름하는 경험을 한 번쯤 해보셨을 겁니다. Caddy 웹서버는 이 번거로움을 근본적으로 없앱니다. 도메인 이름만 적으면 Let’s Encrypt 인증서를 자동으로 발급하고, 만료 전에 알아서 갱신합니다. 리버스 프록시 설정도 단 3줄이면 완성됩니다.

2014년 첫 릴리스 이후 꾸준히 발전해 온 Caddy는 2026년 현재 GitHub 스타 6만 개를 넘기며 홈서버와 인디 해커 커뮤니티에서 Nginx를 빠르게 대체하는 모던 웹서버로 자리 잡았습니다. 이 글에서는 Caddy의 핵심 특징부터 설치, Caddyfile 작성법, Docker Compose 멀티 서비스 리버스 프록시 구성, 실전 레시피와 트러블슈팅까지 한 번에 다룹니다. Nginx 설정에 지친 분이라면, 이 글을 읽고 나서 바로 전환을 시도해 보고 싶어질 겁니다.

Caddy 웹서버가 주목받는 5가지 이유

Caddy는 단순히 ‘또 하나의 웹서버’가 아닙니다. HTTPS를 기본값으로 만든 최초의 범용 웹서버이고, 설정 철학부터 Nginx나 Apache와 근본적으로 다릅니다. 왜 이렇게 많은 개발자가 Caddy로 갈아타고 있는지, 핵심 이유 다섯 가지를 짚어 보겠습니다.

1. 자동 HTTPS가 기본값

Caddy의 가장 강력한 차별점입니다. Caddyfile에 도메인 이름을 적는 것만으로 다음이 전부 자동으로 이루어집니다.

  • Let’s Encrypt 또는 ZeroSSL에서 TLS 인증서 자동 발급
  • HTTP(80번 포트) → HTTPS(443번 포트) 자동 리다이렉트
  • OCSP 스테이플링 자동 활성화 (인증서 유효성을 클라이언트에 미리 전달)
  • 인증서 만료 약 30일 전 자동 갱신

Nginx에서 같은 결과를 얻으려면 Certbot 설치, 인증서 발급 명령 실행, Nginx 설정 파일에 인증서 경로 추가, cron 갱신 스케줄 등록, 갱신 후 Nginx reload 훅 설정까지 다섯 단계 이상을 거쳐야 합니다. Caddy에서는 이 전체가 0단계입니다. 아무것도 하지 않아도 됩니다.

2. Caddyfile — 읽는 대로 동작하는 설정

Caddy의 설정 파일인 Caddyfile은 JSON이나 YAML이 아닌 독자적인 DSL(Domain-Specific Language)입니다. 그런데 이 DSL이 놀라울 정도로 직관적입니다. 아래는 리버스 프록시 설정 전체입니다.

app.example.com {
    reverse_proxy localhost:3000
}

이 3줄이 하는 일을 풀어 쓰면 이렇습니다. app.example.com이라는 도메인에 대해 HTTPS 인증서를 자동 발급하고, HTTP 요청은 HTTPS로 리다이렉트하며, HTTPS로 들어온 요청을 localhost:3000으로 프록시합니다. Nginx로 같은 동작을 구현하면 server 블록 두 개(HTTP 리다이렉트용, HTTPS 프록시용)에 인증서 경로, proxy_pass, proxy_set_header 등 최소 15줄이 필요합니다.

3. Go 단일 바이너리, 크로스 플랫폼

Caddy는 Go로 작성된 단일 실행 파일입니다. 외부 의존성이 전혀 없어서 바이너리 하나만 다운로드하면 어디서든 실행할 수 있습니다. Linux, macOS, Windows, FreeBSD를 공식 지원하고, ARM64(라즈베리 파이, NAS)용 빌드도 제공됩니다. 특히 Windows를 공식 1차 지원하는 점은 Nginx와의 큰 차이입니다. Nginx의 Windows 빌드는 공식이긴 하지만 프로덕션 사용을 권장하지 않는 반면, Caddy는 Windows에서도 동일한 기능과 안정성을 보장합니다.

4. 무중단 핫 리로드

설정 파일을 수정한 뒤 caddy reload 명령 한 줄이면 기존 연결을 끊지 않고 새 설정이 적용됩니다. Nginx도 nginx -s reload로 비슷한 동작을 하지만, Caddy는 설정 검증(validate)과 적용이 원자적으로 이루어져 중간에 잘못된 설정이 반영될 틈이 없습니다. 설정에 오류가 있으면 reload 자체가 거부되고 기존 설정이 그대로 유지됩니다.

5. 확장 가능한 플러그인 아키텍처

Caddy는 xcaddy라는 빌드 도구로 원하는 플러그인을 포함한 커스텀 바이너리를 만들 수 있습니다. 가장 많이 쓰이는 플러그인은 Cloudflare DNS 챌린지 모듈입니다. 이 모듈을 추가하면 포트 80을 열지 않고도 DNS 검증으로 인증서를 발급받을 수 있어, Cloudflare Tunnel 환경이나 포트포워딩이 불가능한 네트워크에서 특히 유용합니다. 그 외에도 rate limiting, IP 차단, 캐싱, 동적 DNS 업데이트 등 200개 이상의 커뮤니티 플러그인이 존재합니다.

Caddy vs Nginx — 어떤 상황에서 뭘 쓸까

Caddy 웹서버와 Nginx 기능 비교표

Caddy가 모든 면에서 Nginx보다 우월한 것은 아닙니다. 각자의 강점이 뚜렷하기 때문에 상황에 따라 선택이 달라집니다. 아래에서 핵심 항목별로 비교해 보겠습니다.

설정 복잡도

Caddy가 압도적으로 간결합니다. 같은 리버스 프록시 + HTTPS 구성을 만들 때 Caddy는 3줄, Nginx는 인증서 경로·프록시 헤더·리다이렉트 서버 블록 등을 포함해 15줄 이상이 필요합니다. 서비스가 10개, 20개로 늘어날수록 이 차이는 기하급수적으로 벌어집니다. Caddy에서는 서비스를 하나 추가할 때마다 3줄만 추가하면 되지만, Nginx에서는 매번 server 블록을 복붙하고 인증서 경로를 맞춰야 합니다.

HTTPS 자동화

Caddy는 ACME 클라이언트가 내장되어 있어 아무 설정 없이 HTTPS가 동작합니다. Nginx는 Certbot, acme.sh 같은 외부 도구에 의존합니다. 이 차이가 가장 크게 체감되는 순간은 인증서 갱신 실패입니다. Certbot의 cron이 어떤 이유로 실행되지 않으면 사이트가 만료된 인증서를 제공하게 되는데, Caddy에서는 이런 상황 자체가 발생하지 않습니다.

성능

순수 처리량 면에서 Nginx가 근소하게 앞섭니다. Nginx는 C로 작성되어 이벤트 드리븐 아키텍처에 극도로 최적화되어 있습니다. 하지만 차이가 체감되려면 초당 수만 요청 이상의 트래픽이 필요합니다. 홈서버나 중소 규모 서비스에서는 Caddy의 성능이 충분하고도 남습니다. Caddy도 Go의 goroutine 기반 동시성 모델을 활용하므로 일반적인 워크로드에서 병목이 될 일은 거의 없습니다.

커뮤니티와 생태계

Nginx는 20년 이상의 역사를 가지고 있어 Stack Overflow 답변, 블로그 글, 책이 압도적으로 많습니다. Caddy는 상대적으로 젊지만 공식 문서의 질이 뛰어나고, 커뮤니티 포럼도 활발합니다. 무엇보다 Caddy의 설정이 단순하기 때문에 검색할 일 자체가 훨씬 적습니다.

결론: 어떤 걸 선택할까

하루 수백~수만 요청 수준의 홈서버, 사이드 프로젝트, 개인 블로그, 소규모 API 서버라면 Caddy를 추천합니다. 설정 시간을 극적으로 줄여주고 HTTPS 관련 문제에서 완전히 해방됩니다. 반면 하루 수억 요청 이상의 엔터프라이즈급 트래픽을 처리하거나, OpenResty 기반 Lua 스크립팅이 필요하거나, 팀 전체가 이미 Nginx에 익숙한 조직이라면 Nginx를 유지하는 것이 합리적입니다.

설치 방법 — Docker, 패키지 매니저, 바이너리

Caddy 설치는 어떤 방법을 선택하든 5분 안에 끝납니다. 환경별로 가장 권장되는 방법을 안내합니다.

Docker (권장)

홈서버 환경에서는 Docker가 가장 깔끔합니다. 다른 셀프호스팅 서비스들과 같은 Docker 네트워크에서 통신할 수 있어 리버스 프록시 구성이 자연스럽습니다.

docker run -d --name caddy \
  -p 80:80 -p 443:443 -p 443:443/udp \
  -v $PWD/Caddyfile:/etc/caddy/Caddyfile \
  -v caddy_data:/data \
  -v caddy_config:/config \
  caddy:2

여기서 -p 443:443/udp는 HTTP/3(QUIC) 지원을 위한 것입니다. Caddy는 HTTP/3를 기본으로 활성화합니다. caddy_data 볼륨에는 발급된 인증서가 저장되므로 컨테이너를 재생성해도 인증서가 유지됩니다. 이 볼륨을 반드시 영속화하세요.

Linux (apt — Debian/Ubuntu)

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy

패키지로 설치하면 systemd 서비스로 자동 등록되어 sudo systemctl start caddy로 바로 실행할 수 있습니다.

macOS (Homebrew)

brew install caddy

Windows (Scoop 또는 Winget)

scoop install caddy

또는 Caddy 공식 다운로드 페이지에서 Windows 바이너리를 직접 받아 PATH에 등록해도 됩니다. Windows에서 서비스로 등록하려면 NSSM이나 작업 스케줄러를 활용하면 됩니다.

xcaddy로 플러그인 포함 빌드

Cloudflare DNS 챌린지 모듈처럼 기본 빌드에 포함되지 않은 플러그인이 필요하다면 xcaddy를 사용합니다.

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/caddy-dns/cloudflare

이렇게 빌드하면 Cloudflare DNS 모듈이 내장된 커스텀 Caddy 바이너리가 생성됩니다. Docker 환경에서는 멀티스테이지 빌드로 동일한 결과를 얻을 수 있는데, 이 방법은 뒤에서 자세히 다루겠습니다.

Caddyfile 기초 문법 — 설정 3줄의 비밀

Caddyfile 설정 파일 구조 다이어그램

Caddyfile은 Caddy의 핵심입니다. JSON API로도 설정할 수 있지만, 대부분의 사용자는 Caddyfile만으로 모든 것을 해결합니다. 문법이 직관적이어서 별도의 학습 없이도 바로 읽고 쓸 수 있습니다.

가장 기본 — 정적 파일 서버

:80 {
    root * /var/www/html
    file_server
}

포트 80에서 /var/www/html 디렉토리의 파일을 서빙합니다. 도메인 이름 대신 포트 번호를 쓰면 HTTPS 자동 발급이 비활성화됩니다. 로컬 개발이나 내부망 전용 서비스에 적합한 방식입니다.

리버스 프록시 — 3줄의 마법

app.example.com {
    reverse_proxy localhost:3000
}

이것이 실제로 작동하는 완전한 설정 파일입니다. 이 3줄은 다음을 전부 수행합니다.

  • app.example.com에 대한 TLS 인증서를 Let’s Encrypt에서 자동 발급
  • HTTP(80) 요청을 HTTPS(443)로 자동 리다이렉트
  • HTTPS로 들어온 요청을 localhost:3000으로 프록시
  • 프록시 시 X-Forwarded-For, X-Forwarded-Proto 등 표준 헤더 자동 추가
  • OCSP 스테이플링 활성화
  • HTTP/2 및 HTTP/3 활성화

Nginx에서 동일한 동작을 만들려면 아래와 같은 설정이 필요합니다.

# Nginx 동등 설정 (참고용)
server {
    listen 80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl http2;
    server_name app.example.com;
    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

15줄 대 3줄. 차이가 명확합니다. 그리고 Nginx 쪽은 이 설정 외에 Certbot으로 인증서를 별도로 발급하고 갱신 스케줄을 등록하는 과정까지 추가로 필요합니다.

복수 사이트 — 도메인별 블록 추가

blog.example.com {
    reverse_proxy localhost:2368
}

api.example.com {
    reverse_proxy localhost:8080
}

grafana.example.com {
    reverse_proxy localhost:3000
}

사이트를 추가할 때마다 3줄 블록만 붙이면 됩니다. 각 도메인에 대해 개별 인증서가 자동 발급됩니다.

스니펫 — 반복 설정 재사용

(common) {
    encode gzip zstd
    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
    }
}

blog.example.com {
    import common
    reverse_proxy localhost:2368
}

api.example.com {
    import common
    reverse_proxy localhost:8080
}

괄호로 감싼 이름이 스니펫입니다. import로 여러 사이트에서 재사용할 수 있어 설정의 중복을 줄여줍니다. gzip·zstd 압축이나 보안 헤더처럼 모든 사이트에 공통 적용할 설정을 스니펫으로 만들어 두면 편리합니다.

자동 HTTPS 동작 원리와 실전 요구사항

Caddy ACME 자동 HTTPS 인증서 발급 흐름도

Caddy의 자동 HTTPS는 마법이 아니라 ACME(Automatic Certificate Management Environment) 프로토콜의 구현입니다. 내부에서 어떤 일이 일어나는지 이해하면 문제가 생겼을 때 빠르게 진단할 수 있습니다.

ACME 프로토콜의 동작 흐름

  • 도메인 확인: Caddy가 시작되면 Caddyfile에 명시된 도메인 목록을 수집합니다.
  • CA 선택: 기본적으로 Let’s Encrypt를 사용하고, 실패 시 ZeroSSL로 자동 폴백합니다.
  • 도메인 소유권 검증 (챌린지): CA가 “이 도메인이 정말 당신 것인가?”를 확인하는 단계입니다.
  • 인증서 발급: 검증이 통과되면 CA가 인증서를 발급하고, Caddy가 로컬에 저장합니다.
  • 자동 갱신: 만료 약 30일 전에 같은 과정을 자동으로 반복합니다.

챌린지 방식 3가지

HTTP-01 챌린지는 가장 일반적인 방식입니다. CA가 http://도메인/.well-known/acme-challenge/토큰에 접근해서 Caddy가 올려둔 검증 파일을 확인합니다. 포트 80이 외부에서 접근 가능해야 합니다.

TLS-ALPN-01 챌린지는 포트 443에서 TLS 핸드셰이크를 통해 검증합니다. 포트 80을 열 수 없는 환경에서 대안이 됩니다.

DNS-01 챌린지는 도메인의 DNS TXT 레코드를 추가하여 소유권을 증명합니다. 포트를 전혀 열지 않아도 되고, 와일드카드 인증서(*.example.com)를 발급받을 수 있는 유일한 방법입니다. 단, Caddy에 DNS 프로바이더 플러그인(예: Cloudflare)을 추가로 빌드해야 합니다.

홈서버에서 자동 HTTPS를 쓰기 위한 전제 조건

  • 도메인: 자체 도메인이 있어야 합니다. IP 주소만으로는 공개 CA 인증서를 받을 수 없습니다.
  • DNS 설정: 도메인이 서버의 공인 IP를 가리켜야 합니다. DDNS를 쓰고 있다면 도메인의 A 레코드가 현재 공인 IP와 일치하는지 확인하세요.
  • 포트 개방: HTTP-01 챌린지를 쓸 경우 공유기에서 80번, 443번 포트포워딩이 필요합니다.

포트포워딩이 어렵거나 ISP가 80/443을 차단하는 환경이라면, Cloudflare Tunnel과 DNS-01 챌린지 조합을 사용하면 됩니다. 이 방법은 아래 실전 레시피 섹션에서 자세히 다룹니다.

내부망 전용 — 로컬 CA로 자체 인증서 발급

외부에 공개하지 않는 내부 서비스에도 HTTPS를 쓰고 싶다면 Caddy의 내부 CA 기능을 활용할 수 있습니다.

nas.home.local {
    tls internal
    reverse_proxy 192.168.1.100:5000
}

tls internal을 지정하면 Caddy가 자체 루트 CA를 생성하고 그 CA로 인증서를 발급합니다. 브라우저에 이 루트 CA를 신뢰하도록 등록하면(Caddy가 caddy trust 명령으로 자동화해 줍니다) 내부망에서도 경고 없이 HTTPS를 사용할 수 있습니다. 홈 NAS의 관리 UI나 내부 대시보드에 활용하기 좋습니다.

Docker Compose로 멀티 서비스 리버스 프록시 구성하기

Docker Compose Caddy 리버스 프록시 구성도

홈서버를 운영하다 보면 Uptime Kuma, Grafana, Immich, Jellyfin 같은 서비스를 여러 개 띄우게 됩니다. Caddy를 Docker Compose로 구성하면 이 서비스들에 대한 리버스 프록시를 한 파일로 관리할 수 있습니다.

docker-compose.yml 전체 예시

services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - proxy

  uptime-kuma:
    image: louislam/uptime-kuma:1
    restart: unless-stopped
    volumes:
      - uptime_data:/app/data
    networks:
      - proxy

  homepage:
    image: ghcr.io/gethomepage/homepage:latest
    restart: unless-stopped
    volumes:
      - ./homepage:/app/config
    networks:
      - proxy

  vaultwarden:
    image: vaultwarden/server:latest
    restart: unless-stopped
    volumes:
      - vw_data:/data
    environment:
      - DOMAIN=https://vault.example.com
    networks:
      - proxy

volumes:
  caddy_data:
  caddy_config:
  uptime_data:
  vw_data:

networks:
  proxy:

대응하는 Caddyfile

status.example.com {
    reverse_proxy uptime-kuma:3001
}

home.example.com {
    reverse_proxy homepage:3000
}

vault.example.com {
    reverse_proxy vaultwarden:80
}

핵심은 Docker 네트워크입니다. 모든 서비스가 같은 proxy 네트워크에 속해 있으므로 Caddy는 서비스 이름(uptime-kuma, homepage, vaultwarden)을 호스트명으로 직접 사용할 수 있습니다. localhost 대신 컨테이너 이름을 쓰는 것이 Docker 환경에서의 핵심 포인트입니다.

기존 Docker Compose에 Caddy를 추가하는 경우

이미 각 서비스가 개별 docker-compose.yml로 돌아가고 있다면, 외부 네트워크를 활용하세요.

# 먼저 외부 네트워크 생성
docker network create proxy

각 서비스의 docker-compose.yml에 다음을 추가합니다.

networks:
  proxy:
    external: true

그리고 해당 서비스를 이 네트워크에 연결합니다. Caddy의 docker-compose.yml도 같은 외부 네트워크를 사용하면 모든 서비스와 통신할 수 있습니다.

영속 볼륨이 중요한 이유

caddy_data 볼륨에는 발급된 인증서, 개인키, ACME 계정 정보가 저장됩니다. 이 볼륨을 잃으면 인증서를 처음부터 다시 발급받아야 하고, Let’s Encrypt의 rate limit(주당 도메인당 5회)에 걸릴 수 있습니다. Docker Compose를 재구성할 때 이 볼륨은 반드시 보존하세요. 백업 대상에도 포함시키는 것을 권장합니다.

실전 설정 레시피 7가지

기본 리버스 프록시 외에 실무에서 자주 필요한 설정들을 모아 봤습니다. 필요한 것만 골라 Caddyfile에 붙여 쓰시면 됩니다.

레시피 1: Basic Auth로 접근 제한

admin.example.com {
    basicauth {
        admin $2a$14$Zkq...(해시값)
    }
    reverse_proxy localhost:8080
}

비밀번호 해시는 caddy hash-password 명령으로 생성합니다. 터미널에서 비밀번호를 입력하면 bcrypt 해시가 출력되므로 이를 Caddyfile에 붙여 넣으면 됩니다. 관리자 대시보드처럼 민감한 서비스에 1차 방어선을 추가할 때 유용합니다.

레시피 2: 보안 헤더 일괄 적용

(security) {
    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        Referrer-Policy strict-origin-when-cross-origin
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        -Server
    }
}

app.example.com {
    import security
    reverse_proxy localhost:3000
}

-Server는 Caddy의 응답 헤더에서 서버 식별 정보를 제거합니다. 스니펫으로 만들어 두면 모든 사이트에 import security 한 줄로 적용할 수 있습니다.

레시피 3: 액세스 로그 파일 출력

app.example.com {
    log {
        output file /var/log/caddy/app.log {
            roll_size 10mb
            roll_keep 5
            roll_keep_for 720h
        }
        format json
    }
    reverse_proxy localhost:3000
}

JSON 형식 로그를 파일로 출력하고, 10MB마다 롤링하며 최대 5개 파일을 30일간 보관합니다. jq나 Grafana Loki 같은 도구로 분석할 때 JSON 포맷이 편리합니다.

레시피 4: www 리다이렉트

www.example.com {
    redir https://example.com{uri} permanent
}

example.com {
    reverse_proxy localhost:3000
}

www.example.com으로 들어온 요청을 example.com으로 301 리다이렉트합니다. 반대 방향도 동일하게 가능합니다. SEO에서 중복 콘텐츠 이슈를 방지하려면 한쪽으로 통일하는 것이 중요합니다.

레시피 5: 업스트림 로드 밸런싱

app.example.com {
    reverse_proxy localhost:3001 localhost:3002 localhost:3003 {
        lb_policy round_robin
        health_uri /healthz
        health_interval 10s
    }
}

여러 백엔드 인스턴스에 라운드 로빈으로 트래픽을 분산합니다. health_uri를 설정하면 헬스체크에 실패한 인스턴스는 자동으로 제외됩니다. 정책으로는 round_robin, least_conn, random, ip_hash 등을 선택할 수 있습니다.

레시피 6: Cloudflare DNS 챌린지 — 포트 개방 없는 HTTPS

포트포워딩이 불가능한 환경에서 HTTPS를 사용하는 방법입니다. Cloudflare API로 DNS TXT 레코드를 자동 조작하여 인증서를 발급받습니다.

먼저 Cloudflare DNS 모듈이 포함된 커스텀 Docker 이미지를 빌드합니다.

# Dockerfile.caddy
FROM caddy:2-builder AS builder
RUN xcaddy build --with github.com/caddy-dns/cloudflare

FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

docker-compose.yml에서 이미지 대신 빌드를 사용합니다.

services:
  caddy:
    build:
      context: .
      dockerfile: Dockerfile.caddy
    environment:
      - CLOUDFLARE_API_TOKEN=your-api-token-here
    # ... (나머지 동일)

Caddyfile에서 와일드카드 도메인을 설정합니다.

*.example.com {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }

    @grafana host grafana.example.com
    handle @grafana {
        reverse_proxy grafana:3000
    }

    @api host api.example.com
    handle @api {
        reverse_proxy api-server:8080
    }

    handle {
        respond "Not Found" 404
    }
}

이렇게 하면 하나의 와일드카드 인증서로 모든 서브도메인을 커버할 수 있습니다. 서비스를 추가할 때 DNS에 CNAME만 추가하면 되고, 인증서를 새로 발급할 필요가 없습니다. Cloudflare API 토큰은 Cloudflare 대시보드에서 Zone:DNS:Edit 권한으로 생성하면 됩니다.

레시피 7: WebSocket 프록시

ws.example.com {
    reverse_proxy localhost:6001
}

Caddy는 WebSocket 프록시를 별도 설정 없이 자동으로 처리합니다. Nginx에서는 proxy_http_version 1.1, proxy_set_header Upgrade, proxy_set_header Connection "upgrade" 등을 명시해야 하지만, Caddy는 클라이언트의 Upgrade 헤더를 감지하면 알아서 WebSocket 연결을 프록시합니다.

트러블슈팅 FAQ — 자주 만나는 문제와 해결법

Caddy는 설정이 단순한 만큼 문제도 명확합니다. 자주 발생하는 상황과 해결법을 정리합니다.

인증서 발급이 실패합니다

가장 흔한 원인 세 가지입니다.

  • DNS가 서버 IP를 가리키지 않음: nslookup 도메인으로 DNS 확인. A 레코드가 서버의 공인 IP와 일치해야 합니다.
  • 포트 80이 차단됨: HTTP-01 챌린지는 외부에서 포트 80에 접근할 수 있어야 합니다. 공유기 포트포워딩, ISP의 80 포트 차단 여부를 확인하세요.
  • 다른 프로세스가 포트 80을 사용 중: Nginx, Apache 등이 이미 80번 포트를 점유하고 있으면 Caddy가 바인딩에 실패합니다. 기존 웹서버를 중지하거나 Caddy의 HTTP 포트를 변경하세요.

디버깅 시 caddy run(포그라운드 실행)으로 로그를 실시간 확인하면 원인을 빠르게 파악할 수 있습니다.

설정 파일 문법 오류를 확인하고 싶습니다

caddy validate --config /etc/caddy/Caddyfile

이 명령은 Caddyfile의 문법을 검사하고 오류가 있으면 위치와 메시지를 출력합니다. reload 전에 항상 validate를 먼저 실행하는 습관을 들이면 실수를 방지할 수 있습니다.

Let’s Encrypt rate limit에 걸렸습니다

Let’s Encrypt는 동일 도메인에 대해 주당 5회까지만 인증서를 발급합니다. 테스트 단계에서는 Caddyfile의 글로벌 옵션에서 스테이징 CA를 사용하세요.

{
    acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}

스테이징 CA에서 발급된 인증서는 브라우저에서 신뢰되지 않지만, rate limit이 훨씬 느슨해서 테스트에 적합합니다. 설정이 확정되면 이 줄을 제거하고 프로덕션 CA로 전환하면 됩니다.

Caddy를 Cloudflare 프록시(주황색 구름)와 함께 쓸 수 있나요

가능합니다. 다만 주의점이 있습니다. Cloudflare 프록시가 활성화되면 Let’s Encrypt의 HTTP-01 챌린지가 Cloudflare의 엣지에서 가로채져 Caddy에 도달하지 못할 수 있습니다. 이 경우 DNS-01 챌린지를 사용하거나, Cloudflare의 SSL 모드를 Full(strict)로 설정하고 Cloudflare Origin CA 인증서를 Caddy에 수동 설치하는 방법이 있습니다.

Windows에서 서비스로 상시 실행하고 싶습니다

NSSM(Non-Sucking Service Manager)을 사용하면 Caddy를 Windows 서비스로 등록할 수 있습니다.

nssm install Caddy "C:\caddy\caddy.exe" run --config "C:\caddy\Caddyfile"
nssm start Caddy

또는 Windows 작업 스케줄러에서 시스템 시작 시 자동 실행하도록 등록해도 됩니다.

Nginx Proxy Manager에서 Caddy로 마이그레이션

기존에 Nginx Proxy Manager(NPM)를 사용 중이라면 Caddy로 전환하는 것이 어렵지 않습니다. NPM의 GUI에서 설정한 Proxy Host 목록을 보고, 각 항목을 Caddyfile의 3줄 블록으로 변환하면 됩니다.

NPM에서 Caddy로 전환할 때의 실질적인 장점은 세 가지입니다.

  • 설정의 버전 관리: Caddyfile은 텍스트 파일이므로 Git으로 변경 이력을 관리할 수 있습니다. NPM은 SQLite DB에 설정을 저장해서 diff가 불가능합니다.
  • 자동화 친화적: CI/CD 파이프라인에서 Caddyfile을 생성하거나 수정한 뒤 caddy reload로 적용하는 것이 자연스럽습니다.
  • 리소스 절약: NPM은 Nginx + Node.js 관리 앱 + MariaDB/SQLite로 구성되어 메모리를 상당히 사용합니다. Caddy는 단일 바이너리로 NPM과 동일한 기능을 훨씬 적은 리소스로 수행합니다.

마이그레이션 순서는 간단합니다. NPM의 Proxy Host 목록을 하나씩 Caddyfile에 옮기고, DNS가 Caddy의 IP를 가리키도록 변경한 뒤, NPM 컨테이너를 중지하고 Caddy를 시작하면 됩니다. 인증서는 Caddy가 자동으로 새로 발급하므로 기존 인증서를 이전할 필요가 없습니다.

마무리 — 설정에 시간 쓰지 말고, 만드는 데 시간을 쓰세요

Caddy 웹서버의 핵심 가치는 단순합니다. 웹서버 설정이라는 반복적이고 오류가 잦은 작업을 최소화해서, 개발자가 정작 중요한 일에 집중하게 해 주는 것입니다. 3줄로 리버스 프록시가 완성되고, HTTPS는 도메인을 적는 순간 자동으로 켜집니다.

물론 모든 상황에 Caddy가 정답은 아닙니다. 대규모 트래픽 환경, 레거시 시스템과의 호환, 팀 전체의 기존 지식 등을 고려해야 합니다. 하지만 개인 홈서버, 사이드 프로젝트, 소규모 서비스라면 Caddy가 가져다주는 시간 절약은 압도적입니다.

오늘 소개한 Caddyfile 예시들은 모두 복사해서 바로 사용할 수 있습니다. 기존에 Nginx나 NPM으로 고생하고 계셨다면, 이번 주말에 테스트 환경에서 Caddy를 한번 띄워 보시기 바랍니다. 설정 파일의 간결함에 놀라고, HTTPS가 아무것도 하지 않았는데 동작하는 경험에 다시 한번 놀라게 될 겁니다.

자주 묻는 질문

Caddy 웹서버는 HTTPS 인증서를 어떻게 자동으로 설정하나요?

Caddyfile에 도메인 이름만 적으면 Let’s Encrypt 또는 ZeroSSL에서 TLS 인증서를 자동 발급하고, HTTP를 HTTPS로 리다이렉트하며, 만료 약 30일 전에 자동 갱신까지 처리합니다. Nginx처럼 Certbot 설치, 인증서 경로 설정, cron 갱신 스케줄 등록 같은 별도 작업이 전혀 필요 없습니다.

Caddy와 Nginx 설정 파일 차이가 얼마나 되나요?

Caddy는 리버스 프록시 설정이 도메인, 중괄호, reverse_proxy 지시어 단 3줄이면 완성됩니다. 같은 동작을 Nginx로 구현하면 HTTP 리다이렉트용과 HTTPS 프록시용 server 블록 두 개에 인증서 경로, proxy_pass, proxy_set_header 등 최소 15줄이 필요합니다.

Caddy는 라즈베리 파이나 NAS 같은 ARM 장치에서도 사용할 수 있나요?

네, Caddy는 Go로 작성된 단일 바이너리로 외부 의존성이 없으며, Linux, macOS, Windows, FreeBSD는 물론 ARM64(라즈베리 파이, NAS)용 빌드도 공식 제공됩니다. 바이너리 하나만 다운로드하면 어디서든 실행할 수 있습니다.


🔗 함께 보면 좋은 글

  • [Claude 활용 24회 — AI에게 일을 위임하는 법] 1/24화: Claude 활용, 2026년 7월 판이 바뀐 4가지 이유
  • [온프레미스 AI Assistant 아키텍처 — Qwen3·Qwen3-VL 14일 설계] 14/14화: AI Assistant 레퍼런스 아키텍처 7계층 종합 설계 2026
  • [opencode 시즌 2 심화 — 나만의 도메인 특화 에이전트 만들기] 2/12화: opencode 커스텀 에이전트 만들기 2가지 방법 — JSON vs Markdown 완전 비교 (2026)
  • Lazygit 사용법 총정리 — 8가지 핵심 기능과 실전 가이드

Tags:

CaddyDockerHTTPS리버스프록시홈서버
작성자

AICosmus

Follow Me
다른 기사
AI 시대 개발자의 작업 환경
Previous

[Claude 활용 24회 — AI에게 일을 위임하는 법] 1/24화: Claude 활용, 2026년 7월 판이 바뀐 4가지 이유

opencode 시스템 프롬프트로 에이전트 정체성 빚기
Next

[opencode 시즌 2 심화 — 나만의 도메인 특화 에이전트 만들기] 3/12화: opencode 시스템 프롬프트 설계 5단계 — 에이전트 정체성 빚는 법 (2026)

댓글 없음! 첫 댓글을 남겨보세요.

답글 남기기 응답 취소

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

최신 글

  • 디지털자산 뉴스 4선 — 2026년 9월 12일, 입법과 인프라가 동시에 다음 단계로 넘어가다
  • AI 트렌드 뉴스 5선 — 2026년 9월 11일, 자본과 규제가 같은 속도로 달린다
  • 디지털자산 뉴스 4선 — 2026년 9월 10일, 은행과 빅테크가 같은 날 스테이블코인 인프라를 가동하다
  • AI 트렌드 뉴스 4선 — 2026년 9월 둘째 주, 수학 난제부터 반도체 현장까지 AI가 증명을 시작했다
  • 디지털자산 뉴스 4선 — 2026년 9월 8일, $320M 해킹과 CBDC 실거래가 같은 주에 터지다

최신 댓글

  1. 디지털자산 뉴스 4선 — 2026년 9월 10일, 은행과 빅테크가 같은 날 스테이블코인 인프라를 가동하다의 디지털자산 뉴스 4선 — 2026년 9월 12일, 입법과 인프라가 동시에 다음 단계로 넘어가다 - AICosmus
  2. AI 트렌드 뉴스 5선 — 2026년 9월 11일, 자본과 규제가 같은 속도로 달린다의 디지털자산 뉴스 4선 — 2026년 9월 12일, 입법과 인프라가 동시에 다음 단계로 넘어가다 - AICosmus
  3. AI 트렌드 뉴스 4선 — 2026년 9월 둘째 주, 수학 난제부터 반도체 현장까지 AI가 증명을 시작했다의 AI 트렌드 뉴스 5선 — 2026년 9월 11일, 자본과 규제가 같은 속도로 달린다 - AICosmus
  4. 디지털자산 뉴스 4선 — 2026년 9월 8일, $320M 해킹과 CBDC 실거래가 같은 주에 터지다의 디지털자산 뉴스 4선 — 2026년 9월 10일, 은행과 빅테크가 같은 날 스테이블코인 인프라를 가동하다 - AICosmus
  5. 디지털자산 뉴스 4선 — 2026년 9월 10일, 은행과 빅테크가 같은 날 스테이블코인 인프라를 가동하다의 AI 트렌드 뉴스 5선 — 2026년 9월 11일, 자본과 규제가 같은 속도로 달린다 - AICosmus
  • About
  • Contact
  • Disclaimer
  • Privacy - Policy
  • Terms of Service
Copyright 2026 — AICosmus. All rights reserved. Blogsy WordPress Theme