본문 바로가기
어플리케이션

Caddy란? 자동 HTTPS 시대의 웹 서버와 리버스 프록시

by forward error correction Circle 2026. 6. 5.
반응형

Caddy는 웹 서버, 정적 파일 서버, 리버스 프록시 역할을 수행하면서 HTTPS 인증서 발급·갱신을 자동화하는 Go 기반 오픈소스 서버입니다. Nginx + Certbot + Cron + Reload Hook 조합으로 운영하던 인증서 관리 부담을 줄이고, Docker·내부 도구·소규모 서비스·고객 도메인 연결 환경에서 특히 강점을 보입니다.

Ⅰ. 왜 Caddy가 필요한가?

웹 서비스를 운영할 때 가장 먼저 마주치는 문제는 애플리케이션 실행이 아니라 외부 접속 구조를 안전하게 만드는 일입니다.
서비스를 공개하려면 보통 다음 작업이 필요합니다.

도메인 연결
 → 웹 서버 설치
 → Reverse Proxy 구성
 → HTTPS 인증서 발급
 → 인증서 경로 설정
 → 인증서 자동 갱신 설정
 → 갱신 후 웹 서버 reload
 → 로그 수집
 → 장애 대응

 

기존에는 이 역할을 주로 Nginx, Apache, HAProxy가 담당했습니다.
이 방식은 안정적이지만, HTTPS 인증서 관리까지 포함하면 운영 구조가 복잡해집니다.

예를 들어 Nginx 환경에서는 보통 다음 구성이 필요합니다.

Nginx 설정 파일
Certbot 또는 acme.sh
Let's Encrypt 인증서
Cron 또는 systemd timer
갱신 후 nginx reload hook
방화벽 80/443 포트 정책
로그 수집기


이 구조에서는 다음 문제가 자주 발생합니다.

 ⅰ. 인증서가 만료되었는데 갱신 hook이 동작하지 않음
 ⅱ. 인증서는 갱신되었지만 Nginx reload가 누락됨
 ⅲ. Docker 컨테이너 재생성 후 인증서 저장 경로가 사라짐
 ⅳ. 80번 포트가 막혀 ACME HTTP-01 Challenge 실패
 ⅴ. 설정 파일 문법 오류로 웹 서버 재시작 실패
 ⅵ. 새 도메인을 추가할 때마다 반복 작업 발생
 ⅶCaddy는 이 문제에 대해 간단한 방향을 제시합니다.

 

인증서 운영은 외부 도구가 아니라 웹 서버 본체가 책임져야 합니다.

Ⅱ. Caddy 기술이란?

Caddy는 Go 언어로 작성된 오픈소스 웹 서버입니다. 정적 파일 서버, Reverse Proxy, TLS 종료, 자동 HTTPS, 로드밸런싱, 헬스체크, Admin API 등을 제공합니다. 가장 큰 특징은 Automatic HTTPS입니다.

도메인만 Caddy 서버를 바라보도록 설정되어 있다면, Caddy는 다음 작업을 자동으로 수행합니다.

 ⅰ. HTTPS 인증서 발급
 ⅱ. 인증서 갱신
 ⅲ. HTTP → HTTPS 리다이렉트
 ⅳ. TLS 기본 보안 설정
 ⅴ. OCSP Stapling 처리
 ⅵ. HTTP/2 지원
 ⅶ. HTTP/3 사용 가능 환경 구성

 즉, Caddy는 단순한 웹 서버라기보다 HTTPS 운영을 기본값으로 내장한 현대적인 Reverse Proxy라고 보는 것이 정확합니다.

Ⅲ. Caddy의 핵심 특징

ⅰ. 자동 HTTPS
 일반적인 웹 서버는 인증서를 외부 도구로 발급하고, 웹 서버는 그 인증서 파일을 읽어 사용합니다. 반면 Caddy는 인증서 발급과 갱신 과정을 자체적으로 처리합니다.

기본 방식 Caddy 방식
Certbot이 인증서 발급 Caddy가 도메인 확인
인증서 파일 생성 인증서 자동 발급
Nginx가 인증서 경로 참조 인증서 자동 저장
Cron으로 갱신 만료 전 자동 갱신
갱신 후 Nginx reload 서비스 중단 없이 적용

 

운영자 입장에서는 인증서 작업이 크게 줄어듭니다.

 ⅱ. Caddyfile 기반 간결한 설정
Caddy는 사람이 읽기 쉬운 Caddyfile을 제공합니다.

example.com {
    reverse_proxy 127.0.0.1:3000
}


이 몇 줄만으로 다음 작업을 수행할 수 있습니다.

 ⅰ. example.com 요청 수신
 ⅱ. HTTPS 인증서 자동 발급
 ⅲ. HTTP 요청을 HTTPS로 리다이렉트
 ⅳ. 백엔드 127.0.0.1:3000으로 Reverse Proxy
 ⅴ. 기본 TLS 설정 적용

Nginx에 비해 설정이 짧고 직관적입니다.

 ⅲ. Reverse Proxy 기능
Caddy는 Reverse Proxy를 기본 기능으로 제공하여 다음 서비스 앞단에 배치할 수 있습니다.

 1) Node.js
 2) Spring Boot
 3)  FastAPI
 4) Django
 5) Grafana
 6) Prometheus
 7) Jenkins
 8) GitLab
 9) Redmine
 10) Wiki 서비스
 11) 내부 관리자 페이지


예시:

grafana.example.com {
    reverse_proxy grafana:3000
}

jenkins.example.com {
    reverse_proxy jenkins:8080
}

wiki.example.com {
    reverse_proxy wiki:3000
}


 ⅳ 정적 파일 서버
Caddy는 정적 파일 서버로도 사용할 수 있습니다.

example.com {
    root * /var/www/html
    file_server
}

 

활용 예시는 다음과 같습니다.

 1) 정적 HTML 사이트
 2) 기술 문서 사이트
 3) 다운로드 서버
 4) 내부 포털
 5) SPA 배포 서버


 ⅴ. JSON 기반 Admin API
내부적으로 Caddy는 설정을 JSON 구조로 변환하여 동작합니다.

caddy adapt --config /etc/caddy/Caddyfile --pretty


이 구조 덕분에 Caddy는 API 기반 설정 변경도 가능합니다.

운영 자동화나 CI/CD 환경에서는 다음과 같은 방식도 사용할 수 있습니다.

curl -X POST http://localhost:2019/load \
  -H "Content-Type: application/json" \
  --data-binary @new-config.json

단, Admin API는 기본적으로 외부 공개용이 아닙니다.

운영 환경에서는 반드시 localhost, Unix Socket, SSH Tunnel, Sidecar 방식 등으로 제한해야 합니다.

Ⅳ. Caddy 동작 방식

 ⅰ. 기본 구성 요소

구성 요소 역할
Client 브라우저, 모바일 앱, API 호출자
DNS 도메인을 Caddy 서버 IP로 연결
Firewall / Security Group 80, 443 포트 접근 제어
Caddy HTTPS 처리, Reverse Proxy, 정적 파일 제공
Backend App 실제 애플리케이션 서비스
ACME CA 인증서 발급 기관
Log / Monitoring  접근 로그, 에러 로그, 메트릭 수집


 ⅱ. 기본 요청 흐름

사용자 브라우저
        ↓
DNS 조회
       
Caddy 서버 80/443 포트 수신
        ↓
TLS 인증서 확인
        ↓
인증서가 없으면 자동 발급
        ↓
요청 라우팅
        ↓
백엔드 애플리케이션으로 전달
        ↓
백엔드 응답 반환
        ↓
Caddy가 클라이언트에게 HTTPS 응답

Ⅴ. Caddy 구성도

 ⅰ. Caddy 기반 Reverse Proxy 구성도

 ⅱ. TLS 처리 흐름도

sequenceDiagram
    participant U as Client
    participant C as Caddy
    participant A as ACME CA
    participant B as Backend App

    U->>C: HTTPS 접속 요청
    C->>C: SNI 기준 인증서 확인

    alt 인증서 없음
        C->>A: 인증서 발급 요청
        A-->>C: 인증서 발급
        C->>C: 인증서 저장
    else 인증서 있음
        C->>C: 기존 인증서 사용
    end

    C->>B: Reverse Proxy 요청 전달
    B-->>C: 애플리케이션 응답
    C-->>U: HTTPS 응답 반환

Ⅵ. Caddy 설치 방법

 ⅰ. Ubuntu / Debian 계열

sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl

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 -y


 ⅱ. Rocky Linux / AlmaLinux / RHEL 계열

dnf install 'dnf-command(copr)' -y
dnf copr enable @caddy/caddy -y
dnf install caddy -y


 ⅲ. Docker Compose 설치 예시

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./site:/srv
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - web

  app:
    image: your-app:latest
    container_name: app
    expose:
      - "3000"
    networks:
      - web

networks:
  web:

volumes:
  caddy_data:
  caddy_config:

Docker 환경에서는 /data, /config 볼륨을 반드시 유지해야 합니다.
이 경로에는 인증서와 Caddy 운영 데이터가 저장됩니다.

Ⅶ. Caddy 사용 방법

 ⅰ. 단순한 Reverse Proxy

example.com {
    reverse_proxy 127.0.0.1:3000
}

 ⅱ. Docker Compose 서비스 프록시
Docker 컨테이너 안에서 localhost는 호스트 서버가 아니라 컨테이너 자기 자신입니다.
따라서 Docker Compose 환경에서는 서비스 이름을 사용해야 합니다.

잘못된 예:

example.com {
    reverse_proxy localhost:3000
}

올바른 예:

example.com {
    reverse_proxy app:3000
}


 ⅲ. 여러 서비스 도메인 분리

grafana.example.com {
    reverse_proxy grafana:3000
}

redmine.example.com {
    reverse_proxy redmine:3000
}

zabbix.example.com {
    reverse_proxy zabbix-web:8080
}


 ⅳ. 헬스체크 포함 Reverse Proxy

api.example.com {
    reverse_proxy app1:3000 app2:3000 {
        lb_policy least_conn
        health_uri /health
        health_interval 10s
        health_timeout 3s
        fail_duration 30s
    }
}

운영 환경에서는 백엔드 애플리케이션에 /health 같은 헬스체크 엔드포인트를 만들어 두는 것이 좋습니다.

 ⅴ. 정적 사이트 + API 분리

example.com {
    encode gzip zstd

    handle /api/* {
        reverse_proxy api:8080
    }

    handle {
        root * /srv
        try_files {path} /index.html
        file_server
    }

    log {
        output file /var/log/caddy/example.log
        format json
    }
}

Ⅷ. 자주 쓰는 명령어

명령어 설명
caddy version Caddy 버전 확인
caddy validate --config Caddyfile  설정 문법 검사
caddy adapt --config Caddyfile --pretty Caddyfile을 JSON으로 변환
caddy fmt --overwrite Caddyfile Caddyfile 포맷 정리
sudo systemctl reload caddy 무중단 설정 리로드
systemctl status caddy 서비스 상태 확인
journalctl -u caddy -f 실시간 로그 확인
caddy list-modules 빌드된 모듈 확인
caddy reverse-proxy --from :8080 --to :3000 임시 Reverse Proxy 실행
caddy file-server --listen :8080 --root /srv 임시 정적 파일 서버 실행

Ⅸ. 운영 시 주의해야 할 점

 ⅰ. DNS가 먼저 정상 여부 확인
Caddy의 자동 HTTPS는 DNS가 정상이라는 전제에서 동작합니다.

nslookup example.com
dig example.com

확인할 내용:

A 레코드가 올바른 서버 IP를 바라보는가?
CNAME이 잘못 연결되어 있지 않은가?
DNS 전파가 완료되었는가?
Cloudflare 같은 프록시 서비스가 중간에 있는가?

 

 ⅱ. 80번과 443번 포트 확인
자동 HTTPS를 사용하려면 외부에서 80번 또는 443번 포트 접근이 가능해야 합니다.

ss -lntp | grep -E ':80|:443'
firewall-cmd --list-all
ufw status


클라우드 환경에서는 다음도 함께 확인해야 합니다.

Security Group
Network ACL
Load Balancer Listener
NAT 정책
사내 방화벽 정책


 ⅲ. 인증서 저장소는 데이터처럼 관리 필요
Caddy의 /data 디렉터리는 단순 캐시가 아닙니다.

인증서, 계정 키, 관련 운영 데이터가 저장될 수 있습니다.

Docker에서 다음 볼륨은 반드시 유지해야 합니다.

volumes:
  caddy_data:
  caddy_config:

※ 이 볼륨을 삭제하면 인증서를 다시 발급해야 하며, 반복 재발급으로 인증기관의 rate limit에 걸릴 수 있습니다.


 ⅳ. Admin API는 외부에 노출하지 않는다
Caddy의 Admin API는 강력하지만, 외부 공개용이 아닙니다.

운영 원칙

1) 외부 인터넷 노출 금지
2) localhost 바인딩 권장
3) 필요 시 SSH Tunnel 사용
4) Kubernetes에서는 Sidecar 또는 Operator 방식 권장


 ⅳ. reload 전 validate 습관화

운영 환경에서는 다음 순서가 안전합니다.

1) caddy fmt --overwrite /etc/caddy/Caddyfile
2) caddy validate --config /etc/caddy/Caddyfile
3) sudo systemctl reload caddy
4) systemctl status caddy
5) journalctl -u caddy -n 50

Ⅹ. 트러블슈팅

ⅰ. HTTPS 인증서가 발급되지 않는 경우

1) 가능한 원인
  - DNS가 서버 IP를 바라보지 않음
  - 80번 포트가 차단됨
  - 443번 포트가 차단됨
  - Cloudflare SSL 모드 문제
  - 내부망 또는 폐쇄망이라 ACME 서버 접근 불가
  - 같은 도메인으로 인증서를 너무 자주 재발급함

2) 확인 명령어
  - dig example.com
  - curl -I http://example.com
  - curl -Iv https://example.com
  - journalctl -u caddy -f

3) 해결 방향
 - DNS부터 확인합니다.
 - OS 방화벽과 클라우드 보안 그룹을 함께 확인합니다.
 - CDN 뒤에 있다면 DNS-01 Challenge를 검토합니다.
 - Docker 볼륨이 사라져 인증서를 반복 발급하고 있지 않은지 확인합니다.



 ⅱ. 502 Bad Gateway가 발생하는 경우

1) 가능한 원인
 - 백엔드 애플리케이션이 실행 중이 아님
 - Caddyfile의 포트가 잘못됨
 - Docker 네트워크가 다름
 - 백엔드가 127.0.0.1에만 바인딩됨
 - 컨테이너 서비스 이름을 잘못 입력함

2) 확인 명령어
 - docker ps
 - docker logs app
 - docker network inspect web
 - curl -I http://app:3000
 - journalctl -u caddy -f

3) 해결 방향
 - Caddy 컨테이너와 백엔드 컨테이너가 같은 네트워크에 있는지 확인합니다.
 - localhost 대신 Docker Compose 서비스 이름을 사용합니다.
 - 백엔드가 0.0.0.0으로 listen 하는지 확인합니다.

 

 ⅲ. WebSocket이 끊기는 경우

1) 가능한 원인
Caddy의 Reverse Proxy는 일반적인 WebSocket을 지원합니다.
중간 방화벽, 보안 장비, L7 장비가 장시간 연결을 끊을 수 있습니다.

2) 확인할 내용
 - 백엔드 로그에 Upgrade 요청이 도달하는가?
 - Caddy 라우팅 경로가 WebSocket 경로를 포함하는가?
 - 중간 장비가 idle timeout을 짧게 잡고 있지 않은가?
 - 애플리케이션에서 Origin 제한을 걸고 있지 않은가?


 ⅳ. HTTP/3가 동작하지 않는 경우

1) 가능한 원인
HTTP/3는 UDP 443을 사용합니다.
TCP 443만 열려 있으면 HTTPS는 되지만 HTTP/3는 동작하지 않을 수 있습니다.

2) 확인할 내용
sudo ss -lunp | grep 443

3) 점검 포인트
 - UDP 443이 열려 있는가?
 - 방화벽에서 UDP 443을 허용하는가?
 - 클라우드 보안 그룹에서 UDP 443을 허용하는가?
 - 중간 네트워크 장비가 QUIC을 차단하지 않는가?

 

ⅴ. access log가 너무 커지는 경우

1) 증상
고트래픽 서비스에서 JSON access log를 파일로 남기면 디스크가 빠르게 찰 수 있습니다.

2) 대응 방법
 - logrotate 설정
 - Fluent Bit, Vector, Promtail 같은 수집기로 전달
 - Loki, Elasticsearch, OpenSearch로 중앙화
 - 불필요한 debug log 비활성화


■ Caddy와 Traefik 차이
Docker에서 Caddy와 자주 비교되는 기술은 Traefik입니다. 둘 다 Reverse Proxy 역할을 하지만 방향성이 다릅니다.

구분 Caddy Traefik
핵심 철학 간단한 설정과 자동 HTTPS 컨테이너 자동 감지와 동적 라우팅
설정 방식 Caddyfile 중심 Docker label 중심
초보자 접근성 쉬움 처음에는 복잡함
Docker 자동 감지 기본은 약함 강함
서비스  수가 적을 때 적합 약간 과할 수 있음
서비스 수가 많고 자주 변할 때 관리 부담 증가 적합
Kubernetes 친화성 가능 강함
운영 가독성 Caddyfile 하나로 명확 label이 많아지면 복잡

  
※ 추천 기준
 ⅰ. 도메인 몇 개, 내부 도구, 개인 서버, Docker Compose 중심 → Caddy 추천
 ⅱ. 컨테이너가 많고 서비스가 자주 생기고 사라짐 → Traefik 추천

반응형