Docker 服务配置反向代理与 HTTPS:隔离源站并验证转发
反向代理的价值不只是把域名转发到容器端口。可靠的入口还要终止 TLS、隐藏源站、传递经过清洗的客户端信息,并兼容 WebSocket、SSE、上传和长请求。
本文的主路径假设 Caddy 安装在 Linux 宿主机并由 systemd 管理,应用通过宿主机 127.0.0.1:8080 提供 HTTP,公网域名为 app.example.com。实际部署必须替换为自己的域名、端口和应用真实路径,并先确认应用支持的 Base URL、可信代理与回调地址配置。没有公网域名或入站 80/443 时,不要依赖下面的公开证书自动签发路径。
前置条件
域名的 DNS 记录指向服务器,云安全组和主机防火墙允许公网访问代理所需的 80/443,但不放行应用端口。安装 Caddy 官方发行包并确认 caddy systemd 服务可用。应用 Compose 将源站限制在宿主机回环地址:
services:
app:
image: example/app:1.2.3
ports:
- "127.0.0.1:8080:8080"在 Compose 项目目录执行 docker compose config --quiet、docker compose up -d,然后从宿主机用 curl -fsS http://127.0.0.1:8080/ 验证源站路径;若应用要求登录,401 或跳转也可能表明服务已响应,应按应用文档核对。不要同时保留 0.0.0.0:8080:8080,否则客户端可以绕过代理、TLS 和代理层认证直接访问应用。Docker Engine 早于 28.0.0 时,同一二层网络中的其他主机可能访问绑定 localhost 的已发布端口;应升级 Engine,并从外部网络验证隔离效果。
这里的 127.0.0.1 属于宿主机。若代理也运行在容器内,容器的回环地址指向代理容器自身,不能照抄本例;将代理和应用接入同一受控 Compose 网络,使用服务名与容器端口访问上游,并仅发布代理的 80/443。容器内代理的网络、证书持久化和权限应另行配置。
使用 Caddy
将以下内容保存到宿主机 /etc/caddy/Caddyfile。公网 DNS 和端口满足要求时,Caddy 会尝试自动申请并续期证书:
app.example.com {
reverse_proxy 127.0.0.1:8080
}配置前先确认该文件没有其他正在服务的站点需要保留;修改已有配置时只增加站点块。校验配置后重新加载正在运行的服务:
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager若服务未启动,按安装包的 service 文档启动,再查看 journalctl -u caddy -n 100 --no-pager 定位签发、端口或解析问题。Caddy 默认处理常见代理头并支持 WebSocket;默认忽略来自不受信任客户端的 X-Forwarded-* 值。应用仍需要设置公开 Base URL,且只信任代理的转发信息。CDN 或多层代理必须另外限定可信上游网段,不能直接信任所有来访头。
使用 Nginx
若使用宿主机 Nginx 代替 Caddy,先由可信证书平台或 ACME 客户端取得域名证书并安排自动续期;以下路径必须替换为实际证书文件。示例放在 Nginx 的 http 上下文中(常见发行版的 conf.d/*.conf 由 http 包含),假设 Nginx 是公网第一跳:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name app.example.com;
return 308 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/ssl/app/fullchain.pem;
ssl_certificate_key /etc/ssl/app/privkey.pem;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
location / { proxy_pass http://127.0.0.1:8080; }
# 仅当应用确有 /events/ 流式路由时启用,并替换为真实路径。
# location /events/ {
# proxy_pass http://127.0.0.1:8080;
# proxy_buffering off;
# proxy_read_timeout 300s;
# }
}测试并加载配置:
sudo nginx -t
sudo systemctl reload nginxnginx -t 失败时不要 reload;检查文件放置位置、证书路径和权限。证书私钥限制为代理进程所需的最小读取权限,不能放入文章、镜像或公开仓库。续期完成后还要确保 Nginx 重新加载新证书。直连边缘代理用 $remote_addr 覆盖客户端的 X-Forwarded-For;若前面已有 CDN 或其他代理,先明确其可信地址范围与真实 IP 处理链,不能直接照抄这条规则或无条件使用 $proxy_add_x_forwarded_for。
WebSocket、SSE 和长请求
上面的 Nginx 配置通过动态 Connection 值处理 WebSocket 握手;Caddy 的 reverse_proxy 也支持 WebSocket。SSE 或流式响应可按实际路由启用示例中的 proxy_buffering off 和读取超时;不要把长超时应用到所有未知接口。上传大小、超时和缓冲策略应按实际协议调整,并验证客户端断开后上游请求是否正确取消。Caddy 重载配置可能关闭现存 WebSocket 长连接,更新窗口应考虑客户端重连。
可信转发头
客户端可以自行伪造 X-Forwarded-For。只有边缘代理清洗或覆盖外部请求中的转发头,并且应用只能从代理入口访问时,应用才可以把这些头用于真实 IP、HTTPS 判断或安全审计。经过 CDN 或多层代理时,应按明确代理链选择可信来源,不能无条件信任最左侧地址。
验证 HTTPS 和源站隔离
从服务器外的网络检查 HTTPS、重定向和证书,不要使用 -k 跳过证书校验:
curl -Iv https://app.example.com/
curl -I http://app.example.com/http 应跳转到 https,TLS 证书须与域名匹配。某些应用不支持 HEAD,此时用 GET 验证业务路径。在外部网络确认 app.example.com:8080 无法连接,再从服务器宿主机验证源站仍可访问:
curl -fsS http://127.0.0.1:8080/登录应用,检查生成链接和回调地址使用 https://app.example.com。存在 WebSocket、SSE、上传或大文件下载时,分别执行真实业务请求;首页返回 200 不能证明这些协议已经可用。
更新与回滚
修改代理前保存当前配置并运行 Caddy 或 Nginx 的语法检查。新配置验证失败时恢复旧配置再 reload,不要在证书和源站同时变化时删除唯一可用副本。应用升级流程见 Docker Compose 更新、回滚与卸载服务。
安全边界
反向代理不能替代应用认证。无认证 API 即使使用 HTTPS,任何能够访问域名的人仍可能调用。管理入口应增加身份认证、来源限制或 VPN;源站、防火墙、代理和应用四层需要共同阻止绕过。TLS 私钥、Basic Auth 文件和上游令牌不得写入共享配置示例。
常见问题
代理后出现重定向循环
检查应用公开 URL、HTTPS 判断和可信代理设置,并确认代理发送 X-Forwarded-Proto: https。不要同时在多个层重复强制跳转。
页面能打开但 WebSocket 失败
检查 Upgrade 头、HTTP/1.1、路径匹配和上游端口。浏览器开发者工具中的握手状态比首页响应更有判断价值。
日志中的客户端 IP 都是代理地址
确认代理重写转发头,再按应用文档配置可信代理范围。不要直接信任任意客户端提供的 X-Forwarded-For。
配置完成后
记录域名、证书续期方式、源站地址、代理配置路径和协议验收结果。证书、应用端口、CDN 或代理层变化后重新执行外部访问和源站隔离检查。
总结
Docker 服务的 HTTPS 入口需要同时闭合域名、证书、代理、源站和应用配置。源站绑定回环地址,代理清洗转发头,再分别验证普通 HTTP、登录、WebSocket、SSE 和回调,才能避免“首页能开但关键功能不可用”。