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

安全替换软件包自带的二进制文件

如果 Caddy 是从官方 apt 仓库安装的,官方文档推荐用 `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 端口,并把每个连接转发到私有地址上的 PostgreSQL 服务器,例如 WireGuard 隧道的另一端。没有匹配器意味着所有连接都走这条路由。上游可以填写多个 `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 应用。由于 443 端口已归 layer4 所有,HTTP 应用不能再绑定它:在 Caddyfile 全局选项中设置 `https_port 8443`(或在 JSON 中设置等效的 `listen` 地址),并保持 80 端口开放以完成 HTTP-01 质询。设置 `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.通过 drop-in 覆盖让 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,并希望用一个进程处理 HTTPS 和少量 TCP 路由,caddy-l4 是不错的选择。对于以 TCP 为主的场景,Debian 和 Ubuntu 仓库中的成熟替代方案可能更易运维:`mode tcp` 下的 HAProxy 可用 `req.ssl_sni` 按 SNI 路由,并提供详细的统计和健康检查;sslh 则是专为在 SSH、TLS、OpenVPN 等之间共享一个端口而设计的小型守护进程。两者都通过 apt 安装,并随系统一起更新,无需维护自定义构建。

将 SSH 暴露在 443 端口有助于在只放行 Web 端口的网络中使用,但它并不是安全特性。请保持 SSH 加固指南中的仅密钥认证。在 NoctisNode VPS 上你拥有完整 root 权限,上述任何工具都可以绑定所需端口;HTTPS 端的证书行为请参阅 Caddy 自动 HTTPS,容器场景请参阅将 Caddy 用作 Docker 反向代理。

工具优势取舍
Caddy + caddy-l4一个进程同时处理自动 HTTPS 与 TCP,协议匹配器,JSON API需自定义构建,模块仍在演进
HAProxy成熟的 TCP/HTTP 代理,SNI 路由,丰富的健康检查与统计配置较多,无内置 ACME 客户端
sslh小巧,专为 SSH/TLS/VPN 端口共享而生只做复用;不支持 TLS 终止或负载均衡

常见问题

QCaddy 能代理 TCP 流量吗?

A开箱即用不行。核心版 Caddy 只代理 HTTP;要代理原始 TCP 或 UDP,你需要包含第三方 caddy-l4(layer4)模块的 Caddy 构建。

Q如何通过 Caddy 代理 SSH?

A用 caddy-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