PR

Caddyのreverse_proxyが502になる?HTTP・HTTPS取り違えをXServer VPSで実測

【PR】この記事にはA8.netのアフィリエイト広告を含みます。

先に結論:Caddy 2.10.2reverse_proxyで平文HTTP upstreamをhttps://指定すると、XServer VPS上の実測では5/5回が502になり、ログへtls: first record does not look like a TLS handshakeが残りました。自己署名HTTPSはschemeを正しくしてもCA未信頼なら5/5回が502です。HTTP/HTTPS、証明書trustHost headerを順に確認すれば切り分けられるため、判定は切り分けについて解決です。

Caddyの502はHTTPとHTTPSの取り違えで起きる?

起きます。ただし、取り違える向きによって同じstatusになるとは限りません。今回、HTTP serverへTLSで接続すると502、HTTPS serverへ平文HTTPで接続すると400になりました。

悩みの起点にしたRedditAuthentik behind Caddy Docker Proxy」では、Authentikの9443番へproxyした際にClient sent an HTTP request to an HTTPS serverが出て、HTTP portへ変えると502になったと報告されています。投稿者はupstreamhttpsを明示して解決したと追記しています。

Caddy公式ではupstream schemeをどう扱う?

Caddy公式のreverse_proxy docsによると、schemeを省略したupstreamは既定で平文HTTPです。https://を付けるとHTTP transportのTLSが有効になります。port番号だけでHTTPSとは推測されません。

# 平文HTTP upstream
reverse_proxy http://app:8080

# HTTPS upstream
reverse_proxy https://app:8443

つまり、外側がHTTPSかどうかと、Caddyからappまでの内側通信がHTTPかHTTPSかは別設定です。browserCaddyがHTTPSでも、CaddyappはHTTPという構成は普通にあります。

XServer VPSでどんな実験をした?

項目 実測条件
VPS XServer VPS、4 vCPU4GB RAM、150GB
OS Ubuntu 26.04
Caddy 2.10.2-alpine
upstream 平文HTTP :8080、内部CAのHTTPS :8443
proxy 一時Caddy :8081
network Docker internal network、公開portなし
試行 6設定×5回、計30 request
上限 Caddy 0.25 CPU128MiB RAMPID 96

本番Caddyfileや公開portは変更していません。HTTP upstream、HTTPS upstream、proxyclientは一時network内だけで起動し、statusbodyproxy logcgroup値を採取しました。

[PR]
VPSでDockerとCaddyを試すなら『XServer VPS』

HTTP・HTTPSの6設定はどうなった?

設定 5回のstatus 中央値 body
HTTP→HTTP 200×5 358.174ms upstream-http-ok
HTTPをHTTPS扱い 502×5 349.647ms
HTTPSをHTTP扱い 400×5 305.802ms
HTTPS、CA未信頼 502×5 252.519ms
HTTPS、検証skip、元Host 200×5 296.964ms
HTTPS、検証skipupstream Host 200×5 266.775ms upstream-https-ok

30/30 requestcaseごとの期待statusと一致しました。ただし「200だから正常」とは限りません。5番目はstatus 200でもbodyが0 byteでした。health checkstatusだけでなく、期待するJSON fieldや文字列まで検証する必要があります。

HTTP upstreamをHTTPS指定すると何が記録された?

proxyを次のように設定しました。

reverse_proxy https://llmo-exp-30-upstream-http:8080

平文HTTP serverへTLS ClientHelloを送ったため、5回すべて502になりました。Caddyapplication logには各requestで次のmessagestatusが残りました。

tls: first record does not look like a TLS handshake
status: 502
err_trace: reverseproxy.statusError

接続拒否やDNS errorとは違い、TCP接続先は存在するもののprotocolが違う状態です。container名とportだけでなく、app側がそのportで実際にHTTPとHTTPSのどちらを話すかを確認します。

HTTPS upstreamをHTTP指定すると必ず502になる?

今回のCaddy同士では502ではなく400でした。http://...:8443で平文GETを送ると、TLS listenerがHTTP requestを受けて400 Bad Requestを返し、proxyがそのstatusclientへ渡しました。

upstream実装が接続を切る場合は502になることもあります。statusだけで原因を固定せず、Caddy logupstream logschemeportの4点を合わせて判断します。

schemeをHTTPSへ直したのに502のままだった理由は?

自己署名HTTPS upstreamをhttps://で指定すると、protocolは一致しましたがCAが未信頼でした。5/5回が502で、次の実ログを取得しました。

tls: failed to verify certificate:
x509: certificate signed by unknown authority

schemeを直すことと、証明書を信頼できることは別条件です。productionでは公開CAの証明書、または管理しているprivate CACaddyへ明示的に信頼させます。

自己署名HTTPS upstreamはどう直す?

Caddy公式docsには、tls_trust_pool fileupstreamCAを信頼し、必要ならtls_server_nameを証明書のhostnameへ合わせる例があります。

reverse_proxy https://app:8443 {
  transport http {
    tls_trust_pool file /run/certs/upstream-ca.pem
    tls_server_name app.internal
  }
}

今回のtls_insecure_skip_verifyは、CA検証が原因かを判定する診断だけに使いました。公式docsproductionでは使わないよう明記しています。検証を止めたまま運用すると、中間者攻撃や接続先偽装を検出できません。

200なのにbodyが空だったのはなぜ?

Caddy 2.10.2では受信したHostがHTTPS upstreamへそのまま渡りました。TLS handshakeを通過しても、upstream Caddysite addressHostが一致せず、期待routeに入らなかったため200・0 byteになりました。

reverse_proxy https://app:8443 {
  header_up Host {upstream_hostport}
  transport http {
    tls_insecure_skip_verify
  }
}

header_up Host {upstream_hostport}を加えると、upstream-https-okを5/5回取得できました。Caddy公式docsでは、HTTPS upstreamのHost調整はv2.11.0以降に自動化されています。今回のXServer VPSは2.10.2だったため、version差も問題の一部でした。

Caddy 502を調べる順番は?

  1. Docker network内からupstream名とportへ到達できるか確認する。
  2. upstreamがHTTPかHTTPSか、app公式設定または直接requestで確認する。
  3. reverse_proxyへ正しいhttp://またはhttps://を明示する。
  4. HTTPSならcertificate chainCA trustSNI名を確認する。
  5. Caddy 2.10以前ではHost headerupstream routeと一致するか確認する。
  6. statusだけでなくresponse bodyCaddy error messageを記録する。

first record does not look like a TLS handshakeならHTTPS→HTTP serverの可能性、x509: certificate signed by unknown authorityならschemeは合っているがtrust設定が不足している可能性が高いです。

実験中にどんな問題が起きた?

初回は隔離性を上げるため--cap-drop=ALLno-new-privilegesを指定しましたが、Caddyが起動前に停止しました。さらにcollectorstdoutしか読んでおらず、最初は停止理由が空に見えました。

stderrも保存するよう直すと、exec /usr/bin/caddy: operation not permittedを確認できました。Caddy binaryfile capabilitycontainerbounding setが合っていなかったため、公開portを作らずNET_BIND_SERVICEだけを戻しました。再実験は正常に完走しました。

CPURAM・ストレージへの影響は?

request時間には一時client containerの起動を含みます。upstreamだけの純network latencyではなく、設定差分を同条件で再現するための値です。

本番サービスと後片付けは?

公開/health/readyは開始前、実験中、終了後の全3回でHTTP 200でした。database=okartifact_storage=okも維持しています。

本番CaddyFastAPIPostgreSQLは前後ともrestart 0でした。一時container 3個とinternal networkを削除し、dangling volumeは前後0件、公開portも0件です。

初心者が迷いそうなポイントは?

  • browserCaddyのHTTPSと、Caddyappprotocolは別。
  • 9443や8443というport名だけではCaddyはHTTPSを推測しない。
  • schemeを直しても、自己署名CAが未信頼なら502になる。
  • tls_insecure_skip_verifyは恒久対策ではない。
  • 200でもbodyが期待値と違えば正常とはいえない。
  • Caddy 2.10と2.11以降ではHTTPS upstreamのHost処理が異なる。
  • docker logsstdoutstderrの両方を確認する。

XServer VPSでこの悩みはどこまで解決した?

切り分けについて解決です。HTTPをHTTPS扱いした502、HTTPSをHTTP扱いした400、CA未信頼の502、Host不一致の200・空bodyを別々に再現できました。正しいHTTP設定と、診断用にtrustHost条件をそろえたHTTPS設定は各5/5回200でした。

production用のprivate CA trust poolAuthentik本体、mTLS、複数upstreamload balancingは未検証です。次の実装ではtls_trust_pool fileを使い、certificate検証を有効にしたままHTTPS upstreamへ接続します。

よくある質問

Q. Caddyのreverse_proxyはscheme省略時にHTTPSになりますか?

A. なりません。既定は平文HTTPです。HTTPS upstreamにはhttps://を明示します。

Q. 502ならHTTP・HTTPS取り違えで確定ですか?

A. 確定ではありません。DNS、接続拒否、timeoutcertificate検証でも502になります。error logmessageで区別します。

Q. first record does not look like a TLS handshakeとは何ですか?

A. CaddyがTLSとして接続した先が平文HTTPなど、TLS以外の最初のbyteを返した可能性が高いという意味です。

Q. 自己署名証明書はtls_insecure_skip_verifyでよいですか?

A. 診断用途に限定します。本番ではCA certificatetls_trust_pool fileで信頼させます。

Q. status 200なら修正完了ですか?

A. いいえ。今回もHost不一致で200・空bodyが出ました。期待bodyJSON fieldapplication-level healthまで確認します。

[PR]
XServer VPSの公式情報を確認する

実験日:2026年9月2日。数値は筆者が契約中のXServer VPS 4GB環境における各5回の実測です。Caddy versionupstreamcertificateDocker networkにより結果は変わります。

コメント

タイトルとURLをコピーしました