Skip to content

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 将源站限制在宿主机回环地址:

yaml
services:
  app:
    image: example/app:1.2.3
    ports:
      - "127.0.0.1:8080:8080"

在 Compose 项目目录执行 docker compose config --quietdocker 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 会尝试自动申请并续期证书:

caddyfile
app.example.com {
    reverse_proxy 127.0.0.1:8080
}

配置前先确认该文件没有其他正在服务的站点需要保留;修改已有配置时只增加站点块。校验配置后重新加载正在运行的服务:

bash
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/*.confhttp 包含),假设 Nginx 是公网第一跳:

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;
    # }
}

测试并加载配置:

bash
sudo nginx -t
sudo systemctl reload nginx

nginx -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 跳过证书校验:

bash
curl -Iv https://app.example.com/
curl -I http://app.example.com/

http 应跳转到 https,TLS 证书须与域名匹配。某些应用不支持 HEAD,此时用 GET 验证业务路径。在外部网络确认 app.example.com:8080 无法连接,再从服务器宿主机验证源站仍可访问:

bash
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 和回调,才能避免“首页能开但关键功能不可用”。

参考资料

全部公开文章由同一个站点构建和发布。