caddy-l4 で Caddy を TCP リバースプロキシ・SSH プロキシにする
標準の Caddy がプロキシできるのは HTTP だけです。生の TCP や SSH には、caddy-l4 の layer4 アプリを組み込んで Caddy をビルドし、ポートを転送するか、プロトコル判別でポート 443 を SSH と HTTPS で共有します。
結論:標準の 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 クライアント、ゲームのプロトコルはそれを送らないので、Caddy は TLS や HTTP のエラーを返してしまいます。
caddy-l4 は、生の接続を扱う別の `layer4` アプリを追加します。接続の最初のバイト(SSH バナー、SNI 付きの TLS ClientHello、HTTP リクエスト行、PostgreSQL のスタートアップなど)をマッチングし、プロキシしたり、TLS を終端したり、ストリームを別のハンドラーに渡したりできます。`http` と同じくアプリの1つなので、両方を同じプロセスで動かせます。
| 必要な機能 | 標準の Caddy | Caddy + 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` アドレスを指定する)とともに、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"]}]}
]
}
]
}
}
}
}
}起動とテスト
- 01.JSON を `/etc/caddy/caddy.json` として保存し、検証します。
- 02.サービスを停止し、接続しながらログを確認できるよう Caddy をフォアグラウンドで実行します。
- 03.ドロップインのオーバーライドで systemd ユニットが JSON ファイルを使うよう設定し、`sudo systemctl daemon-reload` と `sudo systemctl start caddy` を実行します。
- 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 ルートを1つのプロセスで扱いたい場合に適しています。TCP 中心の構成では、Debian や Ubuntu のリポジトリにある成熟した代替ツールの方が運用しやすいかもしれません。`mode tcp` の HAProxy は `req.ssl_sni` で SNI ルーティングを行い、詳細な統計とヘルスチェックを備えています。sslh は、SSH、TLS、OpenVPN などで1つのポートを共有するためだけに作られた小さなデーモンです。どちらも apt でインストールでき、システムの他の部分と一緒に更新されるので、カスタムビルドを保守する必要がありません。
SSH を 443 で公開すると、Web ポートしか許可しないネットワークでは役立ちますが、セキュリティ機能ではありません。SSH 強化ガイドに従い、鍵認証のみを維持してください。NoctisNode のVPSではフル root 権限があるので、これらのツールのどれでも必要なポートをバインドできます。HTTPS 側の証明書の挙動はCaddy の自動 HTTPSを、コンテナについてはDocker リバースプロキシとしての Caddyを参照してください。
| ツール | 長所 | トレードオフ |
|---|---|---|
| Caddy + caddy-l4 | 自動 HTTPS と TCP を1プロセスで、プロトコルマッチャー、JSON API | カスタムビルドが必要、モジュールはまだ発展途上 |
| HAProxy | 成熟した TCP/HTTP プロキシ、SNI ルーティング、充実したヘルスチェックと統計 | 設定量が多い、ACME クライアントは内蔵していない |
| sslh | SSH/TLS/VPN のポート共有に特化した小型ツール | 多重化のみ。TLS 終端やロードバランシングはできない |
よくある質問
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 を確認してください。