Caddy를 활용한 로컬 HTTPS 환경 구축

로컬에서 개발하다 보면 HTTPS가 필요한 상황이 생긴다. OAuth 리다이렉트 URL이 HTTPS를 요구하거나, Secure 속성이 걸린 쿠키를 테스트해야 하거나, 서비스 워커를 로컬에서 돌려야 하는 경우가 대표적이다. http://localhost로는 한계가 있는 순간이 오는 것이다.

이럴 때 Nginx로 리버스 프록시를 세팅하고 자체 서명 인증서를 만드는 방법도 있지만, 설정도 번거롭고 브라우저에서 인증서 경고가 계속 뜨는 게 귀찮다. Caddy를 쓰면 이 과정이 훨씬 간단해진다.

Caddy란

Caddy는 Go로 작성된 웹 서버다. 가장 큰 특징은 자동 HTTPS 다. 프로덕션 환경에서는 Let's Encrypt를 통해 인증서를 자동 발급하고, 로컬 환경에서는 자체 로컬 CA(인증 기관)를 만들어서 인증서를 발급해준다. Nginx처럼 별도로 인증서를 생성하거나 OpenSSL 명령어를 칠 필요가 없다.

설정 파일인 Caddyfile도 직관적이다. Nginx 설정에 비하면 작성해야 하는 양이 확연히 줄어든다.

설치

macOS 기준으로 Homebrew를 사용하면 된다.

brew install caddy

설치가 끝나면 caddy version으로 확인할 수 있다.

루트 인증서 신뢰 등록

Caddy가 로컬에서 HTTPS를 제공하려면 자체 CA의 루트 인증서를 시스템에 신뢰 등록해야 한다. 이걸 해줘야 브라우저에서 인증서 경고 없이 https://localhost에 접속할 수 있다.

caddy trust

이 명령어를 실행하면 Caddy가 로컬 CA를 생성하고 시스템 키체인에 루트 인증서를 등록한다. macOS에서는 키체인 접근 비밀번호를 물어볼 수 있다. 이 작업은 최초 한 번만 하면 된다.

Caddyfile 작성

프로젝트 루트에 Caddyfile을 만든다. 예를 들어 로컬의 3000번 포트에서 돌고 있는 개발 서버를 HTTPS로 접근하고 싶다면 이렇게 작성한다.

localhost {
    reverse_proxy localhost:3000
}

이게 전부다. Nginx였다면 server 블록, ssl_certificate 경로, proxy_pass 설정 등을 직접 작성해야 했겠지만, Caddy는 도메인만 지정하면 알아서 HTTPS를 적용한다.

커스텀 도메인을 쓰고 싶다면 /etc/hosts에 도메인을 추가하고 Caddyfile에 해당 도메인을 적어주면 된다.

# /etc/hosts에 추가
127.0.0.1 myapp.local
myapp.local {
    reverse_proxy localhost:3000
}

여러 서비스를 동시에 띄우는 경우에도 간단하다.

api.localhost {
    reverse_proxy localhost:8080
}

app.localhost {
    reverse_proxy localhost:3000
}

실행

Caddyfile이 있는 디렉토리에서 실행한다.

sudo caddy run --config ./Caddyfile

sudo가 필요한 이유는 443 포트(HTTPS 기본 포트)를 사용하기 때문이다. 1024 이하의 포트는 관리자 권한이 필요하다.

실행하면 터미널에 로그가 출력되고, 브라우저에서 https://localhost로 접속하면 인증서 경고 없이 정상적으로 HTTPS 연결이 되는 걸 확인할 수 있다.

백그라운드에서 실행하고 싶다면 run 대신 start를 쓰면 된다.

sudo caddy start --config ./Caddyfile

이 경우 멈추려면 다음 명령어를 사용한다.

sudo caddy stop

제대로 종료되지 않은 경우

간혹 Caddy가 깔끔하게 종료되지 않아서 포트가 이미 사용 중이라는 에러가 날 수 있다.

Error: loading initial config: loading new config: http app module: start: listening on :443: listen tcp :443: bind: address already in use

이럴 때는 프로세스를 강제 종료하면 된다.

sudo pkill -9 caddy

그래도 안 되면 어떤 프로세스가 443 포트를 점유하고 있는지 확인해본다.

sudo lsof -i :443

해당 프로세스의 PID를 확인하고 직접 종료하면 된다.

sudo kill -9 <PID>

정리

로컬 HTTPS 환경이 필요할 때 Caddy를 쓰면 자체 서명 인증서 생성, 시스템 신뢰 등록, 리버스 프록시 설정을 한 번에 해결할 수 있다. 핵심은 세 단계다.

  1. brew install caddy — 설치
  2. caddy trust — 루트 인증서 신뢰 등록 (최초 1회)
  3. sudo caddy run --config ./Caddyfile — 실행

Nginx처럼 인증서 파일을 직접 만들고 경로를 지정하는 과정이 없어서, 로컬 개발 환경에서는 Caddy가 확실히 편하다.