Grafana 部署在服务器后,最直接的远程访问方式是把容器的 3000 端口映射到公网,例如 3000:3000。这种配置虽然简单,却把应用服务直接暴露给互联网:流量默认没有 TLS 保护,扫描器可以直接识别端口,登录入口也缺少统一的访问策略。即使 Grafana 自身启用了账号密码,公网链路、证书更新、域名跳转和安全响应头仍需要单独处理。
更稳妥的结构是只让反向代理接收公网流量,由它完成 HTTPS 和证书管理;Grafana 仅接入 Docker 内部网络,不发布宿主机端口:
浏览器
│ HTTPS :443
▼
Caddy 反向代理
│ HTTP(Docker 私有网络)
▼
Grafana :3000
这套结构解决的是传输安全和暴露面问题,不等于让 Grafana 自动具备完整的互联网安全能力。管理员仍需维护强密码、账号权限、插件更新、日志审计和数据备份。对于只供少量固定人员使用的面板,VPN 或零信任访问网关通常比直接公网发布更容易收紧边界。
核心原理
TLS 终止与反向代理
浏览器与 Caddy 建立 TLS 连接,证书校验、HTTPS 重定向和加密都发生在这一层。Caddy 解密请求后,通过容器网络把请求转发给 Grafana。Grafana 无须自己读取证书私钥,也不必直接监听公网地址。
反向代理会传递原始主机名、协议和客户端地址等信息。Grafana 需要知道自己的外部地址,否则登录跳转、邮件链接或子路径资源可能指向内部的 http://grafana:3000。因此必须明确配置 domain 与 root_url。
最小暴露面
Docker Compose 中的 expose 只用于声明容器内部端口,不会像 ports 那样将端口发布到宿主机。Grafana 与 Caddy 加入同一个私有网络后,Caddy 可以通过服务名 grafana 访问 3000 端口,而公网只能连接 Caddy 发布的 80 和 443 端口。
80 端口通常用于 HTTP 到 HTTPS 跳转,也可能参与证书签发验证。若采用 DNS 验证或已有外部负载均衡器,端口要求会不同,需按实际证书方案调整。
自动证书的前提
要让 Caddy 自动申请公开可信的证书,至少需要满足以下条件:
- 域名的 A 或 AAAA 记录正确指向服务器公网地址。
- 公网能够访问服务器的 80 和 443 端口。
- 域名不是仅在局域网解析的内部名称。
- Caddy 的数据目录可以持久化,以保存证书和账户信息。
若服务器位于 NAT 后面,还需要在路由器或云负载均衡器上完成端口转发。只有内网地址且无法入站访问时,应改用 VPN、隧道或 DNS 验证方案,不能假设 HTTP 证书验证一定成功。
部署步骤
1. 准备域名与目录
假设使用 grafana.example.com,先在 DNS 控制台添加解析记录,再创建部署目录:
mkdir -p grafana-public
cd grafana-public
不要直接照搬示例域名。替换后可用以下命令检查解析结果:
dig +short grafana.example.com A
dig +short grafana.example.com AAAA
如果配置了 AAAA 记录,必须确保 IPv6 路由和防火墙同样可用;否则客户端可能优先连接不可达的 IPv6 地址。
2. 设置环境变量
在当前终端设置域名、镜像版本和初始管理员密码。版本号应选择经过团队验证的 Grafana 版本,本文不假定某个版本始终是最新版本:
export GRAFANA_DOMAIN='grafana.example.com'
export GRAFANA_VERSION='填写经过验证的版本号'
export GRAFANA_ADMIN_PASSWORD="$(openssl rand -base64 32)"
环境变量只对当前 shell 及其子进程有效。若通过 CI/CD 部署,应从秘密管理系统注入管理员密码,避免写入 Compose 文件、Git 仓库或命令历史。初始密码也不应作为长期共享凭据,首次登录后应建立实名账号并限制管理员数量。
3. 编写 Compose 配置
创建 compose.yaml:
services:
grafana:
image: grafana/grafana:${
GRAFANA_VERSION:?set GRAFANA_VERSION}
restart: unless-stopped
environment:
GF_SERVER_DOMAIN: ${
GRAFANA_DOMAIN:?set GRAFANA_DOMAIN}
GF_SERVER_ROOT_URL: https://${
GRAFANA_DOMAIN}/
GF_SERVER_ENFORCE_DOMAIN: "true"
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: ${
GRAFANA_ADMIN_PASSWORD:?set GRAFANA_ADMIN_PASSWORD}
GF_SECURITY_COOKIE_SECURE: "true"
GF_SECURITY_COOKIE_SAMESITE: strict
GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "false"
volumes:
- grafana-data:/var/lib/grafana
expose:
- "3000"
networks:
- monitoring
caddy:
image: caddy:2
restart: unless-stopped
depends_on:
- grafana
environment:
GRAFANA_DOMAIN: ${
GRAFANA_DOMAIN:?set GRAFANA_DOMAIN}
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks:
- monitoring
networks:
monitoring:
driver: bridge
volumes:
grafana-data:
caddy-data:
caddy-config:
这里没有为 Grafana 配置 ports,因此 服务器IP:3000 不应成为访问入口。443/udp 用于支持 HTTP/3;若网络策略不允许 UDP 443,可删除这一映射,常规 HTTPS 仍可通过 TCP 443 工作。
4. 配置 Caddy
创建 Caddyfile:
{$GRAFANA_DOMAIN} {
encode zstd gzip
header {
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
-Server
}
reverse_proxy grafana:3000
}
不建议在不了解面板嵌入需求时直接添加严格的 Content-Security-Policy 或 X-Frame-Options。这类响应头可能影响 Grafana 插件、外部认证或 iframe 嵌入,应在测试环境根据实际功能逐项收紧。
先检查 Compose 展开结果和 Caddy 配置:
docker compose config
docker compose run --rm caddy caddy validate --config /etc/caddy/Caddyfile
docker compose config 的输出可能包含展开后的管理员密码,不要把完整输出上传到工单或公开日志。
5. 启动并检查状态
docker compose up -d
docker compose ps
docker compose logs --tail=100 caddy
docker compose logs --tail=100 grafana
然后验证 HTTP 跳转、证书和 Grafana 健康接口:
curl -I "http://${GRAFANA_DOMAIN}"
curl -I "https://${GRAFANA_DOMAIN}/login"
curl -fsS "https://${GRAFANA_DOMAIN}/api/health"
openssl s_client -connect "${GRAFANA_DOMAIN}:443" \
-servername "${GRAFANA_DOMAIN}" </dev/null
不要把固定状态码写进自动化判断而忽略跳转差异。更可靠的验收项是:HTTP 最终转到 HTTPS;证书主机名匹配且链路验证通过;登录页资源可以加载;健康接口可访问;外部访问无法连接 3000 端口。
6. 收紧主机防火墙
云安全组和主机防火墙应只开放实际需要的入口。下面是使用 UFW 的示例,执行前必须确认 SSH 端口,避免将自己锁在服务器外:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw enable
sudo ufw status verbose
如果 SSH 不是 22 端口,应替换为真实端口。数据库、Grafana 3000 端口以及 Docker 管理接口都不应面向公网开放。
上线后的安全与运维
公网登录入口会持续遭遇扫描和口令尝试。至少应完成以下工作:
- 禁止匿名访问与自助注册,按团队职责分配 Viewer、Editor 和 Admin 权限。
- 数据源账号采用只读或任务所需的最小权限,不要把数据库超级用户交给 Grafana。
- 定期更新 Grafana、Caddy 和插件;更新前阅读对应版本说明并备份数据。
- 监控反向代理访问日志、Grafana 登录事件和异常状态码。
- 将管理入口限制到 VPN、固定出口 IP 或统一身份认证系统,条件允许时启用多因素认证。
- 对
/var/lib/grafana对应的数据卷做备份,并实际演练恢复。
仅备份 Compose 文件无法恢复仪表盘、用户、告警和本地配置。使用外部数据库保存 Grafana 元数据时,还要按照数据库自身的一致性要求备份,不能只复制容器目录。
可以先停止 Grafana 再归档本地数据卷,以减少写入中的不一致风险。具体卷名由 Compose 项目名决定,应通过 docker volume ls 确认,不能机械套用示例名称。对可用性要求较高的环境,应采用与存储后端匹配的在线备份方案。
常见问题
证书申请失败
先检查 DNS 是否解析到当前服务器,再确认 80、443 端口可以从公网访问。若日志显示连接到了错误地址,常见原因是旧 A 记录、不可达的 AAAA 记录、NAT 未转发或云安全组未放行。频繁重试可能触发证书机构的速率限制,修正网络问题后再重启服务。
页面能打开,但登录后反复跳转
重点检查 GF_SERVER_ROOT_URL 是否为真实的 HTTPS 外部地址,浏览器是否仍保留旧域名 Cookie,以及上游是否还有另一层代理改写协议。如果 TLS 在更外层的负载均衡器终止,还需确保代理链正确传递原始协议,且不要让多个代理互相执行冲突的重定向。
面板中的 WebSocket 或实时功能异常
Caddy 的 reverse_proxy 默认能够处理 WebSocket 升级,一般不需要手写 Upgrade 头。若前面还有 CDN、WAF 或负载均衡器,应逐层确认它们是否允许长连接和协议升级。排查时同时查看浏览器开发者工具与各层代理日志。
访问域名得到 502
502 通常表示 Caddy 无法连接上游。检查 Grafana 容器是否运行、两个服务是否加入同一网络,以及上游名称是否仍为 grafana:3000:
docker compose ps
docker compose logs --tail=200 grafana
docker compose exec caddy wget -qO- http://grafana:3000/api/health
如果最后一条命令使用的镜像环境不包含 wget,可改用同网络中的临时诊断容器,不应据此判断 Grafana 一定故障。
能否把 Grafana 放在 /grafana/ 子路径
可以,但要同时设置 GF_SERVER_ROOT_URL=https://域名/grafana/ 和 GF_SERVER_SERVE_FROM_SUB_PATH=true,并在反向代理中正确处理路径。子路径部署还可能影响 OAuth 回调、插件资源和外部链接,配置完成后需要覆盖登录、静态资源、分享链接及告警链接等场景。没有共享单域名的硬性需求时,独立子域名通常更简单。
总结
将 Grafana 开放到公网的关键不是增加一个端口映射,而是建立明确的边界:Caddy 负责公网 HTTPS 和证书生命周期,Grafana 留在容器私有网络,并通过正确的外部地址配置生成链接和 Cookie。部署完成后还要验证 DNS、跳转、证书、健康接口和 3000 端口的不可达性。
这套方案降低了直接暴露应用端口的风险,但最终安全性仍取决于账号体系、最小权限、网络限制、更新节奏和备份恢复能力。对于内部监控系统,应优先判断它是否真的需要面向整个互联网;能够通过 VPN 或受控身份入口访问时,进一步缩小可访问范围通常更合适。