본문 바로가기
WIKI 기술 지식 베이스

일반적인 Caddyfile 패턴

원문 보기 위키 갱신

일반적인 Caddyfile 패턴 (Common Caddyfile Patterns)

출처: Caddy 공식 문서

본문

이 페이지는 일반적인 사용 사례에 대한 완전하고 최소한의 Caddyfile 구성을 몇 가지 보여줘요. 여러분만의 Caddyfile 문서를 시작할 때 유용한 출발점이 될 수 있어요.

이들은 그냥 대입하면 되는 해결책이 아니에요. 도메인 이름, 포트/소켓, 디렉터리 경로 등을 직접 커스터마이즈해야 해요. 가장 일반적인 구성 패턴을 보여주기 위한 것이라고 생각하면 돼요.

정적 파일 서버

example.com {
	root /var/www
	file_server
}

평소처럼 첫 줄이 사이트 주소예요. root지시문은 사이트 루트의 경로를 지정해요(여기서 *는 경로 매처와 구별하기 위해 모든 요청을 일치시키라는 뜻이에요)—사이트가 현재 작업 디렉터리가 아니라면 경로를 바꿔주세요. 마지막으로 정적 파일 서버를 활성화해요.

리버스 프록시

모든 요청을 프록시:

example.com {
	reverse_proxy localhost:5000
}

/api/로 시작하는 경로가 있는 요청만 프록시하고 나머진 모두 정적 파일로 서빙:

example.com {
	root /var/www
	reverse_proxy /api/* localhost:5000
	file_server
}

이 방식은 요청 매처를 사용해 /api/로 시작하는 요청만 일치시키고 백엔드로 프록시해요. 다른 모든 요청은 정적 파일 서버로 사이트 root에서 서빙돼요. reverse_proxy가 지시문 순서에서 file_server보다 위에 있다는 사실에도 의존하고 있어요.

handle_path를 사용해 여러 앱을 하나의 도메인에서 각자 자신의 경로 접두사 아래에 서빙하고, 프록시 전에 접두사를 제거해요:

example.com {
	handle_path /app1/* {
		reverse_proxy localhost:5001
	}
	handle_path /app2/* {
		reverse_proxy localhost:5002
	}
	handle {
		respond "Not found" 404
	}
}

대부분의 앱은 이런 "하위 폴더"에서 동작하지 않는다는 점을 명심해주세요. 링크, 리다이렉트, 에셋 URL이 접두사를 포함하지 않기 때문이에요. 앱에 base path나 public URL 설정이 있다면 그걸 접두사로 설정하고, 앱이 경로에 접두사를 받기를 기대한다면 handle 대신 handle_path를 handle로 바꿔 사용해요. 그렇지 않으면 각 앱에 자체 서브도메인을 주는 것을 고려해보세요.

백엔드가 다운됐을 때(예: 배포 중) 오류 대신 유지보수 페이지를 보여주기:

example.com {
	reverse_proxy localhost:5000

	handle_errors 502 503 {
		root * /srv/maintenance
		rewrite * /index.html
		file_server
	}
}

reverse_proxy가 백엔드에 연결할 수 없으면 502 오류(업스트림이 없으면 503)를 만들고, 이를 handle_errors가 처리해요. 페이지는 오류의 상태 코드로 서빙되므로 클라이언트와 검색 엔진이 중단이 일시적임을 알게 돼요.

reverse_proxy에는 더 많은 예제가 여기 있습니다.

PHP

PHP-FPM

PHP FastCGI 서비스가 실행 중이라면, 이런 형태가 대부분의 현대 PHP 앱에서 동작해요:

example.com {
	root /srv/public
	encode
	php_fastcgi localhost:9000
	file_server
}

사이트 루트를 상황에 맞게 커스터마이즈해주세요. 이 예제는 PHP 앱의 webroot가 public 디렉터리 안에 있다고 가정해요—디스크에 존재하는 파일에 대한 요청은 file_server로 서빙되고, 나머지는 PHP 앱이 처리하도록 index.php로 라우팅돼요.

때로는 unix 소켓을 사용해 PHP-FPM에 연결하기도 해요:

php_fastcgi unix//run/php/php8.5-fpm.sock

php_fastcgi지시문은 실제로 여러 구성의 축약이에요.

WordPress라면 민감한 경로에 대한 요청을 차단하고 싶을 수도 있어요:

example.com {
	root /var/www/wordpress
	encode
	php_fastcgi unix//run/php/php8.5-fpm.sock
	file_server

	@blocked path /xmlrpc.php *.sql /wp-content/uploads/*.php
	rewrite @blocked /index.php
}

이렇게 하면 XML-RPC 엔드포인트(무차별 대입 공격의 주요 표적이에요—필요한 앱이나 플러그인이 있다면 목록에서 제거하세요), 데이터베이스 덤프, 업로드 디렉터리의 PHP 스크립트를 차단해요. 차단된 요청은 index.php로 재작성되어, WordPress가 원래 요청 URI를 보기 때문에 정상적인 "찾을 수 없음" 페이지로 처리해요.

FrankenPHP

혹은 FrankenPHP를 사용할 수도 있어요. 이는 CGO(Go to C 바인딩)를 사용해 PHP를 직접 호출하는 Caddy 배포판이에요. PHP-FPM보다 최대 4배 빠를 수 있고, worker 모드를 사용할 수 있다면 더 좋아요.

example.com {
	root /srv/public
    encode zstd br gzip
    php_server
}

www. 서브도메인 리다이렉트

HTTP 리다이렉트로 www. 서브도메인을 추가하려면:

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

www.example.com {
}

제거하려면:

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

example.com {
}

여러 도메인에 대해 동시에 제거하려면 이렇게 해요. {labels.*} 플레이스홀더는 호스트 이름의 일부이며 오른쪽에서 0부터 인덱스되기 때문이에요(예: 0=com, 1=example-one, 2=www):

www.example-one.com, www.example-two.com {
	redir https://{labels.1}.{labels.0}{uri}
}

example-one.com, example-two.com {
}

끝 슬래시

보통 이걸 직접 구성할 필요는 없어요. file_server지시문이 요청된 리소스가 디렉터리인지 파일인지에 따라 HTTP 리다이렉트로 요청의 끝 슬래시를 자동으로 추가하거나 제거해요.

하지만 필요하다면 구성으로 끝 슬래시를 강제할 수 있어요. 방법은 내부적 또는 외부적으로 두 가지예요.

내부적 강제

rewrite 지시문을 사용해요. Caddy가 URI를 내부적으로 재작성해 끝 슬래시를 추가하거나 제거해요:

example.com {
	rewrite /add     /add/
	rewrite /remove/ /remove
}

rewrite를 사용하면 끝 슬래시가 있든 없든 요청이 동일하게 처리돼요.

외부적 강제

redir 지시문을 사용해요. Caddy가 브라우저에 URI를 바꾸라고 요청해 끝 슬래시를 추가하거나 제거해요:

example.com {
	redir /add     /add/
	redir /remove/ /remove
}

리다이렉트를 사용하면 클라이언트가 요청을 다시 발행해야 하므로 리소스에 대해 허용되는 단일 URI가 강제돼요.

와일드카드 인증서

Caddy가 와일드카드 인증서를 자동으로 관리하게 하려면 ACME DNS 챌린지를 활성화해야 해요.

같은 와일드카드 인증서로 여러 서브도메인을 서빙하려면 구성 방법이 두 가지예요: 암시적으로 또는 라우트 핸들러를 통해.

Caddy 2.10부터는 Caddy가 서브도메인에 대해 별도 인증서를 요청하는 것보다 적용 가능한 와일드카드 인증서를 선호해요. 즉, 평소처럼 서브도메인을 나열하면 자동으로 정의된 와일드카드 인증서를 사용하기 시작한단 뜻이에요:

*.example.com {
	tls {
		dns <provider_name> [<params...>]
	}
	abort
}

# This will use the above certificate
foo.example.com {
	respond "Foo!"
}

혹은 handle지시문과 host매처를 사용해 명시적으로 처리할 수도 있어요. 호스트 간에 구성의 상당 부분이 공유된다면 이 방법이 선호될 수 있어요.

*.example.com {
	tls {
		dns <provider_name> [<params...>]
	}

	@foo host foo.example.com
	handle @foo {
		respond "Foo!"
	}

	@bar host bar.example.com
	handle @bar {
		respond "Bar!"
	}

	# Fallback for otherwise unhandled domains
	handle {
		abort
	}
}

단일 페이지 앱(SPA)

웹 페이지가 자체 라우팅을 하면 서버는 서버 측에 존재하지 않지만, 단일 인덱스 파일을 대신 서빙하면 클라이언트 측에서 렌더링 가능한 페이지에 대한 요청을 많이 받을 수 있어요. 이렇게 아키텍처된 웹 애플리케이션을 SPA(단일 페이지 앱)이라고 해요.

핵심 아이디어는 서버가 "파일을 시도(try files)"해서 요청된 파일이 서버 측에 존재하는지 확인하고, 없으면 클라이언트가 라우팅하는 인덱스 파일(보통 클라이언트 측 JavaScript)로 폴백하는 것이에요.

전형적인 SPA 구성은 보통 이런 느낌이에요:

example.com {
	root /srv
	encode
	try_files {path} /index.html
	file_server
}

SPA가 API나 다른 서버 측 전용 엔드포인트와 결합되어 있다면 handle 블록을 사용해 그것들을 배타적으로 처리하고 싶을 거예요:

example.com {
	encode

	handle /api/* {
		reverse_proxy backend:8000
	}

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

index.html이 해시된 파일명을 가진 JS/CSS 에셋 참조를 포함한다면, 클라이언트에 캐시하지 말라고 알리는 Cache-Control 헤더를 추가하는 것을 고려해볼 수 있어요(에셋이 바뀌면 브라우저가 새 것을 가져오도록). try_files 재작성으로 다른 파일과 일치하지 않는 경로에서 index.html을 서빙하므로, header 핸들러가 재작성 이후에 실행되도록 try_files를 route로 감쌀 수 있어요(보통은 지시문 순서 때문에 앞서 실행돼요):

route {
	try_files {path} /index.html
	header /index.html Cache-Control "public, max-age=0, must-revalidate"
}

다른 Caddy로 프록시하는 Caddy

공개적으로 접근 가능한 Caddy 인스턴스 하나("front"라고 부를게요)와 실제 앱을 서빙하는 사설 네트워크의 다른 Caddy 인스턴스("back"이라고 부를게요)가 있다면, reverse_proxy지시문으로 요청을 통과시킬 수 있어요.

Front 인스턴스:

foo.example.com, bar.example.com {
	reverse_proxy 10.0.0.1:80
}

Back 인스턴스:

{
	servers {
		trusted_proxies static private_ranges
	}
}

http://foo.example.com {
	reverse_proxy foo-app:8080
}

http://bar.example.com {
	reverse_proxy bar-app:9000
}

이 예제는 두 개의 서로 다른 도메인을 서빙하고, 둘 다 같은 back Caddy 인스턴스(포트 80)로 프록시해요. back 인스턴스는 두 도메인을 서로 다른 방식으로 서빙하므로 두 개의 별도 사이트 블록으로 구성돼 있어요.

back에서 http://는 포트 80에서 HTTP를 수락하는 데 사용돼요. front 인스턴스가 TLS를 종료하고 front와 back 사이의 트래픽은 사설 네트워크에 있으므로 다시 암호화할 필요가 없어요.

필요하다면 back 인스턴스에서 8080 같은 다른 포트를 사용해도 돼요. back 구성의 각 사이트 주소에 :8080을 붙이거나, http_port전역 옵션을 8080으로 설정하면 돼요.

back에서 trusted_proxies전역 옵션은 front 인스턴스를 프록시로 신뢰하라고 Caddy에 알려줘요. 이렇게 하면 실제 클라이언트 IP가 보존돼요.

더 나아가, 로드 밸런싱을 하는 여러 back 인스턴스를 가질 수도 있어요. front 인스턴스에서 acme_server를 사용해 mTLS(상호 TLS)를 설정해서 back 인스턴스의 CA처럼 동작하게 할 수도 있어요(front와 back 사이의 트래픽이 신뢰할 수 없는 네트워크를 가로지르는 경우 유용해요).

더 알아보기 (Learn more)