Caddy
Caddy is a powerful, easy-to-use web server with automatic HTTPS.
Caddy
Caddy는 설정이 매우 간단하고 HTTPS를 자동으로 설정해주는 강력한 웹 서버입니다. 특히 20개 정도의 마이크로 서비스를 로컬에서 관리할 때, Nginx보다 훨씬 적은 설정으로 서브도메인 기반 개발 환경을 구축할 수 있어 요즘 개발자들 사이에서 인기가 높습니다.
1. Caddy의 핵심 특징
- 자동 HTTPS: 별도 설정 없이도 로컬 및 운영 환경에서 TLS(SSL) 인증서를 자동으로 발급하고 갱신합니다.
- 간결한 설정 (Caddyfile): Nginx의 복잡한 설정 파일과 달리, 사람이 읽기 쉬운 직관적인 문법을 사용합니다.
- 단일 바이너리: Go 언어로 작성되어 설치가 간편하고 실행 파일 하나로 돌아갑니다.
- 역방향 프록시(Reverse Proxy) 특화: 마이크로 서비스 아키텍처에서 여러 포트를 도메인으로 연결하기에 최적화되어 있습니다.
2. 마이크로 서비스를 위한 Caddy 설정 예시
20개의 서비스를 운영하신다면 아래와 같이 Caddyfile 하나로 모든 포트를 서브도메인에 매핑할 수 있습니다. localhost 대신 *.localhost를 사용하면 별도의 /etc/hosts 수정 없이도 브라우저에서 바로 접속 가능합니다.
# Caddyfile 예시
# 메인 웹 서비스
app.localhost {
reverse_proxy localhost:3000
}
# 인증 서비스 (Node.js)
auth.localhost {
reverse_proxy localhost:4001
}
# 유저 서비스 (Python/FastAPI)
user.localhost {
reverse_proxy localhost:6001
}
# 와일드카드로 한 번에 처리도 가능 (고급 설정)
# *.localhost {
# reverse_proxy localhost:{header.X-Service-Port}
# }3. Nginx와 비교했을 때 장점
| 특징 | Nginx | Caddy |
|---|---|---|
| 설정 난이도 | 다소 복잡함 (시작/끝 중괄호 등) | 매우 쉬움 (한 줄로 프록시 가능) |
| HTTPS | Certbot 등을 별도로 써야 함 | 기본 내장 (자동 적용) |
| 로컬 도메인 | 설정이 까다로움 | localhost 서브도메인 즉시 지원 |
| 성능 | 매우 높음 (대규모 트래픽 유리) | 준수함 (개발 및 일반 운영에 충분) |
4. 로컬 개발 환경 구축 순서 (Mac/Linux 기준)
- 설치:
- Mac:
brew install caddy - Linux: 공식 레포지토리를 통해 설치
- Mac:
- Caddyfile 생성: 프로젝트 루트에 위 예시와 같은
Caddyfile을 만듭니다. - 실행: 터미널에서
caddy run명령어를 입력합니다. - 접속: 브라우저에서
http://auth.localhost로 접속하면 자동으로localhost:4001로 연결됩니다.
5. Jace님을 위한 팁: docker-compose와 시너지
20개 서비스를 Docker로 띄우신다면, Caddy 역시 컨테이너로 하나 띄워두는 것이 좋습니다. 그러면 각 서비스의 컨테이너 이름을 도메인처럼 바로 쓸 수 있어 포트 번호를 일일이 관리할 필요조차 없어집니다.
# docker-compose.yml 일부
services:
caddy:
image: caddy:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile이렇게 하면 개발 환경에서도 실제 운영 서버와 거의 동일한 서브도메인 기반 이동 로직을 그대로 테스트하실 수 있습니다.
Caddyfile vs caddy.json
1. Caddyfile vs caddy.json 한눈에 비교
| 특징 | Caddyfile (권장 ⭐) | caddy.json (고급/자동화) |
|---|---|---|
| 가독성 | 매우 높음 (사람 중심) | 낮음 (기계/프로그램 중심) |
| 작성 난이도 | 매우 쉬움 (한 줄로 프록시 가능) | 복잡함 (중첩된 구조) |
| 기능 범위 | 일반적인 웹 서버 기능 95% 커버 | Caddy의 모든 세부 기능 제어 가능 |
| 변경 방식 | 파일 수정 후 caddy reload | API 호출을 통한 실시간 무중단 변경 |
| 주석 | # 사용 가능 | 불가능 (표준 JSON 규격) |
2. 코드 예시로 보는 차이 (역방향 프록시 설정)
동일하게 auth.localhost를 4001 포트로 연결하는 설정입니다.
Caddyfile (단 3줄)
auth.localhost {
reverse_proxy localhost:4001
}caddy.json (일부 발췌)
같은 내용을 JSON으로 짜면 훨씬 길고 복잡해집니다.
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [":443"],
"routes": [
{
"match": [{ "host": ["auth.localhost"] }],
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [{ "dial": "localhost:4001" }]
}
]
}
]
}
}
}
}
}3. 어떤 상황에 무엇을 써야 할까?
언제 Caddyfile을 쓰나요? (Jace님의 20개 서비스 환경)
- 로컬 개발 환경: 20개 서비스를 일일이 JSON으로 관리하면 오타 찾기도 힘듭니다. Caddyfile이 압도적으로 유리합니다.
- 대부분의 운영 환경: 복잡한 라우팅 로직이 필요한 게 아니라면 Caddyfile만으로도 충분히 강력합니다.
- 버전 관리: Git에 올리고 팀원들과 공유하기에 가장 좋습니다.
언제 caddy.json을 쓰나요?
- 동적 대규모 인프라: 서비스가 수천 개로 늘어나서 코드가 아니라 프로그램(API)이 자동으로 설정을 추가/삭제해야 할 때 사용합니다.
- Caddyfile에서 지원 안 하는 특수 기능: 아주 드물게 극도로 세부적인 튜닝이 필요한 경우에만 사용합니다.
- 무중단 설정 변경: 파일 리로드 없이 API 통신으로 즉시 설정을 바꾸고 싶을 때 유용합니다.
💡 실질적인 팁
이미 Caddyfile을 잘 쓰고 있는데, 내부적으로 이게 어떻게 JSON으로 바뀌는지 궁금하다면 터미널에 아래 명령어를 입력해 보세요.
caddy adapt --config ./Caddyfile --pretty이 명령어를 실행하면 현재 작성한 Caddyfile이 어떤 JSON 구조로 변환되는지 실시간으로 확인할 수 있습니다.
20개 서비스를 관리하신다면 우선 Caddyfile로 시작하시는 걸 강력 추천드립니다. 혹시 특정 서비스에서 특이한 라우팅(예: 특정 경로만 제외하고 프록시 등)이 필요한 부분이 있으신가요? 상황에 맞는 Caddyfile 문법을 알려드릴 수 있습니다.
Caddy로 정적 파일 서빙
네, 당연히 가능합니다! Caddy는 단순한 역방향 프록시뿐만 아니라 **정적 파일 서버(Static File Server)**로서의 성능도 매우 뛰어납니다. 20개의 마이크로 서비스 중 일부가 순수 HTML/CSS/JS로 구성된 랜딩 페이지나 문서 사이트라면 Caddy가 직접 서빙하게 만드는 것이 훨씬 가볍고 빠릅니다.
1. Caddyfile 설정 방법 (Static + Proxy 혼합)
file_server 지시어를 사용하면 됩니다. 마이크로 서비스들과 함께 섞어서 설정하는 예시입니다.
# 1. 정적 HTML 서비스 (예: 메인 랜딩 페이지)
www.localhost {
# HTML 파일들이 위치한 경로 지정
root * /path/to/your/html/folder
# 정적 파일 서버 활성화
file_server
}
# 2. 기존의 Node.js/Python 서비스 (역방향 프록시)
auth.localhost {
reverse_proxy :4001
}2. 주요 옵션들
index자동 인식: 폴더 안에index.html이 있다면 브라우저에서 폴더 경로만 입력해도 알아서 해당 파일을 보여줍니다.templates: 정적 파일 내에서 간단한 동적 처리가 필요할 때 Caddy 템플릿 기능을 쓸 수도 있습니다.browse: 만약 파일 목록을 리스트 형태로 보여주고 싶다면 아래와 같이 설정합니다.docs.localhost { root * /path/to/docs file_server browse }
3. Jace님을 위한 실전 구조 (Docker 기준)
만약 Docker를 사용하신다면, 로컬의 HTML 폴더를 Caddy 컨테이너에 볼륨으로 연결해주기만 하면 됩니다.
docker-compose.yml:
services:
caddy:
image: caddy:latest
ports:
- "80:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./static-html:/var/www/html # 로컬의 html 폴더를 연결Caddyfile:
static.localhost {
root * /var/www/html
file_server
}💡 정리하자면
- Node/Python 서비스:
reverse_proxy사용. - 순수 HTML 서비스:
root+file_server사용.
Caddy 하나로 **API 게이트웨이 역할(Proxy)**과 **웹 서버 역할(Static)**을 동시에 수행할 수 있어 관리가 매우 편해집니다.
혹시 지금 정적 파일들의 폴더 구조가 어떻게 되어 있나요? 여러 폴더를 각각 다른 서브도메인에 연결해야 한다면 그에 맞는 경로 설정법도 알려드릴 수 있습니다. 어떤 것을 더 도와드릴까요?
CORS
마이크로 서비스(MSA)를 구축하다 보면 반드시 마주치게 되는 벽이 바로 **CORS(Cross-Origin Resource Sharing)**입니다.
간단히 말해, 브라우저가 **"A라는 도메인(localhost)에서 온 자바스크립트가 B라는 도메인(auth.localhost)의 데이터를 마음대로 가져가는 것"**을 보안상 차단하는 규칙입니다. 20개의 서비스를 서브도메인으로 나누셨으니, 서비스 간에 데이터를 주고받을 때 이 에러를 보게 될 확률이 100%입니다.
1. 왜 발생하는 걸까? (SOP 원칙)
브라우저는 기본적으로 **SOP(Same-Origin Policy)**라는 원칙을 따릅니다. "출처(도메인, 프로토콜, 포트)가 다르면 남남이다"라는 뜻이죠.
localhost:3000과localhost:4001은 포트가 다르므로 남남입니다.www.localhost와auth.localhost는 서브도메인이 다르므로 남남입니다.
남남인 사이에서 데이터를 요청하면 브라우저는 "이거 위험한 거 아니야?"라며 요청을 막아버립니다. 이걸 허용해주는 약속이 바로 CORS입니다.
2. Caddy에서 CORS 한 방에 해결하기
각 서비스(Node.js, Python 등) 코드에서 일일이 CORS 설정을 할 수도 있지만, Caddy(Gateway) 레벨에서 설정하면 훨씬 깔끔합니다.
Caddyfile에 아래와 같은 헤더 설정을 추가하면 브라우저의 허락을 받을 수 있습니다.
(cors_settings) {
header {
# 모든 도메인 허용 (개발 환경용)
Access-Control-Allow-Origin *
# 또는 특정 도메인만 허용 (보안상 추천)
# Access-Control-Allow-Origin https://*.localhost
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "Content-Type, Authorization"
}
}
auth.localhost {
import cors_settings
reverse_proxy :4001
}
user.localhost {
import cors_settings
reverse_proxy :4002
}3. Preflight (예비 요청)의 이해
브라우저는 "진짜 요청"을 보내기 전에 OPTIONS 메서드로 "내가 이 요청 보내도 돼?"라고 서버에 먼저 물어봅니다. 이를 Preflight라고 합니다.
Caddy는 이 OPTIONS 요청에 대해 적절한 응답을 보내줌으로써 백엔드 서비스(Node/Python)까지 요청이 가기도 전에 브라우저를 안심시켜 줍니다.
💡 Jace님을 위한 실전 팁
20개 서비스를 운영할 때 CORS 때문에 고생하지 않으려면 이 원칙을 기억하세요.
- 개발 환경: Caddy에서
Access-Control-Allow-Origin *로 열어두고 편하게 개발하세요. - 인증(Cookie/Session): 만약 쿠키를 사용해 로그인 상태를 공유해야 한다면,
Allow-Origin을*(와일드카드)로 쓸 수 없고 반드시 정확한 도메인을 명시해야 하며Access-Control-Allow-Credentials true설정이 추가로 필요합니다. - 에러 확인: 브라우저 콘솔에 빨간색으로 CORS 에러가 뜬다면, 서버 응답 헤더에
Access-Control-Allow-Origin이 빠져있지는 않은지 네트워크(Network) 탭에서 먼저 확인해 보세요.
여러개의 Caddy 동시 실행
방법 1. 굳이 2개 띄우지 않고 하나로 합치기 (강력 추천) 🌟 [5]
Caddy는 파일 하나에 수십, 수백 개의 도메인을 적어도 알아서 잘 분기해 줍니다. 작성하셨던 설정들을 하나의 Caddyfile에 아래와 같이 합쳐서 한 번만 실행하세요. [5, 6]
# Caddyfile (하나로 통합)
# 공통 설정
(common_settings) {
header {
Access-Control-Allow-Origin *
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "Content-Type, Authorization"
}
}
# 1. 메인 웹 서비스 (4000번 포트로 프록시)
jace.localhost {
import common_settings
reverse_proxy localhost:4000
}
www.jace.localhost {
redir https://jace.localhost{uri}
}
# 2. 문서 서비스 (4009번 포트로 프록시)
docs-jace.localhost {
import common_settings
reverse_proxy localhost:4009
}
# 3. 인증 서비스 (주석 해제 시 활성화)
# auth.localhost {
# import common_settings
# reverse_proxy localhost:4001
# }
# 4. 유저 서비스 (주석 해제 시 활성화)
# user.localhost {
# import common_settings
# reverse_proxy localhost:6001
# }방법 2. 그래도 무조건 Caddy 2개를 따로 run 해야 한다면?
특수한 목적이 있어서 반드시 프로세스를 쪼개야 한다면, 두 번째 Caddy가 사용하는 포트들을 전부 다른 번호로 비켜주어야 합니다. [5]
1. 첫 번째 Caddy 실행 (기본값 사용):
caddy run --config ./Caddyfile1# 이 Caddy는 80, 443, 2019 포트를 점유합니다.
2. 두 번째 Caddy 실행 (포트 수동 지정):
두 번째 Caddyfile의 글로벌 옵션에서 관리자(admin) 포트와 HTTP/HTTPS 포트를 겹치지 않게 변경해 주어야 합니다.
# Caddyfile2 (두 번째 전용)
{
admin localhost:2020 # 기본 2019 포트와 충돌 방지
http_port 8080 # 기본 80 포트와 충돌 방지
https_port 8443 # 기본 443 포트와 충돌 방지
}
# 이제 이 Caddy는 https_port로 지정한 8443 포트로 접속해야 합니다.
docs-jace.localhost:8443 {
reverse_proxy localhost:4009
}