haproxy backend health check 失败但 curl 直连正常的协议坑

8次阅读

HAproxy健康检查返回404而curl正常,主因是http/1.0无Host头触发后端路由失败;需配置http-check send hdr Host;状态码校验需显式指定如204或正则匹配;timeout check超时应优化探针路径而非盲目调大;https后端需补SNI和证书配置;中间件(如JWT鉴权)未放行健康路径会导致401。

haproxy backend health check 失败但 curl 直连正常的协议坑

HAProxy backend 健康检查返回 404 但 curl 直连正常

这是最常见的“协议坑”:HAProxy 默认用 HTTP/1.0 发起健康检查,不带 Host 头,而现代 Web 服务(如 nginxcaddy、spring Boot 内置 tomcat)往往依赖 Host 头做虚拟主机路由或安全策略,一收不到就直接 404 或 400。

实操建议:

  • tcpdumphaproxy -d 抓包确认实际发出的健康检查请求内容(尤其看是否有 Host: 行)
  • 在 backend 中显式配置 http-check send hdr Host myapp.local,把关键 Host 头补上
  • 后端强制 HTTPS 重定向,而健康检查走的是 HTTP,则可能收到 301 —— 此时需改用 option httpchk GET /health HTTP/1.1 并配 hdr Host,或直接切到 option ssl-hello-chk(仅 TCP 层握手探测)

HTTP 状态码校验逻辑被默认值误导

HAProxy 的 option httpchk 默认只认 2xx3xx 为成功,但很多健康接口(如 spring boot Actuator)默认返回 200,而有些自定义接口返回 204 No Content4xx(比如 /health 返回 422 Unprocessable Entity 表示部分组件异常但仍可服务)—— 这些都会被判定为失败。

实操建议:

  • http-check expect status 204 显式接受非 200 成功码
  • 用正则匹配响应体:http-check expect status 200 body_regex up(要求响应体含 “up” 字符串
  • 避免写成 http-check expect ! status 500 —— HAProxy 不支持 ! 否定语法,会静默忽略该行

timeout check 超时但 curl 看似很快

curl 直连快 ≠ HAProxy 健康检查快。HAProxy 的 timeout check 是独立于 timeout connect 的,它控制的是从发送完请求到读取完响应头的总耗时;而如果后端应用在健康检查路径里做了同步 DB 查询、远程依赖调用,或启用了慢日志/审计中间件,就容易超这个阈值(默认是 5s)。

实操建议:

  • 先查 HAProxy 日志里的 check 行,找 timeout 关键字确认是否真超时
  • 不要盲目调大 timeout check 10s,应优先优化健康检查路径:比如用内存态探针(/healthz),绕过 ORM 和外部依赖
  • 若必须容忍延迟,记得同步调大 timeout servertimeout connect,否则连接阶段就断了,根本到不了健康检查环节

HTTPS backend 的 SNI 和证书验证陷阱

当 backend 是 https:// 时,HAProxy 默认不发 SNI 扩展,也不验证证书;但某些后端(如 Cloudflare Tunnel、某些 istio Ingress)会拒收无 SNI 的 TLS 握手,或要求证书域名匹配 —— 导致健康检查 TCP 连接直接被 RST,日志里只显示 L4T(Layer 4 timeout)而非 HTTP 错误。

实操建议:

  • server app1 10.0.1.10:443 check ssl verify none sni str(app.example.com),强制发 SNI
  • 若后端证书是私有 CA 签发,且你信任它,用 ca-file /etc/haproxy/certs/internal-ca.pem 替代 verify none 更安全
  • 注意 sni 参数值必须是字符串(str(...)),不能直接写域名,否则 HAProxy 启动报错 invalid argument

实际部署时最容易被忽略的,是健康检查路径和主业务路径共用同一套中间件 —— 比如一个全局 JWT 鉴权 Filter,没对 /health 放行,结果检查永远 401;这种问题不会出现在 curl 直连测试里,因为 curl 没带 Token,反而“误打误撞”绕过了鉴权。

text=ZqhQzanResources