必须用systemd托管编译后的gin二进制,禁用nohup和screen;需配置workingdirectory、environment、restart=always及journal日志,并确保端口、权限、静态资源与环境变量对齐。

直接用 systemd 管理 Gin 二进制,别碰 nohup 和 screen
用 nohup go run main.go & 启动 Gin 是临时调试手段,不是部署。它不自动拉起、不记录标准错误、不隔离环境变量,崩溃后服务就消失了。真正上线必须用 systemd —— Linux 发行版(CentOS 7+、Ubuntu 16.04+)默认的服务管理器。
关键前提是:先编译出可执行文件,而不是依赖 go run。
- 在项目根目录执行
go build -o myapp .,生成myapp二进制(确保GOOS=linux GOARCH=amd64交叉编译,如果本地是 macOS/Windows) - 把
myapp上传到服务器目标路径,比如/opt/mygin/app - 确认该二进制有执行权限:
chmod +x /opt/mygin/app/myapp - 如果项目读配置文件或写日志,确保运行用户(如
www-data或自建ginuser)对相关目录有读写权限
systemd 服务文件里必须设好 WorkingDirectory 和 Environment
Gin 程序常依赖当前工作目录下的 config.yaml、静态资源或模板文件。systemd 默认工作目录是 /,不显式指定就会报 “file not found” 错误。
同时,Gin 的 GIN_MODE、数据库地址等环境变量不能靠 shell profile 生效,必须在 service 文件中声明。
[Unit] Description=My Gin API Service After=network.target [Service] Type=simple User=ginuser Group=ginuser WorkingDirectory=/opt/mygin/app Environment="GIN_MODE=release" Environment="CONFIG_PATH=/opt/mygin/conf/config.yaml" ExecStart=/opt/mygin/app/myapp Restart=always RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
-
Type=simple:适用于前台阻塞式进程(Gin 默认r.Run()就是),别用forking—— 那是为传统 daemon 设计的 -
Restart=always:程序崩溃、OOM kill、panic 都会自动重启;RestartSec=5防止频繁闪退打爆日志 -
StandardOutput/StandardError=journal:日志统一进journald,查日志用journalctl -u mygin.service -f
端口冲突和防火墙是 Gin 启动失败最常见原因
本地能跑,一上服务器就连不上?90% 是端口没放开或被占用了。Gin 默认监听 :8080,但生产环境通常要监听 :80 或 :443 —— 这俩是特权端口,普通用户无法绑定。
- 方案一(推荐):保持
myapp监听非特权端口(如:8080),用 Nginx 反向代理到它,Nginx 以 root 启动再降权,安全且灵活 - 方案二:给二进制加
cap_net_bind_service能力:sudo setcap 'cap_net_bind_service=+ep' /opt/mygin/app/myapp,之后普通用户就能 bind:80 - 检查端口占用:
sudo ss -tulpn | grep ':80';检查防火墙:sudo firewall-cmd --list-ports(CentOS)或sudo ufw status(Ubuntu)
别漏掉 go mod tidy 和静态资源打包
部署前没运行 go mod tidy,会导致 systemd 启动时找不到依赖,报错类似 cannot find module providing package github.com/gin-gonic/gin —— 因为 go build 不会自动下载缺失模块。
另外,如果 Gin 用了 r.StaticFS 或 r.LoadHTMLFiles 加载前端资源,这些文件必须随二进制一起上传到服务器对应路径,go build 不会自动打包它们。
- 建议把静态资源(
templates/、public/)放在项目根目录下,构建后同步到服务器的/opt/mygin/app/下同级位置 - 代码里用相对路径加载:
r.LoadHTMLGlob("templates/*"),而非绝对路径 - 若想彻底嵌入资源,可用
embed(Go 1.16+)://go:embed templates/*+fs.ReadFile,避免部署遗漏
真正麻烦的永远不是启动命令本身,而是环境变量、路径、权限、端口、资源这五样东西没对齐——哪怕只错一个,systemctl start 看似成功,journalctl 里却全是 silent failure。











