reverse_proxy 지시문
reverse_proxy 지시문 (리버스 프록시)
하나 이상의 백엔드로 요청을 프록시하며, 전송(transport), 로드 밸런싱, 상태 확인, 요청 조작, 버퍼링 옵션을 구성할 수 있어요.
출처: Caddy 공식 문서
본문
하나 이상의 백엔드로 요청을 프록시하며, 전송(transport), 로드 밸런싱, 상태 확인, 요청 조작, 버퍼링 옵션을 구성할 수 있어요.
- 문법 (Syntax)
- 업스트림 (Upstreams)
- 업스트림 주소 (Upstream addresses)
- 동적 업스트림 (Dynamic upstreams)
- SRV
- A/AAAA
- Multi
- 로드 밸런싱 (Load balancing)
- 활성 상태 확인 (Active health checks)
- 수동 상태 확인 (Passive health checks)
- 이벤트 (Events)
- 스트리밍 (Streaming)
- 헤더 (Headers)
- 재작성 (Rewrites)
- 전송 (Transports)
http전송fastcgi전송- 응답 가로채기 (Intercepting responses)
- 예제 (Examples)
문법 (Syntax)
reverse_proxy [<matcher>] [<upstreams...>] {
# backends
to <upstreams...>
dynamic <module> ...
# load balancing
lb_policy <name> [<options...>]
lb_retries <retries>
lb_try_duration <duration>
lb_try_interval <interval>
lb_retry_match <request-matcher>
# active health checking
health_uri <uri>
health_upstream <ip:port>
health_port <port>
health_interval <interval>
health_passes <num>
health_fails <num>
health_timeout <duration>
health_method <method>
health_status <status>
health_request_body <body>
health_body <regexp>
health_follow_redirects
health_headers {
<field> [<values...>]
}
# passive health checking
fail_duration <duration>
max_fails <num>
unhealthy_status <status>
unhealthy_latency <duration>
unhealthy_request_count <num>
# streaming
flush_interval <duration>
request_buffers <size>
response_buffers <size>
stream_timeout <duration>
stream_close_delay <duration>
# request/header manipulation
trusted_proxies [private_ranges] <ranges...>
header_up [+|-]<field> [<value|regexp> [<replacement>]]
header_down [+|-]<field> [<value|regexp> [<replacement>]]
method <method>
rewrite <to>
# round trip
transport <name> {
...
}
# optionally intercept responses from upstream
@name {
status <code...>
header <field> [<value>]
}
replace_status [<matcher>] <status_code>
handle_response [<matcher>] {
<directives...>
# special directives only available in handle_response
copy_response [<matcher>] [<status>] {
status <status>
}
copy_response_headers [<matcher>] {
include <fields...>
exclude <fields...>
}
}
}
업스트림 (Upstreams)
-
**<upstreams...>**는 프록시할 업스트림(백엔드) 목록이에요.
-
to는 업스트림 목록을 줄마다 하나(또는 그 이상)씩 지정하는 대체 방법이에요.
-
dynamic은 동적 업스트림 모듈을 구성해요. 이를 통해 매 요청마다 업스트림 목록을 동적으로 얻을 수 있어요. 표준 동적 업스트림 모듈에 대한 설명은 아래 동적 업스트림을 참고해요. 동적 업스트림은 매 프록시 루프 반복마다(즉, 로드 밸런싱 재시도가 활성화되면 요청당 여러 번 가능) 조회되며 정적 업스트림보다 우선해요. 오류가 발생하면 프록시는 정적으로 구성된 업스트림을 사용하는 것으로 폴백해요.
업스트림 주소
정적 업스트림 주소는 스킴과 호스트/포트만 포함하는 URL 형식이거나, 일반적인 Caddy 네트워크 주소 형식을 취할 수 있어요. 유효한 예:
localhost:4000127.0.0.1:4000[::1]:4000http://localhost:4000https://example.comh2c://127.0.0.1example.comunix//var/php.sockunix+h2c//var/grpc.socklocalhost:8001-8006[fe80::ea9f:80ff:fe46:cbfd%eth0]:443
기본적으로 업스트림과의 연결은 평문 HTTP로 이뤄져요. URL 형식 사용 시 스킴을 사용해 일부 transport 기본값을 단축 표기로 설정할 수 있어요.
https://를 스킴으로 사용하면tls가 활성화된http전송을 사용해요.
추가로, 서버가 라우팅과 인증서 선택에 사용하는 TLS SNI 값과 일치하도록 Host 헤더를 덮어써야 할 수도 있어요. 자세한 내용은 아래 HTTPS 섹션을 참고해요.
-
h2c://를 스킴으로 사용하면 평문 HTTP/2 연결을 허용하도록 HTTP 버전이 설정된http전송을 사용해요. -
http://를 스킴으로 사용하는 것은 HTTP가 이미 기본이므로 스킴을 생략한 것과 동일해요. 이 문법은 다른 스킴 단축키와의 대칭을 위해 포함된 거예요.
스킴은 공통 전송 구성을 수정하므로 혼합할 수 없어요(TLS 활성화 전송이 HTTPS와 평문 HTTP를 동시에 수행할 수 없음). 명시적 전송 구성은 덮어쓰지 않으며, 스킴을 생략하거나 다른 포트를 쓰는 것은 특정 전송을 가정하지 않아요.
영역(zone)이 있는 IPv6를 사용할 때(예: 특정 네트워크 인터페이스가 있는 링크-로컬 주소) %가 URL 파싱 오류를 일으키므로 스킴을 단축키로 사용할 수 없어요. 대신 전송을 명시적으로 구성해요.
네트워크 주소 형식을 사용할 때 네트워크 유형은 업스트림 주소의 접두사로 지정돼요. 이것은 URL 스킴과 결합할 수 없어요. 특수한 경우로 unix+h2c/는 unix/ 네트워크와 h2c:// 스킴의 효과를 더한 단축키로 지원돼요. 포트 범위는 단축키로 지원되며, 같은 호스트의 여러 업스트림으로 확장돼요.
업스트림 주소는 프록시하면서 동시에 요청을 재작성한다는 의미가 돼 그 동작이 정의되거나 지원되지 않으므로 경로나 쿼리 문자열을 포함할 수 없어요. 필요하다면 rewrite 지시문을 사용해요.
주소가 URL이 아니라면(즉 스킴이 없다면) placeholder를 사용할 수 있어요. 하지만 이렇게 하면 업스트림이 동적 정적이 되어, 상태 확인과 로드 밸런싱 측면에서 잠재적으로 많은 다른 백엔드가 하나의 정적 업스트림처럼 작동해요. 가능하다면 동적 업스트림 모듈을 권장해요. placeholder 사용 시 포트는 반드시 포함돼야 해요(placeholder 대체로, 또는 주소의 정적 접미사로).
동적 업스트림
Caddy의 리버스 프록시에는 기본적으로 몇 가지 동적 업스트림 모듈이 포함돼 있어요. 동적 업스트림 사용은 특정 정책 구성에 따라 로드 밸런싱과 상태 확인에 영향을 미친다는 점을 유의해요. 활성 상태 확인은 동적 업스트림에는 실행되지 않으며, 업스트림 목록이 비교적 안정적이고 일관적일 때(특히 라운드로빈) 로드 밸런싱과 수동 상태 확인이 가장 잘 작동해요. 이상적으로 동적 업스트림 모듈은 건강하고 사용 가능한 백엔드만 반환해야 해요.
SRV
SRV DNS 레코드에서 업스트림을 가져와요.
dynamic srv [<full_name>] {
service <service>
proto <proto>
name <name>
refresh <interval>
resolvers <ip...>
dial_timeout <duration>
dial_fallback_delay <duration>
}
-
**<full_name>**은 조회할 레코드의 전체 도메인 이름이에요(즉
_service._proto.name). -
service는 전체 이름의 서비스 구성 요소예요.
-
proto는 전체 이름의 프로토콜 구성 요소예요.
tcp또는udp. -
name은 이름 구성 요소예요. 또는
service와proto가 비어 있으면 조회할 전체 도메인 이름이에요. -
refresh는 캐시된 결과를 새로 고치는 주기예요. 기본:
1m. -
resolvers는 시스템 리졸버를 덮어쓸 DNS 리졸버 목록이에요.
-
dial_timeout은 쿼리 다이얼 타임아웃이에요.
-
dial_fallback_delay는 RFC 6555 Fast Fallback 연결을 생성하기 전에 기다리는 시간이에요. 기본:
300ms.
A/AAAA
A/AAAA DNS 레코드에서 업스트림을 가져와요.
dynamic a [<name> <port>] {
name <name>
port <port>
refresh <interval>
resolvers <ip...>
dial_timeout <duration>
dial_fallback_delay <duration>
versions ipv4|ipv6
}
-
name은 조회할 도메인 이름이에요.
-
port는 백엔드에 사용할 포트예요.
-
refresh는 캐시된 결과를 새로 고치는 주기예요. 기본:
1m. -
resolvers는 시스템 리졸버를 덮어쓸 DNS 리졸버 목록이에요.
-
dial_timeout은 쿼리 다이얼 타임아웃이에요.
-
dial_fallback_delay는 RFC 6555 Fast Fallback 연결을 생성하기 전에 기다리는 시간이에요. 기본:
300ms. -
versions는 해석할 IP 버전 목록이에요. 기본:
ipv4 ipv6, 이는 각각 A와 AAAA 레코드에 대응해요.
Multi
여러 동적 업스트림 모듈의 결과를 이어붙여요. 업스트림의 중복 소스를 원할 때 유용해요. 예: 두 번째 SRV 클러스터가 백업하는 기본 SRV 클러스터.
dynamic multi {
<source> [...]
}
- ****는 동적 업스트림용 모듈 이름과 그 구성이에요. 둘 이상 지정할 수 있어요.
로드 밸런싱
로드 밸런싱은 일반적으로 여러 업스트림 간 트래픽을 분산하는 데 사용돼요. 재시도를 활성화하면 하나 이상의 업스트림과 함께 사용해, 건강한 업스트림을 선택할 수 있을 때까지 요청을 보류할 수 있어요(예: 업스트림 재부팅 또는 재배포 중 오류를 완화하기 위해 기다리기).
이것은 random 정책으로 기본 활성화돼 있어요. 재시도는 기본적으로 비활성화돼 있어요.
- lb_policy는 로드 밸런싱 정책 이름과 그 옵션이에요. 기본:
random.
해싱을 수반하는 정책의 경우 highest-random-weight (HRW) 알고리즘을 사용해 같은 해시 키를 가진 클라이언트나 요청이 업스트림 목록이 변경돼도 같은 업스트림에 매핑되도록 보장해요.
일부 정책은 명시된 경우 fallback을 옵션으로 지원해요. 그 경우 fallback <policy>가 있는 블록을 받아 또 다른 로드 밸런싱 정책을 취해요. 해당 정책의 기본 fallback은 random이에요. fallback을 구성하면 기본 정책이 선택하지 않을 때 보조 정책을 사용할 수 있어 강력한 조합이 가능해요. fallback은 원하면 여러 번 중첩할 수 있어요.
예를 들어 header를 기본 정책으로 사용해 개발자가 특정 업스트림을 선택하게 하고, 다른 모든 연결에 first를 fallback으로 사용해 기본/보조 장애 조치를 구현할 수 있어요.
lb_policy header X-Upstream {
fallback first
}
random은 업스트림을 무작위로 선택해요.random_choose <n>은 둘 이상의 업스트림을 무작위로 선택한 후 부하가 가장 적은 하나를 골라요(n은 보통 2).first는 구성에 정의된 순서대로 첫 번째 사용 가능한 업스트림을 선택해 기본/보조 장애 조치를 허용해요. 이와 함께 상태 확인을 활성화해야 하며, 그렇지 않으면 장애 조치가 발생하지 않아요.round_robin은 각 업스트림을 차례로 순회해요.weighted_round_robin <weights...>는 제공된 가중치를 존중하며 각 업스트림을 차례로 순회해요. 가중치 인자의 수는 구성된 업스트림 수와 일치해야 해요. 가중치는 음수가 아닌 정수여야 하며, 최소 하나의 가중치는 0보다 커야 해요. 예를 들어 두 업스트림에 가중치5 1이면 첫 번째 업스트림이 두 번째가 한 번 선택되기 전에 연속 5번 선택되고 그 후 주기가 반복돼요. 가중치로 0을 사용하면 새 요청에 대해 해당 업스트림 선택이 비활성화돼요.least_conn은 현재 요청 수가 가장 적은 업스트림을 선택해요. 둘 이상의 호스트가 최소 요청 수를 가지면 그중 하나가 무작위로 선택돼요.ip_hash는 원격 IP(직접 피어)를 고정 업스트림에 매핑해요.client_ip_hash는 클라이언트 IP를 고정 업스트림에 매핑해요. 이는 실제 클라이언트 IP 파싱을 활성화하는servers > trusted_proxies글로벌 옵션과 가장 잘 어울려요. 그렇지 않으면ip_hash와 동일하게 동작해요.uri_hash는 요청 URI(경로와 쿼리)를 고정 업스트림에 매핑해요.query [key]는 쿼리 값을 해싱해 요청 쿼리를 고정 업스트림에 매핑해요. 지정된 키가 없으면 fallback 정책이 업스트림 선택에 사용돼요(기본random).header [field]는 헤더 값을 해싱해 요청 헤더를 고정 업스트림에 매핑해요. 지정된 헤더 필드가 없으면 fallback 정책이 업스트림 선택에 사용돼요(기본random).cookie [<name> [<secret>]]은 클라이언트의 첫 요청(쿠키가 없을 때)에서 fallback 정책으로 업스트림을 선택하고(기본random), 응답에Set-Cookie헤더를 추가해요(지정하지 않으면 기본 쿠키 이름은lb). 쿠키 값은 선택된 업스트림의 다이얼 주소를<secret>(지정하지 않으면 빈 문자열)을 공유 비밀로 HMAC-SHA256으로 해싱한 값이에요.
이후 쿠키가 있는 요청에서는 쿠키 값이 같은 업스트림에 매핑돼요(가능할 때). 불가능하거나 찾을 수 없으면 fallback 정책으로 새 업스트림이 선택되고 쿠키가 응답에 추가돼요.
디버깅 목적으로 특정 업스트림을 쓰려면 비밀로 업스트림 주소를 해싱하고 HTTP 클라이언트(브라우저든 그 외든)에 쿠키를 설정할 수 있어요. 예를 들어 PHP에서 다음을 실행해 쿠키 값을 계산할 수 있어요. 여기서 10.1.0.10:8080은 업스트림 중 하나의 주소이고 secret은 구성된 비밀이에요.
echo hash_hmac('sha256', '10.1.0.10:8080', 'secret');
// cdd96966817dd14a99f47ee17451464f29998da170814a16b483e4c1ff4c48cf
브라우저의 자바스크립트 콘솔에서 쿠키를 설정할 수 있어요. 예를 들어 lb라는 쿠키를 설정하려면:
document.cookie = "lb=cdd96966817dd14a99f47ee17451464f29998da170814a16b483e4c1ff4c48cf";
- lb_retries는 다음 사용 가능한 호스트가 다운된 경우 각 요청에 대해 사용 가능한 백엔드 선택을 몇 번 재시도할지예요. 기본적으로 재시도는 비활성화(0)돼 있어요.
lb_try_duration도 구성된 경우 기간에 도달하면 재시도가 일찍 멈출 수 있어요. 즉, 재시도 기간이 재시도 횟수보다 우선해요.
- lb_try_duration은 다음 사용 가능한 호스트가 다운된 경우 각 요청에 대해 사용 가능한 백엔드 선택을 얼마나 오래 시도할지 정의하는 기간 값이에요. 기본적으로 재시도는 비활성화(기간 0)돼 있어요.
로드 밸런서가 사용 가능한 업스트림 호스트를 찾는 동안 클라이언트는 이 시간만큼 기다려요. HTTP 전송의 기본 다이얼 타임아웃이 3s이므로 5s가 합리적인 출발점일 수 있어요. 첫 선택 업스트림에 도달할 수 없을 때 최소 한 번의 재시도를 허용하기 때문이에요. 하지만 사용 사례에 맞는 올바른 균형을 찾기 위해 자유롭게 실험해요.
-
lb_try_interval은 풀에서 다음 호스트를 선택하는 사이에 기다릴 시간을 정의하는 기간 값이에요. 기본은
250ms. 업스트림 호스트에 대한 요청이 실패할 때만 관련이 있어요.lb_try_duration이 0이 아닌데 이 값을0으로 설정하면 모든 백엔드가 다운되고 지연 시간이 매우 낮을 때 CPU가 과열될 수 있음을 유의해요. -
lb_retry_match는 재시도가 허용되는 요청을 제한해요. 업스트림 연결은 성공했지만 후속 왕복이 실패한 경우 재시도하려면 요청이 이 조건과 일치해야 해요. 업스트림 연결 자체가 실패하면 항상 재시도가 허용돼요. 기본적으로
GET요청만 재시도돼요.
이 옵션의 문법은 명명된 요청 매처와 동일하되 @name이 없어요. 단일 매처만 필요하면 같은 줄에 구성할 수 있어요. 여러 매처는 블록이 필요해요.
활성 상태 확인
활성 상태 확인은 타이머를 사용해 백그라운드에서 상태 확인을 수행해요. 활성화하려면 health_uri 또는 health_port가 필요해요.
각 reverse_proxy 핸들러는 자체 활성 상태 확인 결과를 추적해요. 따라서 여러 핸들러가 서로 다른 상태 확인 구성으로 같은 업스트림을 프록시하더라도 서로의 health_passes와 health_fails 카운트에 영향을 주지 않아요.
-
health_uri는 활성 상태 확인의 URI 경로(및 선택적 쿼리)예요.
-
health_upstream은 업스트림과 다를 때 활성 상태 확인에 사용할 ip:port예요. 이것은
health_header와{http.reverse_proxy.active.target_upstream}과 함께 사용해야 해요. -
health_port는 업스트림의 포트와 다를 때 활성 상태 확인에 사용할 포트예요.
health_upstream이 사용되면 무시돼요. -
health_interval은 활성 상태 확인을 얼마나 자주 수행할지 정의하는 기간 값이에요. 기본:
30s. -
health_passes는 백엔드를 다시 건강한 상태로 표시하는 데 필요한 연속 상태 확인 수예요. 기본:
1. -
health_fails는 백엔드를 불건강한 상태로 표시하는 데 필요한 연속 상태 확인 수예요. 기본:
1. -
health_timeout은 백엔드를 다운으로 표시하기 전에 응답을 기다릴 시간을 정의하는 기간 값이에요. 기본:
5s. -
health_method는 활성 상태 확인에 사용할 HTTP 메서드예요. 기본:
GET. -
health_status는 건강한 백엔드에서 기대하는 HTTP 상태 코드예요. 3자리 상태 코드 또는
xx로 끝나는 상태 코드 클래스일 수 있어요. 예:200(기본값) 또는2xx. -
health_request_body는 활성 상태 확인과 함께 보낼 요청 본문을 나타내는 문자열이에요.
{env.*}같은 전역 placeholder는 치환되고, 중괄호 안의 다른 텍스트(예: JSON 객체)는 그대로 보내져요. -
health_body는 활성 상태 확인의 응답 본문에서 매치할 부분 문자열 또는 정규 표현식이에요. 백엔드가 일치하는 본문을 반환하지 않으면 다운으로 표시돼요.
-
health_follow_redirects는 상태 확인이 업스트림이 제공하는 리다이렉트를 따르도록 해요. 기본적으로 리다이렉트 응답은 상태 확인이 실패로 계산되게 해요.
-
health_headers는 활성 상태 확인 요청에 설정할 헤더를 지정할 수 있게 해요.
Host헤더를 바꿔야 하거나 상태 확인의 일부로 백엔드에 인증을 제공해야 할 때 유용해요.
수동 상태 확인
수동 상태 확인은 실제 프록시 요청과 함께 인라인으로 발생해요. 활성화하려면 fail_duration이 필요해요.
-
fail_duration은 실패한 요청을 기억할 시간을 정의하는 기간 값이에요.
0보다 큰 기간은 수동 상태 확인을 활성화해요. 기본은0(꺼짐). 불건강한 업스트림을 다시 온라인으로 가져올 때 오류율과 응답성을 균형 맞추려면30s가 합리적인 출발점일 수 있어요. 하지만 사용 사례에 맞는 올바른 균형을 찾기 위해 자유롭게 실험해요. -
max_fails는 백엔드를 다운으로 간주하는 데 필요한
fail_duration내 실패한 요청의 최대 수예요.1이상이어야 하며 기본은1이에요. -
unhealthy_status는 응답이 이 상태 코드 중 하나로 돌아오면 요청을 실패로 계산해요. 3자리 상태 코드 또는
xx로 끝나는 상태 코드 클래스일 수 있어요, 예:404또는5xx. -
unhealthy_latency는 응답을 받는 데 이 시간이 걸리면 요청을 실패로 계산하는 기간 값이에요.
-
unhealthy_request_count는 백엔드를 다운으로 표시하기 전 허용되는 동시 요청 수예요. 즉, 특정 백엔드가 현재 이만큼의 요청을 처리 중이면 "과부하"로 간주되고 다른 백엔드가 대신 선호돼요.
이것은 합리적으로 큰 숫자여야 해요. 구성하면 프록시는 총 unhealthy_request_count × upstreams_count의 동시 요청 한도를 가지며, 그 이후의 요청은 사용 가능한 업스트림이 없어 오류가 발생할 거예요.
이벤트
업스트림이 건강한 상태에서 불건강한 상태로 또는 그 반대로 전환되면 이벤트가 발생해요. 이러한 이벤트는 알림 전송이나 로그 메시지 기록 같은 다른 작업을 트리거하는 데 사용할 수 있어요. 이벤트는 다음과 같아요:
healthy는 이전에 불건강했던 업스트림이 건강한 상태로 표시될 때 발생해요.unhealthy는 이전에 건강했던 업스트림이 불건강한 상태로 표시될 때 발생해요.
두 경우 모두 host가 이벤트의 메타데이터로 포함되어 상태가 바뀐 업스트림을 식별해요. 예를 들어 exec 이벤트 핸들러와 함께 {event.data.host} placeholder로 사용할 수 있어요.
스트리밍
기본적으로 프록시는 와이어 효율을 위해 응답을 부분적으로 버퍼링해요.
프록시는 WebSocket 연결도 지원하며, HTTP 업그레이드 요청을 수행한 후 연결을 양방향 터널로 전환해요.
기본적으로 WebSocket 연결은 구성이 다시 로드될 때 강제로 닫혀요(클라이언트와 업스트림 양쪽에 Close 제어 메시지 전송). 각 요청은 구성에 대한 참조를 보유하므로 메모리 사용을 관리하려면 이전 연결을 닫는 것이 필요해요. 이 닫기 동작은 stream_timeout과 stream_close_delay 옵션으로 커스터마이즈할 수 있어요.
-
flush_interval은 Caddy가 클라이언트로 응답 버퍼를 플러시하는 주기를 조정하는 기간 값이에요. 기본적으로 주기적 플러시는 없어요. 음수 값(보통 -1)은 "저지연 모드"로, 응답 버퍼링을 완전히 비활성화하고 클라이언트에 대한 각 쓰기 직후 즉시 플러시하며, 클라이언트가 일찍 연결을 끊어도 백엔드에 대한 요청을 취소하지 않아요. 응답에 다음 중 하나가 적용되면 이 옵션은 무시되고 응답이 클라이언트로 즉시 플러시돼요:
-
Content-Type: text/event-stream -
Content-Length를 알 수 없음 -
프록시 양쪽이 HTTP/2이고
Content-Length를 알 수 없으며Accept-Encoding이 설정되지 않았거나 "identity"임 -
request_buffers는 프록시가 요청 본문에서 최대
<size>바이트를 업스트림으로 보내기 전에 버퍼로 읽어오게 해요. 이것은 매우 비효율적이며 업스트림이 지연 없이 요청 본문을 읽어야 할 때만 해야 해요(업스트림 애플리케이션이 고쳐야 할 부분). go-humanize가 지원하는 모든 크기 형식을 받아요.unlimited값은 요청 본문 전체를 버퍼링해요. 본문 전체가 버퍼링되면 업스트림 요청에Content-Length헤더가 설정돼요. 이것은 본문이 있고Content-Length가 없을 때fastcgi전송에 필요해요. -
response_buffers는 프록시가 클라이언트로 반환되기 전에 응답 본문에서 최대
<size>바이트를 버퍼로 읽어오게 해요. 성능상 가능하면 피해야 하지만, 백엔드의 메모리 제약이 더 엄격하다면 유용할 수 있어요. go-humanize가 지원하는 모든 크기 형식을 받아요. -
stream_timeout은 WebSocket 같은 스트리밍 요청이 타임아웃 끝에 강제로 닫히는 기간 값이에요. 본질적으로 연결이 너무 오래 열려 있으면 취소해요. 하루보다 오래된 연결을 정리하려면
24h가 합리적인 출발점일 수 있어요. 기본: 타임아웃 없음. -
stream_close_delay는 구성이 언로드될 때 WebSocket 같은 스트리밍 요청이 강제로 닫히는 것을 지연하는 기간 값이에요. 대신 스트림은 지연이 완료될 때까지 열려 있어요. 즉, 활성화하면 Caddy의 구성이 다시 로드될 때 스트림이 즉시 닫히지 않아요. 이를 활성화하면 이전 구성 닫기로 연결이 끊긴 클라이언트가 한꺼번에 다시 연결되는 군중(thundering herd)을 피하는 데 좋을 수 있어요. 구성 재로드 후 사용자가 5분 안에 자연스럽게 페이지를 떠나도록
5m같은 값이 합리적인 출발점일 수 있어요. 기본: 지연 없음.
헤더
프록시는 자체와 백엔드 사이에서 헤더를 조작할 수 있어요:
-
header_up은 백엔드로 가는 업스트림 요청 헤더에서 설정, 추가(
+접두사), 삭제(-접두사), 또는 치환(검색과 대체의 두 인자 사용)을 수행해요. -
header_down은 백엔드에서 오는 다운스트림 응답 헤더에서 설정, 추가(
+접두사), 삭제(-접두사), 또는 치환(두 인자 사용)을 수행해요.
예를 들어 기존 값을 덮어쓰며 요청 헤더를 설정하려면:
header_up Some-Header "the value"
응답 헤더를 추가하려면. 헤더 필드에는 여러 값이 있을 수 있음을 유의해요:
header_down +Some-Header "first value"
header_down +Some-Header "second value"
백엔드에 도달하지 못하게 요청 헤더를 삭제하려면:
header_up -Some-Header
접미사 매치를 사용해 일치하는 모든 요청 헤더를 삭제하려면:
header_up -Some-*
모든 요청 헤더를 삭제하고 원하는 것만 개별적으로 추가하려면(권장하지 않음):
header_up -*
요청 헤더에 정규 표현식 치환을 수행하려면:
header_up Some-Header "^prefix-([A-Za-z0-9]*)$" "replaced-$1-suffix"
사용되는 정규 표현식 언어는 Go에 포함된 RE2예요. RE2 문법 참조와 Go regexp 문법 개요를 참고해요. 대체 문자열은 확장되며, 예를 들어 $1이 첫 번째 캡처 그룹인 캡처된 값 사용을 허용해요.
기본값
기본적으로 Caddy는 Host를 포함한 수신 헤더를 수정 없이 백엔드로 전달해요. 세 가지 예외:
이러한 X-Forwarded-* 헤더에 대해 기본적으로 프록시는 스푸핑을 막기 위해 수신 요청의 해당 값을 무시해요.
클라이언트가 연결하는 첫 서버가 Caddy가 아닌 경우(예: Caddy 앞에 CDN이 있는 경우), trusted_proxies에 이러한 헤더에 대해 좋은 값을 보냈다고 신뢰하는 수신 요청의 IP 범위(CIDR) 목록을 구성할 수 있어요.
이것은 서버의 모든 프록시 핸들러에 적용되고 클라이언트 IP 파싱을 활성화하는 이점이 있으므로, 프록시 내부가 아닌 servers > trusted_proxies글로벌 옵션으로 구성하는 것을 강력히 권장해요.
Caddy 앞에 Cloudflare를 사용 중이라면 X-Forwarded-For 헤더 스푸핑에 취약할 수 있음을 유의해요. 우리의 친구 Authelia가 이 헤더의 수신 값을 무시하도록 Cloudflare를 구성하는 해결 방법을 문서화했어요.
추가로, http전송 사용 시 클라이언트 요청에 Accept-Encoding: gzip 헤더가 없으면 설정돼요. 이로 인해 업스트림이 가능하다면 압축된 콘텐츠를 제공할 수 있어요. 이 동작은 전송에서 compression off으로 비활성화할 수 있어요.
HTTPS
(대부분의) 헤더는 프록시될 때 원래 값을 유지하므로, HTTPS로 프록시할 때 Host 헤더가 TLS ServerName 값과 일치하도록 구성된 업스트림 주소로 Host 헤더를 덮어쓰는 것이 필요할 때가 많아요:
reverse_proxy https://example.com {
header_up Host {upstream_hostport}
}
Caddy v2.11.0부터 이것은 자동으로 수행되므로 HTTPS로 프록시할 때 Host 헤더를 명시적으로 덮어쓸 필요가 없어요. 이 동작을 거부하려면 Host 헤더를 원래 값으로 설정할 수 있어요(하지만 이렇게 하는 건 거의 의미 없음):
reverse_proxy https://example.com {
header_up Host {hostport}
}
X-Forwarded-Host 헤더는 여전히 기본적으로 전달되므로 업스트림이 원래 Host 헤더 값을 알아야 한다면 여전히 사용할 수 있어요.
caddy에서 TLS를 종료하고 포트나 unix 소켓으로 HTTP 프록시할 때도 동일하게 적용돼요. 실제로 caddy 자신이 reverse_proxy의 대상일 때 올바른 Host를 받아야 해요. unix 소켓의 경우 upstream_hostport는 소켓 경로가 되며 Host를 명시적으로 설정해야 해요.
재작성
기본적으로 Caddy는 reverse_proxy에 도달하기 전에 미들웨어 체인에서 재작성이 수행되지 않는 한, 들어오는 요청과 동일한 HTTP 메서드와 URI로 업스트림 요청을 수행해요.
프록시하기 전에 요청이 복제돼요. 이는 핸들러 동안 요청에 가해진 수정이 다른 핸들러로 새지 않도록 보장해요. 이것은 프록시 후 처리를 계속해야 하는 상황에서 유용해요.
헤더 조작 외에도 요청의 메서드와 URI는 업스트림으로 보내기 전에 변경될 수 있어요:
-
method는 복제된 요청의 HTTP 메서드를 변경해요. 메서드가
GET또는HEAD로 변경되면 수신 요청의 본문은 이 핸들러에 의해 업스트림으로 전송되지 않아요. 다른 핸들러가 요청 본문을 소비하도록 허용하려면 유용해요. -
rewrite는 복제된 요청의 URI(경로와 쿼리)를 변경해요.
rewrite지시문과 유사하지만, 재작성이 이 핸들러의 범위를 넘어 지속되지 않는다는 점이 달라요.
이러한 재작성은 "사전 확인 요청" 패턴에 자주 유용해요. 이 패턴에서는 요청이 다른 서버로 전송되어 현재 요청 처리를 계속하는 방법을 결정하는 데 도움을 주는 결정을 내려요.
예를 들어 요청을 인증 게이트웨이로 보내 요청이 인증된 사용자(예: 세션 쿠키 보유)의 것인지 판단하고 계속해야 하는지, 아니면 로그인 페이지로 리다이렉트해야 하는지 결정할 수 있어요. 이 패턴에 대해 Caddy는 대부분의 구성 보일러플레이트를 건너뛰는 단축 지시문 forward_auth를 제공해요.
전송
Caddy의 프록시 전송은 플러그 가능해요:
- transport는 백엔드와 통신하는 방법을 정의해요. 기본은
http.
http 전송
transport http {
read_buffer <size>
write_buffer <size>
max_response_header <size>
proxy_protocol v1|v2
dial_timeout <duration>
dial_fallback_delay <duration>
response_header_timeout <duration>
expect_continue_timeout <duration>
resolvers <ip...>
tls
tls_client_auth <automate_name> | <cert_file> <key_file>
tls_insecure_skip_verify
tls_curves <curves...>
tls_timeout <duration>
tls_trust_pool <module>
tls_server_name <server_name>
tls_renegotiation <level>
tls_except_ports <ports...>
keepalive [off|<duration>]
keepalive_interval <interval>
keepalive_idle_conns <max_count>
keepalive_idle_conns_per_host <count>
versions <versions...>
compression off
max_conns_per_host <count>
network_proxy <module>
}
-
read_buffer는 읽기 버퍼의 크기(바이트)예요. go-humanize가 지원하는 모든 형식을 받아요. 기본:
4KiB. -
write_buffer는 쓰기 버퍼의 크기(바이트)예요. go-humanize가 지원하는 모든 형식을 받아요. 기본:
4KiB. -
max_response_header는 응답 헤더에서 읽을 최대 바이트 수예요. go-humanize가 지원하는 모든 형식을 받아요. 기본:
10MiB. -
proxy_protocol은 업스트림과의 연결에서 PROXY 프로토콜(HAProxy가 대중화)을 활성화해 실제 클라이언트 IP 데이터를 앞에 붙여요. Caddy가 다른 프록시 뒤에 있다면
servers > trusted_proxies글로벌 옵션과 가장 잘 어울려요.v1및v2버전이 지원돼요. 업스트림 서버가 PROXY 프로토콜을 파싱할 수 있다고 아는 경우에만 사용해야 해요. 기본적으로 비활성화돼 있어요. -
dial_timeout은 업스트림 소켓에 연결할 때 기다릴 최대 기간이에요. 기본:
3s. -
dial_fallback_delay는 RFC 6555 Fast Fallback 연결을 생성하기 전에 기다릴 최대 기간이에요. 음수 값은 비활성화돼요. 기본:
300ms. -
response_header_timeout은 업스트림에서 응답 헤더를 읽을 때까지 기다릴 최대 기간이에요. 기본: 타임아웃 없음.
-
expect_continue_timeout은 요청에
Expect: 100-continue헤더가 있을 때 요청 헤더를 완전히 쓴 후 업스트림의 첫 응답 헤더를 기다릴 최대 기간이에요. 기본: 타임아웃 없음. -
read_timeout은 백엔드에서 다음 읽기를 기다릴 최대 기간이에요. 기본: 타임아웃 없음.
-
write_timeout은 백엔드에 대한 다음 쓰기를 기다릴 최대 기간이에요. 기본: 타임아웃 없음.
-
resolvers는 시스템 리졸버를 덮어쓸 DNS 리졸버 목록이에요.
-
tls는 백엔드와 HTTPS를 사용해요.
https://스킴으로 백엔드를 지정하거나 아래tls_*옵션 중 하나가 구성되면 자동으로 활성화돼요. -
tls_client_auth는 두 가지 방법 중 하나로 TLS 클라이언트 인증을 활성화해요: (1) Caddy가 인증서를 얻고 갱신 상태로 유지해야 하는 도메인 이름을 지정하거나, (2) 백엔드와의 TLS 클라이언트 인증에 제시할 인증서와 키 파일을 지정해요.
-
tls_insecure_skip_verify는 TLS 핸드셰이크 검증을 꺼서 연결을 안전하지 않게 만들고 중간자 공격에 취약하게 해요. 프로덕션에서 사용하지 마세요.
-
tls_curves는 업스트림 연결에 지원할 타원 곡선 목록이에요. Caddy의 기본값은 현대적이고 안전하므로 특정 요구 사항이 있을 때만 구성하면 돼요.
-
tls_timeout은 TLS 핸드셰이크가 완료될 때까지 기다릴 최대 기간이에요. 기본: 타임아웃 없음.
-
tls_trust_pool은
tls지시문 문서에 설명된trust_pool하위 지시문과 유사하게 신뢰할 수 있는 인증 기관의 공급원을 구성해요. 표준 Caddy 설치에서 사용 가능한 트러스트 풀 공급원 목록은 여기에 있어요. -
tls_server_name은 TLS 핸드셰이크에서 받은 인증서를 검증할 때 사용할 서버 이름을 설정해요. 기본적으로 업스트림 주소의 호스트 부분을 사용해요.
업스트림 주소가 업스트림이 사용할 가능성이 있는 인증서와 일치하지 않을 때만 덮어쓰면 돼요. 예를 들어 업스트림 주소가 IP 주소라면 업스트림 서버가 서빙하는 호스트 이름으로 이걸 구성해야 해요.
요청 placeholder를 사용할 수 있지만, 그 경우 매 요청마다 HTTP 전송 구성의 복제본이 사용되어 성능 저하가 발생할 수 있어요.
-
tls_renegotiation은 TLS 재협상 수준을 설정해요. TLS 재협상은 첫 핸드셰이크 이후 후속 핸드셰이크를 수행하는 행위예요. 수준:
-
never(기본값)는 재협상을 비활성화해요. -
once는 원격 서버가 연결당 한 번 재협상을 요청하도록 허용해요. -
freely는 원격 서버가 반복적으로 재협상을 요청하도록 허용해요. -
tls_except_ports는 TLS가 활성화된 경우 업스트림 대상이 주어진 포트 중 하나를 사용하면 해당 연결에서 TLS를 비활성화해요. 일부 업스트림은 HTTP 요청을, 다른 업스트림은 HTTPS 요청을 기대하는 동적 업스트림을 구성할 때 유용할 수 있어요.
-
keepalive는
off또는 연결을 얼마나 오래 유지할지(타임아웃) 지정하는 기간 값이에요. 기본:2m.
⚠️ keepalive 기간이 업스트림 서버의 keepalive 타임아웃을 초과하면 HTTP/1.1 업스트림 요청이 "connection reset by peer" 오류로 실패할 수 있어요. 멱등 요청은 Go의 HTTP 전송이 재시도하지만, 다른 경우 Caddy는 502 상태 코드로 응답해요.
-
keepalive_interval은 활성 프로브 사이의 기간이에요. 기본:
30s. -
keepalive_idle_conns는 유지할 최대 연결 수를 정의해요. 기본: 제한 없음.
-
keepalive_idle_conns_per_host는 0이 아닌 경우 호스트당 유지할 최대 유휴(keep-alive) 연결 수를 제어해요. 기본:
32. -
versions는 지원할 HTTP 버전을 커스터마이즈할 수 있게 해요.
유효한 옵션: 1.1, 2, h2c, 3.
기본: 1.1 2, 또는 업스트림의 스킴이 h2c://이면 기본은 h2c 2.
h2c는 업스트림에 대한 평문 HTTP/2 연결을 활성화해요. Go의 기본 HTTP 전송을 사용하지 않는 비표준 기능이므로 다른 기능과 상호 배타적이에요.
3은 업스트림에 대한 HTTP/3 연결을 활성화해요. ⚠️ 이것은 실험적 기능이며 변경될 수 있어요.
-
compression은
off로 설정해 백엔드에 대한 압축을 비활성화하는 데 사용할 수 있어요. -
max_conns_per_host는 다이얼링, 활성, 유휴 상태의 연결을 포함해 호스트당 총 연결 수를 선택적으로 제한해요. 기본: 제한 없음.
-
network_proxy는 업스트림 서버에 대한 요청에 사용할 네트워크 프록시 모듈의 이름을 지정해요. 명시적으로 구성하지 않으면 Caddy는 Go stdlib에 따라 환경 변수(
HTTP_PROXY,HTTPS_PROXY,NO_PROXY)로 구성된 프록시를 존중해요. 이 매개변수에 값이 제공되면 요청은 다음 순서로 리버스 프록시를 통과해요: 클라이언트(사용자) →reverse_proxy→network_proxy→ 업스트림. 내장 모듈: -
none은HTTP_PROXY,HTTPS_PROXY,NO_PROXY의 환경 설정을 무시하는 데 사용돼요. -
url <url>은 환경 구성을 덮어쓰는 단일 URL을 지정하는 데 사용돼요.
fastcgi 전송
transport fastcgi {
root <path>
split <at>
env <key> <value>
resolve_root_symlink
dial_timeout <duration>
read_timeout <duration>
write_timeout <duration>
capture_stderr
}
-
root는 사이트의 루트예요. 기본:
{http.vars.root}또는 현재 작업 디렉터리. -
split은 URI 끝에서 PATH_INFO를 얻기 위해 경로를 나누는 위치예요.
-
env는 주어진 값으로 추가 환경 변수를 설정해요. 여러 환경 변수를 위해 두 번 이상 지정할 수 있어요. 표준 CGI 변수(
SERVER_ADDR포함)와HTTP_*변수로 요청 헤더가 기본적으로 설정돼요. httpoxy를 완화하기 위해 클라이언트의Proxy요청 헤더는HTTP_PROXY로 절대 전달되지 않아요. -
resolve_root_symlink는 심볼릭 링크가 있으면 이를 평가해
root디렉터리를 실제 값으로 해석할 수 있게 해요. -
dial_timeout은 업스트림 소켓에 연결할 때 기다리는 시간이에요. 기간 값을 받아요. 기본:
3s. -
read_timeout은 FastCGI 서버에서 읽을 때 기다리는 시간이에요. 기간 값을 받아요. 기본: 타임아웃 없음.
-
write_timeout은 FastCGI 서버로 보낼 때 기다리는 시간이에요. 기간 값을 받아요. 기본: 타임아웃 없음.
-
capture_stderr는 업스트림 fastcgi 서버가
stderr로 보내는 모든 메시지를 캡처하고 기록하도록 활성화해요. 기본적으로WARN수준으로 기록돼요. 응답이4xx또는5xx상태이면 대신ERROR수준이 사용돼요. 기본적으로stderr는 무시돼요.
현대적인 PHP 애플리케이션을 서빙하려고 한다면, index.php를 라우팅 진입점으로 사용하기 위한 필요한 재작성과 함께 fastcgi 지시문을 사용하는 프록시의 단축키인 php_fastcgi지시문을 찾고 있을 수도 있어요.
응답 가로채기
리버스 프록시는 백엔드의 응답을 가로채도록 구성할 수 있어요. 이를 위해 응답 매처를 정의하고(요청 매처와 유사한 문법), 첫 번째로 일치하는 handle_response 경로가 호출돼요.
응답 핸들러가 호출되면 백엔드의 응답은 클라이언트에 기록되지 않고, 구성된 handle_response 경로가 대신 실행되며, 그 경로가 응답을 기록해야 해요. 경로가 응답을 기록하지 않으면 요청 처리는 이 reverse_proxy 이후에 정렬된 모든 핸들러로 계속돼요.
-
@name은 응답 매처의 이름이에요. 각 응답 매처가 고유한 이름을 가지는 한 여러 매처를 정의할 수 있어요. 응답은 상태 코드와 응답 헤더의 존재 또는 값으로 매치할 수 있어요.
-
replace_status는 주어진 매처가 일치할 때 응답의 상태 코드를 변경해요.
-
handle_response는 주어진 매처가 일치할 때(또는 매처를 생략하면 모든 응답) 실행할 경로를 정의해요. 첫 번째로 일치하는 블록이 적용돼요.
handle_response블록 안에서는 다른 지시문을 사용할 수 있어요.
추가로 handle_response 안에서 두 가지 특수 핸들러 지시문을 사용할 수 있어요:
-
copy_response는 백엔드에서 받은 응답 본문을 클라이언트로 복사해요. 선택적으로 그동안 응답의 상태 코드를 변경할 수 있어요. 이 지시문은
respond앞에 정렬돼요. -
copy_response_headers는 백엔드의 응답 헤더를 클라이언트로 복사해요. 선택적으로 헤더 필드 목록을 포함하거나 제외할 수 있어요(
include와exclude를 동시에 지정할 수 없음). 이 지시문은header뒤에 정렬돼요.
handle_response 경로 안에서 세 가지 placeholder를 사용할 수 있어요:
{rp.status_code}— 백엔드 응답의 상태 코드.{rp.status_text}— 백엔드 응답의 상태 텍스트.{rp.header.*}— 백엔드 응답의 헤더.
리버스 프록시 응답 핸들러는 프록시에서 받은 새 응답을 클라이언트로 복사할 수 있지만, 그 새 응답을 후속 리버스 프록시에 전달할 수는 없어요. reverse_proxy를 사용할 때마다 원래 요청의 본문(또는 다른 모듈로 수정된 것)을 받아요.
예제
모든 요청을 로컬 백엔드로 리버스 프록시:
example.com {
reverse_proxy localhost:9005
}
example.com {
reverse_proxy node1:80 node2:80 node3:80
}
동일하지만 /api 내의 요청만, 그리고 cookie정책으로 고정:
example.com {
reverse_proxy /api/* node1:80 node2:80 node3:80 {
lb_policy cookie api_sticky
}
}
활성 상태 확인으로 건강한 백엔드를 판단하고 실패한 연결에 재시도를 활성화해 건강한 백엔드를 찾을 때까지 요청을 보류:
example.com {
reverse_proxy node1:80 node2:80 node3:80 {
health_uri /healthz
lb_try_duration 5s
}
}
일부 전송 옵션 구성:
example.com {
reverse_proxy localhost:8080 {
transport http {
dial_timeout 2s
response_header_timeout 30s
}
}
}
HTTPS 업스트림으로 리버스 프록시(v2.11.0부터 Caddy가 Host 헤더를 업스트림 호스트와 일치하도록 자동 설정하므로 수동으로 할 필요 없음):
example.com {
reverse_proxy https://example.com
}
HTTPS 업스트림으로 리버스 프록시하되 ⚠️ TLS 검증 비활성화. HTTPS가 제공하는 모든 보안 검사를 비활성화하므로 권장되지 않아요. 가능하면 사설 네트워크에서 HTTP로 프록시하는 게 좋아요. 잘못된 안전감을 피할 수 있으니까요:
example.com {
reverse_proxy 10.0.0.1:443 {
transport http {
tls_insecure_skip_verify
}
}
}
대신 업스트림의 인증서를 명시적으로 신뢰하고 (선택적으로) 업스트림 인증서의 호스트 이름과 일치하도록 TLS-SNI를 설정해 업스트림과 신뢰를 확립할 수 있어요:
example.com {
reverse_proxy 10.0.0.1:443 {
transport http {
tls_trust_pool file /path/to/cert.pem
tls_server_name app.example.com
}
}
}
프록시 전에 경로 접두사를 제거해요. 하지만 하위 폴더 문제를 유의해요:
example.com {
handle_path /prefix/* {
reverse_proxy localhost:9000
}
}
rewrite를 사용해 프록시 전에 경로 접두사를 교체:
example.com {
handle_path /old-prefix/* {
rewrite /new-prefix{path}
reverse_proxy localhost:9000
}
}
응답 가로채기로 요청된 대로 정적 파일을 서빙하는 X-Accel-Redirect 지원:
example.com {
reverse_proxy localhost:8080 {
@accel header X-Accel-Redirect *
handle_response @accel {
root /path/to/private/files
rewrite {rp.header.X-Accel-Redirect}
method GET
file_server
}
}
}
상태 코드별로 오류 응답을 가로채 업스트림 오류의 커스텀 오류 페이지:
example.com {
reverse_proxy localhost:8080 {
@error status 500 503
handle_response @error {
root /path/to/error/pages
rewrite /{rp.status_code}.html
file_server
}
}
}
A/AAAA레코드 DNS 쿼리에서 동적으로 백엔드 가져오기:
example.com {
reverse_proxy {
dynamic a example.com 9000
}
}
SRV레코드 DNS 쿼리에서 동적으로 백엔드 가져오기:
example.com {
reverse_proxy {
dynamic srv _api._tcp.example.com
}
}
활성 상태 확인과 health_upstream을 사용하면 더 철저한 상태 확인을 수행하는 중간 서비스를 만들 때 유용할 수 있어요. 그런 다음 {http.reverse_proxy.active.target_upstream}를 헤더로 사용해 원래 업스트림을 상태 확인 서비스에 제공할 수 있어요.
example.com {
reverse_proxy node1:80 node2:80 node3:80 {
health_uri /health
health_upstream 127.0.0.1:53336
health_headers {
Full-Upstream {http.reverse_proxy.active.target_upstream}
}
}
}