结论:用 post-receive 钩子 + bare 仓库是最轻量、最可控也最容易出问题的“一键部署”方案;post-receive 只在 bare 仓库中生效,因裸仓库无工作区,必须通过 git_work_tree 或 --work-tree 指定路径并配合 checkout -f 实现自动更新,且脚本需用绝对路径、显式解释器、错误终止和权限修正。

直接说结论:用 post-receive 钩子 + bare 仓库,是最轻量、最可控、也最容易出问题的“一键部署”方案;别用 post-update,它不适用于远程推送场景。
为什么必须用 bare 仓库?git init --bare 不是可选项
裸仓库没有工作目录,只存 Git 对象和引用,专为远程协作设计。如果你在服务器上用 git init(没加 --bare),.git/hooks/post-receive 根本不会被 Git 调用——因为 post-receive 只在接收推送的 bare 仓库中生效。
- 错误现象:
git push成功但服务器代码没更新,post-receive完全静默 - 检查方法:
ls -la /path/to/repo/.git—— 如果能看到HEAD、index、objects/等,但没有worktrees/或项目文件,才是 bare 仓库 - 补救操作(已有非 bare 仓库):
git clone --bare /old/repo /new/bare.git && rm -rf /old/repo,再重配钩子
post-receive 脚本里必须写绝对路径和显式解释器
SSH 登录用户环境变量不可靠,git 命令可能找不到,cd .. 可能进错目录,脚本直接退出无报错。
- 正确开头必须是:
#!/bin/bash(不是#!/usr/bin/env bash) - 所有命令用绝对路径:
/usr/bin/git、/bin/chown、/usr/bin/rsync - 切换目录前先确认:
cd /var/www/myapp || exit 1,别信cd .. - 常见坑:
git checkout -f失败却没提示——加set -e让脚本遇到错误立刻终止
权限问题比逻辑错误更常导致部署失败
Git 钩子以 SSH 登录用户身份运行,但 Web 服务(如 Nginx/Apache)通常以 www 或 nginx 用户读取文件。如果钩子没改所有权,网站会 500 或 403。
- 典型错误:
remote: fatal: unable to mkdir 'xxx': Permission denied - 关键操作(以宝塔为例):
/bin/chown -R www:www /www/wwwroot/my-site - 不要用
sudo:SSH 用户没配免密 sudo 权限时会卡住;也不要在钩子里切到 root - 安全底线:部署用户(如
deployuser)不能是 root,且其 home 目录不应有写权限给其他用户
Webhook 替代方案更适合 GitHub/Gitee 私有库
如果你代码托管在 Gitee 或自建 GitLab,post-receive 需要开放 SSH 端口并管理公钥,运维成本高;Webhook + 简单 PHP/Python 接口更易审计、更易限流。
- 必须加校验:
$_GET['token']或X-Hub-Signature-256头,否则任何人都能触发部署 - Nginx 要调大限制:
client_max_body_size 10M、fastcgi_read_timeout 300 - 别把 Webhook 脚本放在 Web 可访问路径下,用
include或 CLI 方式调用实际部署逻辑 - Plan B 更稳:用宝塔计划任务每 3 分钟执行
cd /path && /usr/bin/git pull origin main --ff-only,失败日志一目了然
真正麻烦的从来不是写脚本,而是每次 git push 后得盯着日志看哪一行没输出——因为少了一个 2>&1 重定向,或者 chown 漏掉了子目录。











