Caddy代理Gin完全可行,但需对齐协议、路径与头信息:Gin应监听HTTP(禁用HTTPS)、绑定0.0.0.0,Caddy reverse_proxy须显式写http://,子路径需uri strip_prefix,WebSocket需transport http { upgrade },且Gin需解析X-Forwarded-For获取真实IP。

直接用 Caddy 代理 Gin 服务完全可行,但必须让 Gin 和 Caddy 的协议、路径、头信息对齐,否则会出现 502、404 或 WebSocket 断连——尤其是 Gin 默认不处理 X-Forwarded-For 和 X-Forwarded-Proto,而 Caddy 默认会加这些头。
确认 Gin 启动方式与监听地址
Gin 服务不能绑定到 localhost 或 127.0.0.1 以外的地址(比如 0.0.0.0),否则 Caddy 从本机反代时可能因网络栈绕行失败;同时要禁用 Gin 自带的 HTTPS(除非你真要用自签名证书并手动配 Caddy 跳过验证)。
- 启动 Gin 时显式指定
http.ListenAndServe(":8080", r),不要用router.Run(":8080")的快捷封装(它内部做了额外日志和 panic 捕获,有时干扰代理链路) - 确保 Gin 不监听
https://——Caddy 终结 TLS,Gin 只走纯 HTTP - 如果 Gin 需要识别真实客户端 IP,得在中间件里手动读取
X-Forwarded-For头,例如:func RealIPMiddleware() gin.HandlerFunc { return func(c *gin.Context) { ip := c.Request.Header.Get("X-Forwarded-For") if ip != "" { c.Request.RemoteAddr = strings.Split(ip, ",")[0] } c.Next() } }
Caddyfile 中 reverse_proxy 必须显式声明协议
Caddy 的 reverse_proxy 指令默认按 HTTP/1.1 发起请求,但如果 Gin 服务启用了 HTTP/2 或 gRPC,或你用了自定义端口且没开 HTTPS,漏写协议会导致连接被拒绝或返回空响应。
- 正确写法是:
reverse_proxy http://127.0.0.1:8080(注意开头的http://) - 错误写法:
reverse_proxy 127.0.0.1:8080——Caddy 会尝试用 HTTPS 连接,而 Gin 没开 TLS,直接报remote error: tls: handshake failure - 若 Gin 服务跑在 Docker 容器内且与 Caddy 不在同一网络,地址应填容器名+端口,如
reverse_proxy http://gin-app:8080,而非127.0.0.1
子路径代理时 Gin 的 BasePath 必须同步配置
如果你用 example.com/api 代理到 Gin,但 Gin 路由注册的是 /users,Caddy 默认会把 /api/users 原样转发过去,Gin 找不到匹配路由,返回 404。
- 方案一(推荐):Caddy 剥离前缀,再转发:
example.com/api { uri strip_prefix /api reverse_proxy http://127.0.0.1:8080 } - 方案二:Gin 内部用
gin.Group("/api")注册所有路由,保持路径一致 - 切勿只改 Caddy 配置却不调整 Gin 路由或不加
uri strip_prefix,这是 404 最常见原因
WebSocket 支持需要显式启用 upgrade 头
Gin 本身支持 WebSocket,但 Caddy 默认不透传 Upgrade 和 Connection 头,导致握手失败,浏览器控制台报 Error during WebSocket handshake: Unexpected response code: 400。
- 必须在 Caddyfile 的
reverse_proxy块中加transport http {子块,并启用升级:reverse_proxy http://127.0.0.1:8080 { transport http { keepalive 30s keepalive_idle 30s upgrade } } - 如果 Gin 使用了
gin-contrib/cors中间件,需确保AllowOrigins包含 Caddy 的域名(不是 Gin 的本地地址) - 检查 Gin 日志是否打印
websocket: request origin not allowed,这是 CORS 或 Origin 头未透传的明确信号
最易被忽略的是 uri strip_prefix 和 transport http { upgrade } 这两个配置项——它们不写,服务看起来“能访问”,但子路径路由 404、WebSocket 握手静默失败,问题难以定位。别依赖默认行为,每个代理环节都要显式声明意图。


















