NoctisNode
← 가이드네트워킹2026-10-239분

caddy-l4로 만드는 Caddy TCP 리버스 프록시와 SSH 프록시

기본 Caddy는 HTTP만 프록시합니다. 원시 TCP와 SSH를 다루려면 caddy-l4 layer4 앱으로 Caddy를 빌드하고, 포트를 전달하거나, 프로토콜별로 SSH와 HTTPS가 443번 포트를 공유하게 하세요.

짧은 답변: 표준 Caddy는 원시 TCP나 SSH를 프록시할 수 없습니다. `reverse_proxy`가 HTTP만 다루기 때문입니다. TCP를 전달하려면 xcaddy로 caddy-l4 모듈(github.com/mholt/caddy-l4)을 포함해 Caddy를 빌드한 뒤, `layer4` 앱이 포트에서 리슨하고 연결을 프록시하도록 설정하세요. 프로토콜에 따라 업스트림을 선택할 수도 있어 SSH와 HTTPS가 443번 포트를 공유할 수 있습니다.

caddy-l4는 기본 Caddy 배포판에 포함되지 않은 별도 모듈이며, README에서도 여전히 버전 간 설정이 바뀔 수 있는 개발 중인 프로젝트로 소개됩니다. 이 가이드는 모듈의 문서화된 구조와 그대로 대응하는 JSON 설정을 사용합니다. HTTP나 HTTPS 프록시만 필요하다면 Caddy 리버스 프록시 가이드로 충분합니다.

기본 Caddy가 HTTP 전용인 이유

Caddy의 내장 앱은 `http`, `tls`, `pki`입니다. `reverse_proxy` 핸들러는 HTTP 앱 안에 있으므로 무언가를 라우팅하기 전에 파싱된 HTTP 요청이 필요합니다. SSH 클라이언트, PostgreSQL 클라이언트, 게임 프로토콜은 HTTP 요청을 보내지 않으므로 Caddy는 TLS 또는 HTTP 오류로 응답하게 됩니다.

caddy-l4는 원시 연결에서 작동하는 별도의 `layer4` 앱을 추가합니다. 연결의 첫 바이트(SSH 배너, SNI가 포함된 TLS ClientHello, HTTP 요청 라인, PostgreSQL 시작 메시지 등)를 매칭한 다음 프록시하거나, TLS를 종료하거나, 스트림을 다른 핸들러에 넘길 수 있습니다. `http`와 같은 앱이므로 둘 다 같은 프로세스에서 실행할 수 있습니다.

필요 기능기본 CaddyCaddy + caddy-l4
자동 TLS를 갖춘 HTTPS 리버스 프록시가능가능
일반 TCP 포트 포워딩불가가능(`proxy` 핸들러)
UDP 포워딩불가가능(`udp/:port`에서 리슨)
SNI 기반 TLS 패스스루불가가능(`sni`가 포함된 `tls` 매처)
같은 포트에서 SSH와 HTTPS불가가능(`ssh` 및 `tls` 매처)
Caddyfile 지원완전부분적, JSON이 기준 형식

layer4 모듈을 포함해 Caddy 빌드하기

xcaddy는 추가 모듈을 포함해 Caddy를 컴파일합니다. 또는 공식 Caddy 다운로드 페이지에서 `mholt/caddy-l4`를 선택해 바로 사용할 수 있는 바이너리를 내려받을 수도 있습니다. 어느 쪽이든 설정을 작성하기 전에 `list-modules`로 `layer4` 모듈이 표시되는지 확인하세요.

# Current Caddy needs a recent Go toolchain; distro golang-go packages are
# often too old, so install the latest release from https://go.dev/dl/
# and make sure /usr/local/go/bin is in PATH, then:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

# Build Caddy with the layer4 app
~/go/bin/xcaddy build --with github.com/mholt/caddy-l4

# Confirm the modules are present
./caddy list-modules | grep layer4

패키지 바이너리를 안전하게 교체하기

공식 apt 저장소에서 Caddy를 설치했다면, 커스텀 빌드로 교체하는 문서화된 방법은 `dpkg-divert`와 `update-alternatives`입니다. 그래야 패키지 업그레이드가 바이너리를 덮어쓰지 않습니다. 이제 Caddy 업데이트를 받으려면 다시 빌드해야 한다는 점을 기억하세요.

sudo dpkg-divert --divert /usr/bin/caddy.default --rename /usr/bin/caddy
sudo mv ./caddy /usr/bin/caddy.custom
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.default 10
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.custom 50
sudo systemctl restart caddy

예제 1: 일반 TCP 리버스 프록시

이 설정은 5432번 포트에서 리슨하고 모든 연결을 사설 주소(예: WireGuard 터널의 반대편)의 PostgreSQL 서버로 전달합니다. 매처가 없으면 모든 연결이 이 경로를 탑니다. 업스트림에는 페일오버를 위해 여러 `dial` 주소를 지정할 수 있으며, `proxy` 핸들러는 HTTP `reverse_proxy`와 비슷한 로드 밸런싱과 헬스 체크를 지원합니다.

백엔드는 클라이언트가 아니라 Caddy의 IP를 봅니다. 백엔드가 PROXY 프로토콜을 이해한다면 proxy 핸들러에 `"proxy_protocol": "v2"`를 설정해 원래 주소를 전달하세요.

{
  "apps": {
    "layer4": {
      "servers": {
        "postgres": {
          "listen": [":5432"],
          "routes": [
            {
              "handle": [
                {
                  "handler": "proxy",
                  "upstreams": [{"dial": ["10.8.0.2:5432"]}]
                }
              ]
            }
          ]
        }
      }
    }
  }
}

예제 2: 443번 포트에서 SSH와 HTTPS 다중화

SSH 클라이언트는 연결하자마자 버전 배너(`SSH-2.0-...`)를 보내므로, caddy-l4는 서버가 응답하기 전에 SSH를 인식할 수 있습니다. 아래 설정은 SSH를 로컬 sshd로, `git.example.com`의 TLS는 자체적으로 TLS를 종료하는 서비스로, 나머지 TLS는 8443번에서 리슨하는 Caddy의 HTTP 앱으로 보냅니다. 이제 layer4가 443번 포트를 차지하므로 HTTP 앱은 이 포트에 바인딩하면 안 됩니다. Caddyfile 전역 옵션에 `https_port 8443`을 설정하거나 JSON에서 동등한 `listen` 주소를 설정하고, HTTP-01 챌린지를 위해 80번 포트는 열어 두세요. `https_port 8443`을 설정하면 Caddy의 자동 HTTP→HTTPS 리디렉션이 8443번 포트를 가리키게 되므로, `auto_https disable_redirects`를 설정하고 직접 `http:// { redir https://{host}{uri} permanent }` 사이트를 추가하세요.

SMTP, FTP, MySQL처럼 서버가 먼저 말하는 프로토콜은 클라이언트가 조용히 기다리기 때문에 이 방식으로 감지할 수 없습니다. 이런 프로토콜에는 별도 포트를 주세요.

{
  "apps": {
    "layer4": {
      "servers": {
        "mux443": {
          "listen": [":443"],
          "routes": [
            {
              "match": [{"ssh": {}}],
              "handle": [
                {"handler": "proxy", "upstreams": [{"dial": ["127.0.0.1:22"]}]}
              ]
            },
            {
              "match": [{"tls": {"sni": ["git.example.com"]}}],
              "handle": [
                {"handler": "proxy", "upstreams": [{"dial": ["127.0.0.1:3443"]}]}
              ]
            },
            {
              "match": [{"tls": {}}],
              "handle": [
                {"handler": "proxy", "upstreams": [{"dial": ["127.0.0.1:8443"]}]}
              ]
            }
          ]
        }
      }
    }
  }
}

실행과 테스트

  1. 01.JSON을 `/etc/caddy/caddy.json`으로 저장하고 검증하세요.
  2. 02.서비스를 중지하고 Caddy를 포그라운드에서 실행해, 연결하는 동안 로그를 지켜보세요.
  3. 03.드롭인 오버라이드로 systemd 유닛이 JSON 파일을 사용하도록 지정한 뒤 `sudo systemctl daemon-reload`와 `sudo systemctl start caddy`를 실행하세요.
  4. 04.`ssh -p 443`으로 접속하고 브라우저에서 HTTPS 사이트를 열어 두 경로가 모두 작동하는지 확인하세요.
# Validate and test in the foreground first
sudo caddy validate --config /etc/caddy/caddy.json
sudo systemctl stop caddy
sudo caddy run --config /etc/caddy/caddy.json

# Make the service use the JSON file: sudo systemctl edit caddy
[Service]
ExecStart=
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/caddy.json
ExecReload=
ExecReload=/usr/bin/caddy reload --config /etc/caddy/caddy.json --force

# Client side
ssh -p 443 [email protected]

HAProxy나 sslh를 대신 써야 할 때

caddy-l4는 이미 Caddy를 운영 중이고 HTTPS와 몇 개의 TCP 경로를 하나의 프로세스로 처리하고 싶을 때 잘 맞습니다. TCP 위주의 구성이라면 Debian과 Ubuntu 저장소에 있는 성숙한 대안이 운영하기 더 쉬울 수 있습니다. `mode tcp`의 HAProxy는 `req.ssl_sni`로 SNI 기반 라우팅을 하며 상세한 통계와 헬스 체크를 제공하고, sslh는 SSH, TLS, OpenVPN 등이 하나의 포트를 공유하도록 만들어진 작은 데몬입니다. 둘 다 apt로 설치하고 시스템의 나머지와 함께 업데이트되므로 유지해야 할 커스텀 빌드가 없습니다.

443번에서 SSH를 노출하면 웹 포트만 허용하는 네트워크에서 도움이 되지만, 보안 기능은 아닙니다. SSH 강화 가이드에 따라 키 전용 인증을 유지하세요. NoctisNode VPS에서는 전체 root 권한이 있으므로 이 도구들 모두 필요한 포트에 바인딩할 수 있습니다. HTTPS 쪽 인증서 동작은 Caddy 자동 HTTPS를, 컨테이너는 Docker 리버스 프록시로서의 Caddy를 참고하세요.

도구장점단점
Caddy + caddy-l4자동 HTTPS와 TCP를 하나의 프로세스로, 프로토콜 매처, JSON API커스텀 빌드, 모듈이 아직 발전 중
HAProxy성숙한 TCP/HTTP 프록시, SNI 라우팅, 풍부한 헬스 체크와 통계설정이 더 많음, 내장 ACME 클라이언트 없음
sslhSSH/TLS/VPN 포트 공유 전용의 작은 도구다중화만 함, TLS 종료나 로드 밸런싱 없음

FAQ

QCaddy로 TCP 트래픽을 프록시할 수 있나요?

A기본 상태로는 안 됩니다. 기본 Caddy는 HTTP만 프록시합니다. 원시 TCP나 UDP에는 서드파티 caddy-l4(layer4) 모듈이 포함된 Caddy 빌드가 필요합니다.

QCaddy를 통해 SSH를 프록시하려면 어떻게 하나요?

Acaddy-l4로 Caddy를 빌드하고, `ssh`를 매칭해 `127.0.0.1:22`로 프록시하는 layer4 서버를 추가한 다음 `ssh -p <port>`로 접속하세요. 443번 포트에서는 `tls` 경로와 결합해 HTTPS도 계속 작동하게 할 수 있습니다.

QSSH와 HTTPS가 443번 포트를 공유할 수 있나요?

A네. caddy-l4, sslh, HAProxy 모두 연결의 첫 바이트를 검사해 같은 포트에서 SSH와 TLS를 서로 다른 백엔드로 라우팅할 수 있습니다.

Qcaddy-l4는 Caddyfile을 지원하나요?

A`layer4` 전역 옵션을 통해 Caddyfile을 지원하지만, 가장 완전하고 잘 문서화된 형식은 여전히 JSON입니다. 빌드하는 버전의 모듈 README를 확인하세요.

caddy-l4로 만드는 Caddy TCP 리버스 프록시와 SSH 프록시 · NoctisNode