Caddy 웹서버의 자동 HTTPS 및 리버스 프록시 아키텍처를 추상적으로 묘사한 모던 그래픽 일러스트

도대체 Caddy가 뭐길래 Nginx를 대체할까? Caddy 웹서버 핵심 개념부터 실전 사용법까지 완벽 정리

Caddy 웹서버란 무엇인가

웹 애플리케이션(React, Node.js, Django 등)을 다 만들고 나면 배포 단계에서 흔히 막히는 지점이 나옵니다. 도메인을 연결하고 HTTPS를 적용하려면 기존에는 Nginx를 설치하고, Let’s Encrypt를 연동하고, Certbot으로 인증서를 발급받아 주기적으로 갱신해 주는 파이프라인을 직접 구축해야 했습니다. 저도 사이드 프로젝트를 올릴 때마다 이 구간에서 시간을 많이 썼습니다. 앱 로직은 이미 끝났는데, 인증서 갱신 cron이 깨지거나 Nginx 설정 중괄호 하나를 빼먹어 밤늦게 서버에 붙어 있던 경험이 한두 번이 아닙니다.

Caddy는 이 과정을 텍스트 파일 몇 줄로 줄여주는, Go 언어로 작성된 웹 서버입니다. Caddy 공식 홈페이지에 설명된 대로 설정의 복잡함을 낮추면서 성능과 편의성을 함께 챙긴 덕분에, 2026년 현재 Nginx의 대안으로 자주 언급됩니다. 이 글은 SSL 설정과 Nginx 설정 때문에 애먹던 개발자를 대상으로, Caddy가 정확히 어떤 원리로 작동하고 실무에서 Nginx 대신 어떻게 쓰는지를 공식 문서 링크와 함께 정리한 것입니다. 핵심만 먼저 말하면, Caddy는 별도의 수동 설정 없이 파일 한 줄만으로 SSL 인증서 발급, 리버스 프록시, 정적 파일 서빙을 처리하는 자동 HTTPS 지원 웹 서버입니다.

다만 “Caddy가 Nginx를 완전히 대체한다”고 단정하진 않겠습니다. 아래에서 쓰는 기준은 제 쪽 경험에 가깝습니다. 트래픽이 크지 않은 SaaS 랜딩·API 게이트웨이·사이드 프로젝트 MVP라면 Caddy로 충분했고, 레거시 모듈이나 특수한 Nginx 설정이 이미 팀에 쌓여 있다면 무리하게 갈아탈 필요는 없었습니다.

Caddy가 해결하는 문제

인프라 지식이 많지 않은 개발자에게는 ‘웹 서버’, ‘리버스 프록시’ 같은 용어가 추상적으로 느껴지기 쉽습니다. Caddy가 실제로 하는 일을 세 가지로 나눠 설명하겠습니다.

웹 서버 역할: 요청을 받아 파일을 돌려준다

사용자가 브라우저 주소창에 https://example.com을 입력하면, 그 요청을 서버에서 가장 먼저 받는 것이 Caddy입니다. 사용자가 요청한 경로를 확인해 서버 디스크에 있는 웹 페이지 파일(HTML, CSS, 이미지 등)을 찾아 브라우저로 돌려줍니다.

자동 HTTPS: 인증서를 스스로 발급하고 갱신한다

오늘날 대부분의 웹사이트에서 HTTPS(보안 연결)는 선택이 아니라 필수입니다. Caddy는 Caddy 공식 문서: Automatic HTTPS에 나온 대로 Let’s Encrypt 또는 ZeroSSL과 백그라운드에서 통신해 무료 SSL 인증서를 스스로 발급받고 3개월마다 갱신합니다. 인증서 만료일마다 갱신 명령어를 치거나 크론탭(crontab) 스크립트를 짤 필요가 없습니다.

제가 체감한 이득은 “발급이 빠르다”보다 “갱신을 잊어도 사이트가 안 죽는다” 쪽이었습니다. Certbot + cron 조합은 처음엔 잘 돌아가다가, OS 업그레이드나 권한 변경 뒤 조용히 실패하는 경우가 있었습니다. Caddy는 그 실패 지점을 서버 프로세스 안으로 끌어들여 운영 부담을 줄여 줍니다.

리버스 프록시 역할: 외부 포트와 내부 포트를 연결한다

백엔드를 개발하면 보통 Node.js는 3000번, Python Django는 8000번 같은 내부 포트에서 프로그램이 돌아갑니다. 하지만 외부 사용자는 80번(HTTP)이나 443번(HTTPS) 포트로만 접속할 수 있습니다. Caddy는 443번 포트로 들어온 요청을 서버 내부의 3000번이나 8000번 포트로 전달해 주는 역할을 합니다. 1인 개발 환경에서는 “프론트 빌드 결과물 + API 한 개”를 한 도메인에 묶는 일이 대부분인데, 이 패턴에서 Caddyfile이 특히 짧게 끝납니다.

Caddy와 Nginx 비교

인프라 분야에서 오래 쓰여 온 Nginx와, 비교적 최근 주목받는 Caddy의 차이를 표로 정리했습니다.

비교 항목 Nginx Caddy 웹서버
HTTPS (SSL) 설정 Certbot 등 외부 도구 설치 및 갱신 주기(cron) 수동 세팅 필요 완전 자동화 (설정 없이 기본 탑재)
설정 파일 난이도 구조가 다소 복잡하고 문법이 길어 가독성이 낮음 (약 수십 줄 필요) Caddyfile 형식으로 단 몇 줄이면 세팅 종료
서버 아키텍처 다양한 모듈과 런타임 라이브러리 의존성 존재 의존성 없는 단일 Go 정적 바이너리 파일
HTTP/3 지원 별도 컴파일 및 패치가 필요하거나 설정이 까다로움 기본적으로 활성화되어 제공
생태계·레퍼런스 질문·예제·채용 시장 모두 압도적으로 많음 공식 문서 품질은 좋지만, 사내 레거시 예제는 Nginx 쪽이 많음
플러그인·특수 모듈 성숙한 모듈 생태계 (Lua, 복잡한 rewrite 등) 필요한 기능은 많지만, 일부(레이트리밋 등)는 커스텀 빌드가 필요할 수 있음

언제 Caddy를 쓰고, 언제 Nginx를 남길까

도구 비교 글에서 빠지기 쉬운 함정은 “새 도구가 무조건 낫다”로 끝나는 것입니다. 제 기준으로는 아래처럼 갈랐습니다.

Caddy를 먼저 쓰는 경우

  • 혼자 또는 소규모로 VPS(예: 2GB 램 Ubuntu)에 서비스를 올리고, HTTPS를 빨리 켜야 할 때
  • 도메인이 몇 개 안 되고, 리버스 프록시 + 정적 파일 + SPA fallback 정도면 충분할 때
  • 인증서 갱신 실패로 새벽에 깨고 싶지 않을 때
  • Docker Compose로 앱·DB·프록시를 한 번에 올리고, 설정 파일을 짧게 유지하고 싶을 때

Nginx를 남겨 두는(또는 처음부터 Nginx를 고르는) 경우

  • 이미 팀에 Nginx 설정·모니터링·런북이 깊게 자리 잡은 경우
  • 특수한 모듈, 복잡한 rewrite, OpenResty/Lua 기반 로직이 필요한 경우
  • 클라우드 로드밸런서나 매니지드 인증서(예: AWS ALB + ACM)가 이미 HTTPS를 맡고 있어, 엣지에서의 자동 HTTPS가 큰 이득이 아닐 때
  • 규정·감사 때문에 “검증된 스택만” 쓰라는 제약이 있을 때

한 줄로 줄이면, 운영 인력이 적고 HTTPS·프록시가 병목이면 Caddy, 조직 표준과 특수 모듈이 병목이면 Nginx입니다. 둘을 동시에 돌릴 수도 있지만, 같은 포트(80/443)를 두고 싸우게 되면 디버깅만 어려워지니 한 서버에서는 하나를 정하는 편이 낫습니다.

서버를 직접 관리하는 것 자체가 부담이라면 VPS 대신 PaaS로 가는 선택지도 있습니다. Railway처럼 도메인을 연결하면 인증서를 플랫폼이 알아서 발급해 주는 곳에서는 Caddy를 따로 둘 필요가 없는 경우가 많습니다. 대신 사용량 과금이라 비용 한도를 먼저 걸어야 하는데, 그 설정은 Railway 요금과 사용법 2026에 정리했습니다.

Caddy 설치 및 기본 명령어

Caddy는 다양한 운영체제와 배포 방식을 지원합니다. 세부 사항은 Caddy 공식 설치 가이드에서 확인할 수 있으며, 여기서는 Debian/Ubuntu Linux 계열 서버를 기준으로 설명합니다.

설치 패키지 추가 및 다운로드

안정적인 최신 패키지를 받기 위해 Caddy 공식 리포지토리를 시스템에 등록하고 패키지를 설치합니다.

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

서비스 구동 및 제어 명령어

설치가 끝나면 Caddy 서비스가 시스템 데몬(systemd)으로 자동 등록됩니다. Caddy Getting Started 가이드를 참고하면, CLI를 통해 가동 상태를 쉽게 제어할 수 있습니다.

sudo systemctl start caddy

sudo systemctl status caddy

caddy reload --config /etc/caddy/Caddyfile

실무에서는 reload를 습관처럼 쓰는 편이 좋습니다. 설정만 바꾸고 프로세스를 완전히 재시작하면 잠깐의 단절이 생길 수 있고, 문법 오류가 있어도 reload 단계에서 바로 잡히는 경우가 많습니다. 저는 Caddyfile을 고친 뒤 항상 caddy validate --config /etc/caddy/Caddyfile을 한 번 돌리고 reload합니다.

자주 쓰는 Caddyfile 패턴 세 가지

Caddy는 /etc/caddy/Caddyfile 설정 파일을 수정해 원하는 인프라 구성을 만듭니다. 더 많은 활용 예시는 Caddyfile 공통 패턴 문서에서 확인할 수 있습니다.

1) 리버스 프록시(Reverse Proxy) 패턴

외부 사용자가 example.com 도메인으로 들어오면, 컴퓨터 내부 8080번 포트에서 돌고 있는 백엔드 앱으로 연결하는 구성입니다. 리버스 프록시 퀵스타트 가이드의 기본 형식을 따르고 있습니다. 이 2줄만 적으면 도메인 연결과 SSL(HTTPS) 인증서 발급이 동시에 처리됩니다.

example.com {
    reverse_proxy localhost:8080
}

여기서 자주 놓치는 점은 DNS입니다. 도메인의 A(또는 AAAA) 레코드가 이 서버 IP를 가리키지 않으면 자동 HTTPS 발급이 실패합니다. Caddy가 “인증서를 못 받는다”고 할 때 저는 앱 코드보다 DNS TTL과 80/443 포트 개방부터 확인합니다. n8n 같은 자동화 도구를 VPS에 직접 올릴 때도 이 패턴을 그대로 쓰는데, 클라우드와 비용을 비교한 내용은 n8n 셀프호스팅 vs 클라우드 요금 비교에 정리해 두었습니다.

2) 정적 파일 서빙(Static File Server) 패턴

회사 랜딩 페이지나 React 빌드 결과물(index.html, JS, 이미지 등)을 다이렉트로 호스팅할 때 사용하는 방법입니다.

static.example.com {
    # 정적 파일이 위치한 디렉토리 경로 지정
    root * /var/www/html

    # zstd 및 gzip 압축 인코딩 전송 활성화
    encode zstd gzip

    # 정적 파일 서버 기동
    file_server
}

3) SPA(Single Page Application) 배포 및 API 분기 패턴

React, Vue 등의 프론트엔드 라우팅 구조와 API 통신 경로를 단일 도메인에서 나눠 배포할 때 사용하는 구성입니다.

app.example.com {
    # 1. API 요청(/api/*)은 백엔드 포트로 전달
    handle /api/* {
        reverse_proxy localhost:8080
    }

    # 2. 그 외 모든 일반 요청은 프론트엔드 정적 파일 대응
    handle {
        root * /var/www/frontend

        # 파일이 없을 경우 index.html로 넘겨 브라우저가 라우팅을 맡도록 함 (SPA 대응)
        try_files {path} /index.html
        file_server
    }
}

API와 프론트를 다른 서브도메인으로 나누는 방식도 가능하지만, 쿠키·CORS 설정이 늘어납니다. 초반에는 한 도메인에서 path로 가르는 편이 디버깅이 쉽습니다. 트래픽과 팀이 커지면 그때 분리해도 늦지 않습니다.

실전에서 자주 깨지는 지점(실패 모드)

문서만 보면 “두 줄이면 끝”처럼 보이지만, 실제로는 아래 지점에서 막히는 경우가 많았습니다. 출시 전에 체크리스트로 쓰면 좋습니다.

1) 80·443 포트가 이미 점유됨
같은 머신에 Nginx, Apache, 다른 Caddy 인스턴스가 떠 있으면 바인딩에 실패합니다. sudo ss -tlnp | grep -E ':80|:443'로 누가 잡고 있는지 먼저 확인하세요.

2) 방화벽·클라우드 보안그룹
서버 안에서 Caddy가 정상이어도, AWS 보안그룹이나 ufw에서 80/443이 막혀 있으면 Let’s Encrypt HTTP-01 챌린지가 실패합니다. “로컬 curl은 되는데 외부에서만 안 된다”면 거의 여기입니다.

3) Cloudflare 등 CDN의 프록시 모드
주황색 구름(프록시 ON) 상태에서 인증서 발급 방식이 꼬일 수 있습니다. DNS만 먼저 맞추고(회색 구름), 인증서가 안정된 뒤 CDN을 켜거나, CDN 쪽 Full(strict) SSL 설정을 맞춰야 합니다. 이중으로 HTTPS를 종료하면 브라우저엔 정상이어도 오리진 로그가 이상해 보이는 경우가 있습니다.

4) 내부 앱이 localhost가 아닌 다른 인터페이스에 바인딩
Docker 네트워크나 0.0.0.0 vs 127.0.0.1 문제로 reverse_proxy가 502를 냅니다. 컨테이너라면 localhost 대신 서비스 이름이나 호스트 게이트웨이 주소를 써야 할 때가 있습니다.

5) 권한과 로그 디렉터리
/var/log/caddy를 만들어 두지 않거나 권한이 없으면 로그 설정만으로 reload가 실패합니다. 설정은 맞는데 서비스가 inactive로 남는다면 journalctl부터 보는 습관이 필요합니다.

배포 전 체크리스트 (혼자 올릴 때)

제가 사이드 프로젝트나 작은 SaaS를 VPS에 올릴 때 쓰는 순서입니다. 거창한 런북은 아니고, 실수 방지용입니다.

  1. 도메인 A 레코드가 서버 IP를 가리키는지 확인한다 (전파 여부는 dig 또는 온라인 DNS 체크로).
  2. 방화벽/보안그룹에서 TCP 80, 443을 연다.
  3. 앱이 내부 포트에서 정상 응답하는지 curl -I http://127.0.0.1:8080으로 확인한다.
  4. Caddyfile을 작성한 뒤 caddy validate → caddy reload 순서로 적용한다.
  5. 브라우저와 curl -I https://도메인으로 인증서·리다이렉트를 확인한다.
  6. 보안 헤더 스니펫과 접속 로그를 켠다 (아래 절).
  7. 인증서 자동 갱신이 돌아가는지 며칠 뒤 로그를 한 번 본다. 초기에 이상하면 그때 잡기 쉽습니다.

이 일곱 단계를 건너뛰고 “일단 배포”를 하면, 문제의 원인이 앱인지 인프라인지 구분하는 데만 한두 시간이 사라집니다. 1인 창업에서는 그 한두 시간이 곧 기능 하나입니다.

프로덕션 운영을 위한 보안 조치와 로깅

Caddy가 많은 부분을 자동으로 처리해 주더라도, 서비스를 실제로 운영하는 단계에서는 보안성과 가시성을 위해 다음 설정을 챙겨야 합니다.

공통 보안 헤더 설정(스니펫 기법)

반복되는 보안 설정을 스니펫으로 묶어 여러 가상 호스트에 주입해 줍니다. 클릭재킹 방지 및 원치 않는 MIME 타입 분석 회피를 설정합니다.

(security-headers) {
    header {
        # HSTS 강제 적용 (1년간 HTTPS 유지)
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

        # 브라우저 오동작 및 악성 스크립트 실행 차단 (nosniff)
        X-Content-Type-Options nosniff

        # Clickjacking 방지 (동일 도메인에서만 iframe 허용)
        X-Frame-Options SAMEORIGIN

        # HTTP 헤더에서 Caddy 서버 정보 숨기기
        -Server
    }
}

api.example.com {
    import security-headers
    reverse_proxy localhost:8080
}

HSTS는 한 번 켜면 롤백이 까다롭습니다. 스테이징 도메인에서 먼저 짧게 시험하고, 서브도메인까지 포함할지 결정한 뒤에 프로덕션에 넣는 편이 안전합니다. preload 목록 등록까지 갈 필요는 대부분의 사이드 프로젝트에선 없습니다.

구조화된 접속 로그 남기기

비정상적인 트래픽을 추적하고 웹로그를 분석하기 위해 JSON 형식의 구조화된 로그 파일을 주기적으로 자동 분할(Rotation)하여 저장하는 설정입니다. 세부 속성은 log 지시어 명세 문서 및 글로벌 옵션 설정을 참고했습니다.

example.com {
    # 로그 파일 설정 지시어
    log {
        # 로그 저장소 지정 및 로테이션 설정
        output file /var/log/caddy/access.log {
            roll_size 50MiB      # 파일 크기 50MB 도달 시 새로운 파일 분할
            roll_keep 10         # 백업 파일은 최대 10개까지만 보관
            roll_keep_for 720h   # 30일(720시간)이 지난 로그는 자동 폐기
        }
        format json
    }

    # reverse_proxy 지시어 공식 가이드: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy
    reverse_proxy localhost:8080
}

추가적으로, 프로덕션 환경에서 트래픽 제한(Rate Limiting)이 필요할 경우 공식 빌드에는 포함되어 있지 않으므로 mholt/caddy-ratelimit GitHub 저장소의 서드파티 플러그인을 빌드에 포함해 이용하는 것을 추천합니다. 로그인·회원가입·비밀번호 재설정 같은 엔드포인트에만 거는 편이 운영이 단순합니다. 전역 레이트리밋부터 크게 걸면 정상 사용자까지 막히기 쉽습니다.

결정 기준을 한곳에 모아 두면

마지막으로, “지금 내 상황에 Caddy가 맞는지”를 빠르게 판단할 때 쓰는 질문들입니다.

  • 인증서 발급·갱신을 내가 직접 관리하고 싶은가, 아니면 서버가 알아서 했으면 하는가?
  • 설정 파일을 팀이 아닌 나 혼자 고치는가?
  • 필요한 기능이 리버스 프록시·정적 파일·SPA·기본 보안 헤더 수준인가?
  • 이미 Nginx 설정이 수백 줄이고, 그걸 옮길 시간이 제품 기능보다 우선인가?

앞의 세 질문에 “자동화 / 혼자 / 기본 기능”이 나오고, 마지막 질문에 “아니다”라면 Caddy로 시작하는 쪽을 권합니다. 반대로 Nginx 설정이 이미 자산이라면, 새 프로젝트에만 Caddy를 써 보고 감각을 익히는 절충도 괜찮습니다. 인프라 선택은 종교가 아니라 운영 비용 문제입니다.

정리

Caddy는 소규모 토이 프로젝트에서만 쓰는 서버가 아닙니다. 자동 HTTPS, 사람이 읽기 쉬운 설정 문법(Caddyfile), 의존성이 거의 없는 단일 바이너리 구조를 갖추고 있어 대규모 프로덕션 환경과 클라우드 네이티브 환경에서도 실제 선택지로 쓰입니다. 다만 Nginx를 “구식”으로 치부할 필요는 없습니다. 레거시와 조직 표준이 있는 곳에서는 Nginx가 여전히 합리적이고, Caddy의 강점은 인력이 적을수록 더 선명합니다.

  • SSL 발급 과정을 직접 관리하지 않아도 됩니다: 사이드 프로젝트나 신규 서비스를 준비 중이라면 Nginx와 Certbot 조합 대신 Caddy를 설치해 보세요. 도메인 세팅과 HTTPS 구동을 몇 분 안에 끝낼 수 있습니다.
  • 설정 파일 유지보수가 쉬워집니다: Nginx의 복잡한 config 문법을 찾아보느라 시간을 쓰는 대신, 사람이 읽기 쉬운 몇 줄의 Caddyfile로 인프라를 관리할 수 있습니다.
  • 깨지는 지점을 미리 알면 더 빠릅니다: DNS, 포트 충돌, 방화벽, CDN 프록시, 컨테이너 네트워킹만 점검해도 초반 삽질을 크게 줄일 수 있습니다.

지금 운영 중이거나 준비 중인 웹 서비스 인프라에 Caddy를 도입하면, 설정에 드는 시간을 제품 쪽으로 돌릴 여지가 생깁니다. 그게 정답인지는 트래픽과 팀 상황에 달렸고, 이 글은 그 판단을 위한 실무 메모에 가깝습니다.

참고 자료 및 출처

관련 글