Appearance
Golang 项目生产环境完整部署方案
适用场景
- 预发布环境 / 灰度验证环境 / 内部测试环境
- 核心需求:需要对外网隐藏,仅允许团队成员或特定 IP 段访问,但不希望在应用代码中实现登录逻辑。
架构提醒
如果您的前端是 Nuxt.js / Next.js 等 SSR 框架,并且使用独立的 API 子域名(如 api.yourdomain.com),请务必阅读 5.4 节 的冲突说明。强烈建议使用 Cloudflare Access 替代 Nginx 基础认证。
本指南遵循 Linux 文件系统层次规范 (FHS),结合 Systemd、Nginx 与 Cloudflare,提供一套从代码编译到上线的标准化、可复用的部署流程。
1. 项目构建 (本地)
假设您的 Golang 项目名为 myapp,主程序入口输出为 myapp-apiserver。
bash
# 启用 Go 模块代理
go env -w GOPROXY=https://goproxy.cn,direct
# 为 Linux 环境交叉编译
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o myapp-apiserver .CGO_ENABLED=0: 生成静态依赖的可执行文件,避免运行时动态库缺失-ldflags="-s -w": 剥离调试信息,显著减小二进制文件体积- 编译产物:
myapp-apiserver
2. 服务器环境准备 (以 Ubuntu/Debian 为例)
bash
# 更新系统并安装必要工具
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx apache2-utils
# 创建专用服务用户 (安全最佳实践,可选)
# sudo groupadd -r myapp
# sudo useradd -r -g myapp -s /sbin/nologin -M myapp3. 标准化文件部署 (遵循 FHS)
根据 FHS 规范,将文件放置到标准目录:
bash
# 1. 可执行文件 -> /usr/local/bin
sudo cp myapp-apiserver /usr/local/bin/
sudo chmod 755 /usr/local/bin/myapp-apiserver
# 如使用专用用户: sudo chown myapp:myapp /usr/local/bin/myapp-apiserver
# 2. 配置文件 -> /etc/myapp/
sudo mkdir -p /etc/myapp
sudo cp config.yaml /etc/myapp/config.yaml
sudo chown -R root:root /etc/myapp
sudo chmod 755 /etc/myapp
sudo chmod 644 /etc/myapp/config.yaml
# 3. 工作目录与数据持久化 -> /var/lib/myapp/
sudo mkdir -p /var/lib/myapp
sudo chown root:root /var/lib/myapp # 如使用专用用户,则 chown myapp:myapp
# 4. 日志目录 -> /var/log/myapp/
sudo mkdir -p /var/log/myapp
sudo touch /var/log/myapp/apiserver.log
sudo chown root:root /var/log/myapp/apiserver.log4. 创建 Systemd 服务文件
创建 /usr/lib/systemd/system/myapp-apiserver.service:
ini
[Unit]
Description="MyApp API Server"
Requires=network-online.target
After=network-online.target
[Service]
WorkingDirectory=/var/lib/myapp
StandardOutput=append:/var/log/myapp/apiserver.log
StandardError=append:/var/log/myapp/apiserver.log
User=root
Group=root
ExecStart=/usr/local/bin/myapp-apiserver --config-dir /etc/myapp --config-name config.yaml
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGTERM
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Alias=myapp.service操作命令:
bash
# 重载 systemd 并启动服务
sudo systemctl daemon-reload
sudo systemctl enable myapp-apiserver.service # 开机自启
sudo systemctl start myapp-apiserver.service
# 使用别名管理(可选)
sudo systemctl enable myapp.service --now
# 查看状态
sudo systemctl status myapp-apiserver.service
# 优雅重载配置(发送 HUP 信号)
sudo systemctl reload myapp-apiserver.service
# 查看日志(直接查看文件,因为配置了 StandardOutput/Error)
sudo tail -f /var/log/myapp/apiserver.log
# 通过 journal 查看(如果同时需要)
sudo journalctl -u myapp-apiserver -f5. Basic Auth 灰度访问控制
5.1 为什么需要 Basic Auth?(灰度发布场景)
在正式上线前,预发布环境(Staging/Preview)通常需要对外网隐藏,仅限团队成员访问。开启 Nginx auth_basic 有以下优势:
- 零代码侵入:无需在 Go 应用中实现登录逻辑,由 Nginx 层统一拦截
- 快速生效:修改
.htpasswd文件即可增删成员,无需重启服务 - 配合灰度策略:可叠加 IP 白名单,实现灵活的访问控制
5.2 生成密码文件
bash
# 创建密码文件 (首次需 -c)
sudo htpasswd -c /etc/nginx/.htpasswd your_username
# 添加更多用户时去掉 -c
sudo htpasswd /etc/nginx/.htpasswd another_user
# 验证密码文件
sudo cat /etc/nginx/.htpasswd5.3 进阶配置:Basic Auth + IP 白名单(推荐)
如果您希望公司内部 IP 免密码访问,外部访问需要密码,可以使用以下配置:
nginx
location / {
# 满足任一条件即可
satisfy any;
# 允许公司出口 IP 免密码访问
allow 203.0.113.0/24; # 公司办公网段
allow 10.0.0.0/8; # 内网 VPN 网段
deny all; # 拒绝其他所有 IP
# 不在白名单的 IP 需要输入密码
auth_basic "Restricted Access - Internal Preview";
auth_basic_user_file /etc/nginx/.htpasswd;
# ... 其他 proxy_pass 配置
}5.4 ⚠️ 重要提醒:Nginx 基础认证与独立 API 域名的冲突问题
如果您的前端项目是 Nuxt.js / Next.js 等 SSR 框架,并且使用独立的 API 子域名(如 api.yourdomain.com),那么 Nginx 基础认证会带来严重的架构问题。
问题场景
假设您的架构如下:
| 子域名 | 用途 | 访问方式 |
|---|---|---|
dash.yourdomain.com | Nuxt 控制台前端 | 用户浏览器访问 |
api.yourdomain.com | Golang API 服务 | Nuxt 服务端调用 + 浏览器直接调用 |
当 Nginx 对 api.yourdomain.com 启用 auth_basic 后:
- Nuxt 服务端调用 API:携带
Authorization: Basic请求头 → 与 API 自身的Bearer Token认证冲突 - Swagger / OpenAPI 文档:完全无法访问(需要浏览器输入密码,但 API 文档页面无法弹出认证框)
- 外部合作伙伴调试:无法提供稳定的 API 测试入口
解决方案对比
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 方案一:Nginx 路径代理 | Nuxt 通过 /api 路径代理到本地 http://127.0.0.1:2759,API 域名不对外暴露 | 简单,无需额外配置 | Swagger 文档完全不可用;API 无法被外部服务调用 |
| 方案二:Cloudflare Access(推荐) | 使用 Cloudflare Access 零信任访问控制,替代 Nginx 基础认证 | 不占用 Authorization 头;支持 Swagger 文档访问;支持服务间调用(Service Token);团队邮箱域授权;上线一键关闭 | 需要域名托管在 Cloudflare(免费计划足够) |
方案一:Nginx 路径代理(不推荐)
nginx
# dash.yourdomain.com 配置中增加路径代理
location /api/ {
proxy_pass http://127.0.0.1:2759/; # 代理到本地 API
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 注意:这里没有 auth_basic
}问题:
https://dash.yourdomain.com/api/swagger/index.html无法访问- API 无法被独立的 API 客户端(如 Postman、外部服务)调用
方案二:Cloudflare Access(强烈推荐)
使用 Cloudflare Access 替代 Nginx 基础认证,彻底解决冲突问题。
配置要点:
| 子域名 | Access 策略 | Action | 规则 |
|---|---|---|---|
dash.yourdomain.com | 团队访问 | Allow | 邮箱以 @company.com 结尾 |
api.yourdomain.com | 服务调用 | Service Auth | Service Token |
api.yourdomain.com | 团队访问 | Allow | 邮箱以 @company.com 结尾 |
核心优势:
- 不占用
Authorization头:API 的Bearer Token认证完全不受影响 - Swagger 文档可访问:团队成员登录后正常查看 API 文档
- 支持服务间调用:通过 Service Token 实现无感调用
- 上线一键关闭:无需修改任何代码或 Nginx 配置
📖 完整配置指南:参考《基于 Cloudflare Access 的零信任访问控制:从灰度发布到正式上线的完美方案》一文,包含 Zero Trust Free 计划激活、Service Token 配置、策略设置等详细步骤。
总结建议
| 您的需求 | 推荐方案 |
|---|---|
仅保护控制台(dash.),API 对外完全公开 | Nginx 基础认证 |
保护 API(api.)但需要 Swagger 文档可访问 | Cloudflare Access |
| 保护 API 且需要服务间无感调用 | Cloudflare Access |
| 追求上线时零配置切换 | Cloudflare Access |
如果您正在使用 Nuxt + 独立 API 域名的架构,强烈建议直接采用 Cloudflare Access 方案,避免后续重构成本。
6. 配置 Nginx 反向代理 + 基础认证
6.0 SSL 证书方案选择
在配置 Nginx HTTPS 之前,您需要选择一种 SSL 证书方案。以下是两种主流免费方案的对比:
| 特性 | Cloudflare Origin CA | Let's Encrypt |
|---|---|---|
| 适用场景 | 域名托管在 Cloudflare | 任意域名(通用) |
| 配置复杂度 | 简单 | 中等 |
| 证书续期 | 15 年有效期,无需续期 | 90 天,自动续期 |
| CDN 加速 | ✅ 需开启 Cloudflare 代理 | ❌ |
| DDoS 防护 | ✅ | ❌ |
| 独立部署 | ❌ 依赖 Cloudflare | ✅ 完全独立 |
选择建议:
- 如果您已在 Cloudflare 托管域名,推荐使用 Cloudflare Origin CA(下文 6.1-6.3 方案)
- 如果您的域名不在 Cloudflare,或希望独立部署,推荐使用 Let's Encrypt(参考 《从零开始部署 Nuxt 应用到 Ubuntu 22.04 服务器》 中的 SSL 配置章节)
以下以 Cloudflare Origin CA 为例进行配置,若使用 Let's Encrypt 可参考上方链接博文。
6.1 准备 Cloudflare Origin CA 证书
从 Cloudflare Dashboard → SSL/TLS → Origin Server 下载证书:
注意
Cloudflare Origin CA 证书是按域区(Zone)签发的,一张证书可以覆盖主域名及所有子域名(如 *.yourdomain.com)。因此证书文件名应使用主域名。
bash
# 放置证书(使用主域名命名)
sudo mkdir -p /etc/nginx/ssl
sudo cp yourdomain.com.pem /etc/nginx/ssl/
sudo cp yourdomain.com.key /etc/nginx/ssl/
sudo chmod 600 /etc/nginx/ssl/yourdomain.com.key6.2 创建 Nginx 站点配置
创建 /etc/nginx/sites-available/api.yourdomain.com.conf:
nginx
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name api.yourdomain.com;
return 301 https://$server_name$request_uri;
}
# HTTPS 配置
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
# 安全响应头
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 全局基础认证 (灰度发布控制)
auth_basic "Restricted Access - Internal Preview";
auth_basic_user_file /etc/nginx/.htpasswd;
# Cloudflare Origin CA 证书(使用主域名证书)
ssl_certificate /etc/nginx/ssl/yourdomain.com.pem;
ssl_certificate_key /etc/nginx/ssl/yourdomain.com.key;
# SSL 优化配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
# 日志
access_log /var/log/nginx/api.yourdomain.com-access.log;
error_log /var/log/nginx/api.yourdomain.com-error.log;
# Cloudflare IP 地址范围(获取真实客户端 IP)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
# 主端点代理
location / {
proxy_pass http://127.0.0.1:2759; # 假设 Go 服务监听此端口
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
proxy_pass http://127.0.0.1:2759;
expires 1y;
add_header Cache-Control "public, immutable";
}
# 健康检查免认证(供负载均衡器探测)
location = /health {
auth_basic off;
proxy_pass http://127.0.0.1:2759;
proxy_set_header Host $host;
}
}6.3 启用站点并重载 Nginx
bash
# 启用站点配置
sudo ln -s /etc/nginx/sites-available/api.yourdomain.com.conf /etc/nginx/sites-enabled/
# 检查语法并重载
sudo nginx -t && sudo systemctl reload nginx7. Cloudflare DNS 与 SSL 模式配置
7.1 添加 DNS A 记录
| 类型 | 名称 | 内容 | 代理状态 |
|---|---|---|---|
| A | api | 您的服务器公网 IP | ✅ 开启(橙色云朵) |
7.2 SSL/TLS 设置
在 Cloudflare SSL/TLS 面板中,将模式设置为 Full (strict)
这要求源站具有有效证书,我们已配置 Origin CA 证书,满足此要求
8. 部署后验证
bash
# 1. 检查 Go 服务状态
sudo systemctl status myapp-apiserver
# 2. 检查 Nginx 代理连通性 (本地)
curl -H "Host: api.yourdomain.com" http://127.0.0.1/health
# 3. 通过 Cloudflare 外网测试 (应要求认证)
curl -I https://api.yourdomain.com
# 4. 带认证访问
curl -I -u your_username:your_password https://api.yourdomain.com
# 5. 验证 HSTS 和安全头
curl -I https://api.yourdomain.com | grep -i "strict-transport-security"
# 6. 验证健康检查免认证
curl -I https://api.yourdomain.com/health9. 常见故障排查
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 访问显示默认 Nginx 页面 | sites-enabled 中有其他 default_server 配置冲突 | 确保删除或移除非必要的 default 配置链接 |
| 502 Bad Gateway | Go 服务未启动或端口不匹配 | sudo systemctl status myapp-apiserver;检查 Nginx proxy_pass 端口 |
| 无认证提示 (直接 200) | auth_basic 未正确继承或被覆盖 | 检查 location 块中是否有 auth_basic off; 或语法错误 |
| 日志中显示客户端 IP 为 Cloudflare 节点 | real_ip_header 配置缺失或顺序错误 | 确认 set_real_ip_from 包含 Cloudflare 所有范围,且 real_ip_header CF-Connecting-IP; 正确 |
证书报错 SSL_ERROR_BAD_CERT_DOMAIN | 证书域名与实际访问域名不匹配 | 确认证书是主域名签发(如 yourdomain.com),可覆盖 *.yourdomain.com |
10. 快速维护命令速查
bash
# 重新编译并替换二进制文件
go build -o myapp-apiserver .
sudo systemctl stop myapp-apiserver
sudo cp myapp-apiserver /usr/local/bin/
sudo systemctl start myapp-apiserver
# 修改配置文件后重载(发送 HUP 信号)
sudo vi /etc/myapp/config.yaml
sudo systemctl reload myapp-apiserver
# 查看实时日志
sudo tail -f /var/log/myapp/apiserver.log
sudo tail -f /var/log/nginx/api.yourdomain.com-access.log
# 更新 Nginx 配置后重载
sudo nginx -t && sudo systemctl reload nginx
# 管理 Basic Auth 用户
sudo htpasswd /etc/nginx/.htpasswd new_user # 添加用户
sudo htpasswd -D /etc/nginx/.htpasswd old_user # 删除用户11. 遵循 FHS 原则的总结
| 文件类型 | 存放路径 | 说明 |
|---|---|---|
| 可执行文件 | /usr/local/bin/myapp-apiserver | 本地编译的程序,不属于发行版仓库 |
| 配置文件 | /etc/myapp/config.yaml | 全局配置目录与文件 |
| 日志文件 | /var/log/myapp/apiserver.log | 应用日志(追加模式) |
| 工作/持久化目录 | /var/lib/myapp/ | 应用状态数据、工作目录 |
| 系统服务 | /usr/lib/systemd/system/myapp-apiserver.service | 系统单元 |
| Web 代理配置 | /etc/nginx/sites-available/ 与 /etc/nginx/sites-enabled/ | Nginx 站点配置 |
快速记忆口诀
二进制找
bin,配置找etc,日志找
var,静态找share,自己编译
local,大厂软件放opt。
这套布局确保了系统的整洁性、可维护性,并与其他 Linux 软件包的行为保持一致。
附录:占位符替换清单
部署时请将以下占位符替换为实际值:
| 占位符 | 示例值 | 说明 |
|---|---|---|
myapp | yourapp | Go 项目名称(目录/服务名前缀) |
myapp-apiserver | yourapp-apiserver | 可执行文件名 |
api.yourdomain.com | api.example.com | API 服务完整域名 |
yourdomain.com | example.com | Cloudflare 证书主域名 |
2759 | 8080 | Go 服务监听端口 |
