去年在客户现场遇到一次相当尴尬的故障:项目交付前最后一天,需要把代码推到客户的 GitLab 仓库做归档,结果 git push 卡了几分钟直接超时,换了几台电脑都一样。当时第一反应是网络问题,排查了半天才发现,仓库的远程地址是 git@gitlab.xxx.com:team/project.git 这种 SSH 形式,而客户办公网对所有出站的 22 端口有限制,Git 的 SSH 连接根本出不去。后来我执行了一条 git remote set-url origin https://gitlab.xxx.com/team/project.git,再配上个人访问令牌(PAT)做认证,一分钟内就推上去了。
这个场景其实就是今天想聊的话题:Git 远程地址在 SSH 和 HTTPS 之间切换,尤其是切到 HTTPS 之后怎么用 PAT 完成认证。它不像 git add、git commit 那样每天都会用到,但只要你换过网络环境、接过新单位的仓库、或者帮别人处理过 git push 失败,几乎都会撞上这个问题。这篇文章我尽量把原理讲清楚,再把操作步骤和踩过的坑一次说完。
1. SSH 和 HTTPS 两种远程协议,到底差在哪
1.1 本质区别:传输通道和认证方式
很多同学对 SSH 和 HTTPS 的认知停留在"一个要配密钥,一个要输密码",其实这只是一个表象。两者的核心差异在于:
SSH 走的是 22 端口,使用公钥加密机制完成身份认证。客户端本地保存私钥,服务器端保存公钥,连接时通过签名和质询完成双向验证。因为整个过程没有密码在网络上传输,所以安全级别天然更高,适合长期、频繁的代码操作。
HTTPS 走的是 443 端口,本质上是 HTTP 协议加了一层 TLS 加密。Git 用它做远程操作时,认证靠的是"用户名 + 密码(或令牌)"。TLS 保证了传输过程加密,所以密码不会明文暴露在网络链路上。
这里有个容易被忽视的点:很多办公网络、云厂商的安全组策略默认会放行 443 端口,但 22 端口常常被限制,因为 22 端口是黑客扫描的重灾区。所以你会发现,在公司能正常访问网页(HTTPS),但 git clone git@... 就是连不上,原因多半就是 22 端口不通。
打个比方:SSH 像持门禁卡走员工通道,HTTPS 像走访客通道。员工通道安静高效,但是门禁系统只对特定人群开放;访客通道虽然每次要登记(输密码/token),但只要你拿到访客凭据,任何时间都能进来。
1.2 什么时候需要切换
切换不是没事找事,通常遇以下几种情况,你就该考虑动了:
| 场景 | 推荐协议 | 原因 |
|---|---|---|
| 家庭网络/服务器部署 | SSH | 脚本化和自动化方便,无需重复认证 |
| 办公网/客户现场等受限网络 | HTTPS | 443 端口基本畅通,不容易被网络策略卡脖子 |
| 多平台多账号管理 | HTTPS + PAT | 避免维护多套 SSH 密钥对,token 粒度更细 |
| CI/CD 自动化 | HTTPS + Token/Secret | 流水线中明文密码不友好,token 可单独授权、可撤销 |
| 二步验证开启后的本地 pc | HTTPS + PAT | 普通密码无法用于 Git 操作,必须用令牌 |
我自己用得最多的场景,就是"开发机在公司内网,需要访问 GitHub 拉公共依赖"。公司网络不限制 GitHub 的 HTTPS 访问,但如果用 SSH,则经常超时。这时候临时把 origin 改成 HTTPS 地址,用完再切回来,比反复调整网络配置省事太多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手切换前,先摸清当前远程地址配置
2.1 查看当前远程仓库地址
切换之前,先确认自己仓库现在的远程地址是什么状态。命令很简单:
bash复制git remote -v
输出通常长这样:
code复制origin git@github.com:user/repo.git (fetch)
origin git@github.com:user/repo.git (push)
或者:
code复制origin https://github.com/user/repo.git (fetch)
origin https://github.com/user/repo.git (push)
看到 git@ 开头,就是 SSH 协议;看到 https:// 开头,就是 HTTPS 协议。有时还会看到 http://(不带 s),那就是更老的配置,这种地址传输不加密,token 和密码等同于裸奔,强烈建议顺手改成 HTTPS。
如果仓库有多个远程源(比如一个 origin 一个 upstream),可以追加参数查看特定远程源:
bash复制git remote get-url origin
这条命令在脚本里特别有用,你可以基于它的返回值做条件判断。
2.2 判断该往哪个方向切
摸清现状后,你要判断的是"当前环境适合哪种协议"。
如果你只是觉得 SSH 配起来麻烦,想省心用 HTTPS,那方向明确:改成 HTTPS 并生成 PAT。如果你已经在用 HTTPS,但觉得每次 push 都要输入密码很烦,想改 SSH 实现免密推送,那方向是生成密钥并切换 SSH。
还有一点需要提前确认:你在这台电脑上是否已经配置过 Git 的凭证存储方式。
bash复制git config --global credential.helper
输出可能是 manager(Windows 上的 Git Credential Manager)、osxkeychain(macOS 的钥匙串)、store,或者什么都不显示。这个配置决定了 HTTPS 方式下,Git 把密码/token 存在哪里、以什么形式存。切换前了解它,有助于预判接下来会不会遇到"反复要求输入密码"的问题。
3. 切换到 SSH:生成密钥、添加公钥、改地址
如果你决定从 HTTPS 切到 SSH,核心要做的事情是三件:生成本地密钥对、把公钥托管到代码平台、修改本地 remote 地址。每一步都有坑,我按顺序说。
3.1 生成 SSH 密钥对
优先推荐 Ed25519 算法,密钥短、安全性高、生成快,主流的 GitHub、GitLab 都支持:
bash复制ssh-keygen -t ed25519 -C "your_email@example.com"
执行后一路回车,会在 ~/.ssh/ 下生成两个文件:id_ed25519(私钥)和 id_ed25519.pub(公钥)。-C 参数只是加个备注,你邮箱、手机号、或者"公司的ThinkPad"都可以,便于以后在平台后台辨认密钥。
如果代码平台比较老,只支持 RSA 算法,那用:
bash复制ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
这里有个小建议:生成密钥时提示 Enter passphrase,不要偷懒直接回车。设一个 passphrase 后,即使私钥文件泄露,攻击者也拿不到密钥内容。担心每次连接都输入 passphrase 麻烦的,可以用 ssh-agent 帮你暂时管理,日常开发完全不会被感知到。
3.2 把公钥添加到代码托管平台
先查看公钥内容:
bash复制cat ~/.ssh/id_ed25519.pub
然后去对应的代码托管平台添加:
- GitHub:右上角头像 → Settings → SSH and GPG keys → New SSH key
- GitLab:个人设置 → SSH Keys,把公钥粘贴进去
- Gitee、云效、Codeup 等平台的路径也类似,都是"个人设置"里的"SSH 公钥"
添加完成后不要直接 git push,先验证连接:
bash复制ssh -T git@github.com
如果看到 Hi username! You've successfully authenticated,说明公钥配置成功。如果提示 Permission denied (publickey),大概率是公钥没粘贴完整、或粘贴到了错误平台。
3.3 修改远程地址
确认 SSH 连接没问题之后,改远程地址:
bash复制git remote set-url origin git@github.com:user/repo.git
改完再执行 git remote -v 确认,然后随便 push 或 fetch 测试。
这里要说一个最常见的坑:如果你的电脑上同时管理多个代码平台的仓库(比如公司 GitLab 和自己 GitHub),并且两个平台用了两对不同密钥,就必须在 ~/.ssh/config 里指定每个 host 用哪把密钥。否则 Git 会默认去找 id_ed25519,找不到就直接拒绝连接。
示例 ~/.ssh/config:
code复制Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
Host gitlab.office.com
HostName gitlab.office.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
这种配置文件改完立即生效,不用重启。但要注意文件权限,~/.ssh 目录和 config 文件不能让其他用户可写,否则 SSH 会报 Bad owner or permissions 并拒绝使用。
4. 切换到 HTTPS 并配合 PAT:最通用的方案
4.1 PAT 为什么能替代密码
先理清一个概念:PAT(Personal Access Token,个人访问令牌)是代码托管平台签发的一串随机字符串,专门用于在命令行、脚本或第三方工具中代替密码完成 Git 操作。
为什么不用密码?两个原因:
第一,现在主流平台基本都强制开启二步验证(2FA),开启之后,你的登录密码就不能直接用于 Git 操作了,平台的 API 和 Git 协议层要求使用 token。
第二,密码的权限范围是"全部"。你用一个密码访问仓库,它同时能改账号设置、删仓库、管理密钥,scope 太大,一旦泄露损失不可控。PAT 可以精确控制权限范围(比如只给读取代码的权限),并且能单独撤销,不影响登录密码。
我用一个类比:密码是你的银行卡密码,PAT 是一张预充值的礼品卡。你可以拿礼品卡在指定商户消费,限额用完了就作废,或者你发现卡丢了,给客服打个电话就可以立刻注销,完全不影响你银行卡里的钱。
4.2 生成 PAT 的标准步骤
不同平台路径略有差异,以 GitHub 为例:
- 登录 GitHub,右上角头像 → Settings
- 左侧菜单找到 Developer settings
- 选择 Personal access tokens → Tokens (classic)
- 点 Generate new token
- 填写 Note(用途说明,比如"home-pc-https"),设置过期时间
- 勾选权限范围(scopes),普通仓库操作勾选
repo即可,如果涉及 GitHub Actions 流水线,再勾workflow - 点生成按钮,把 token 复制下来
注意:token 只会在生成时完整显示一次,关掉页面就再也看不到了。没保存的话只能删掉重新生成。GitLab 类似:Settings → Access Tokens,勾选 read_repository 和 write_repository,生成后同样只显示一次。
对于需要长期自动化运行的机器,我建议 token 的过期时间不要选"永久",选 90 天或一年,然后配合定时提醒。因为 max 期限越短,即使泄露影响也越小,到期后还可以养成定期轮换的好习惯。
4.3 修改远程地址并完成首次认证
切换到 HTTPS 远程地址:
bash复制git remote set-url origin https://github.com/user/repo.git
然后执行任意一个远程操作,比如 git fetch。Git 会提示输入用户名和密码。此时:
- 用户名:你的 GitHub 或 GitLab 用户名
- 密码:不是登录密码,是刚才生成的 PAT
有同学可能在提示密码时直接粘贴了 PAT 但没反应,这里要提醒:终端里粘贴密码时不显示任何字符(连星号都没有),这是正常现象,粘贴后直接回车即可。
如果被反复要求输入密码,可以先手动触发一次凭据存储:
bash复制git config --global credential.helper
在 Windows 上通常是 manager,macOS 上是 osxkeychain,Linux 上可以设置成 store 或 cache。用 store 时,token 以明文形式存在用户目录的 .git-credentials 文件里,仅限个人电脑;如果是多人共享的测试机,建议用 cache 并设置超时时间:
bash复制git config --global credential.helper 'cache --timeout=3600'
4.4 在 CI/CD 和脚本中使用 PAT
在本地敲命令可以手动输 token,但在 CI/CD 流水线里,token 不能出现在 remote URL 里。例如:
code复制https://username:token@github.com/user/repo.git
这种写法虽然能跑通,但 URL 会出现在日志、进程列表、或者仓库配置里,安全性很差。正确做法是把 PAT 配置为 CI 平台的 Secret 变量(比如 GitHub Actions 的 Secrets,GitLab 的 CI/CD Variables),然后在流水线里用变量引用:
yaml复制- name: Push to repository
run: git push https://x-access-token:${{ secrets.MY_TOKEN }}@github.com/user/repo.git HEAD:main
这里 token 只存在于流水线运行环境中,不会出现在源码和日志里。如果你做的是个人自动备份脚本,同样建议从环境变量读取 PAT,而不是写死在脚本里。
5. 切换踩坑实录:从端口不通到凭证串号
5.1 端口 22 连不上,切 HTTPS 后 still 不行
开头说的客户现场就是这个情况。SSH 方式 push 超时,切到 HTTPS 改用了 443 端口,按理说应该没问题,但第一次 push 仍然失败。排查发现,仓库地址虽然改成了 HTTPS,但本机 Git 的全局配置里还残留着一个 insteadOf 规则:
bash复制git config --global --get-regexp "url.*insteadof"
输出里有一条:
code复制url.git@github.com:.insteadof=https://github.com/
这是之前为图方便设置的重写规则,Git 执行时会把 HTTPS 地址自动替换回 SSH,一切努力白费。解决办法是删掉对应配置:
bash复制git config --global --unset url."git@github.com:".insteadof
所以切换协议后仍然失败时,不要只盯着 remote url,也要查一下 insteadOf 这种"隐藏规则"。
5.2 切到 HTTPS 后一直被要求输入密码
有次处理一台 Windows 机器,git remote -v 已经显示 HTTPS,但每次 fetch 都要弹密码框,填了又说认证失败。当时查凭证管理器,发现里面存的是旧密码,代码平台已经改成 PAT 认证,旧密码自然无效。
解决方式有两个:
一是在 Windows 凭据管理器里找到对应的 git:https://github.com 条目,删掉后重新 fetch,再输入用户名 + PAT。
二是用命令强制清空凭证:
bash复制git credential reject
然后按提示输入协议和主机名,也可以用:
bash复制printf "protocol=https\nhost=github.com\n\n" | git credential reject
清除之后再 fetch,Git 会重新询问认证信息。这个坑提醒我:切换协议不只是改 URL,还要清理旧协议下缓存的认证信息,否则会出现"URL 是新的,认证走旧的"这种混乱状态。
5.3 同平台多个账号,认证信息串号
一位朋友的公司把代码托管在同一个 GitLab 实例上,他在上面有两个账号:个人号和工作号。之前用 SSH 时靠不同密钥区分,后来切到 HTTPS,发现每次提交都显示成同一个人的名字。
问题的根源在于 Git 的凭证存储是按主机名区分的,同主机下只保留一份凭证,后输入的 PAT 会覆盖之前的。解决思路有两种:
方案一:既然两台账号在同一平台,最省心的是只保留一个账号,避免凭证冲突。但现实中不一定允许。
方案二:用 SSH 密钥对更合适,通过 ~/.ssh/config 把两个账号的 host 区分开,比如 gitlab-personal 和 gitlab-work,然后 remote url 里用不同的虚拟域名。
如果你的场景涉及多个平台但各只有一个账号,那不会冲突,因为 GitHub 和 GitLab 是不同的主机名,凭证会分开存储。
5.4 切换后分支跟踪关系丢失
还有一种没那么显眼的问题:改了 remote url 之后,本地分支的 upstream 信息有时候会显示不出来,git status 提示 "Your branch is up to date with 'origin/main'",但实际执行 git push 时提示找不到远程分支。
这种情况多数是因为切换协议后,本地还残留着旧 remote 的远程跟踪引用。处理方式:
bash复制git remote prune origin
git fetch origin
git branch -vv
如果分支的 upstream 确实没了,重建即可:
bash复制git branch --set-upstream-to=origin/main main
现象比较隐蔽,但只要你养成修改 remote 后立刻 git fetch 的习惯,能提前发现问题,不至于在 push 时才措手不及。
6. 让切换变得更顺手的几个习惯
6.1 把协议选择沉淀为工具脚本
如果你经常在不同网络环境下切换协议,与其每次手敲 git remote set-url,不如写个小脚本。比如我习惯在仓库根目录放一个 set-protocol.sh,根据参数自动切换,顺带打印切换前后的地址:
bash复制#!/bin/bash
set -e
if [ "$1" == "ssh" ]; then
git remote set-url origin "git@github.com:$(git remote get-url origin | sed -E 's#https?://[^/]+/##')"
elif [ "$1" == "https" ]; then
git remote set-url origin "https://github.com/$(git remote get-url origin | sed -E 's#git@[^:]+:##')"
else
echo "Usage: $0 [ssh|https]"
exit 1
fi
git remote -v
这个脚本只适用于单平台单仓库场景,多平台或多个 remote 时慎用,但它能防止你在连续输入命令时把仓库名记错。
6.2 PAT 的生命周期管理
PAT 是 HTTPS 方案的命门。给它绑定过期时间后,到期会突然之间 push 不了,而且报错信息是"Authentication failed",非常容易让人误以为是网络或密码变了。
我的习惯是:
- 生成后立刻存进密码管理器,分类标注用途
- 设置 90 天过期,日历里加提醒
- 每次到期前主动重新生成,顺便检查 scope 有没有多余权限
- 如果发现某个 PAT 用得少了,宁可删除后再生成,也不要让它在后台长期存活
6.3 远程地址不要在一个仓库里混用
有些同学 clone 的时候用了 HTTPS,后来为了方便又改成 SSH,推了两天又切回来。这种"反复横跳"容易让远程跟踪分支和凭证状态变得很脏。
除非确有需要(比如办公网络禁止 SSH、回家后又想免密),否则选定一种协议就长期用下去。团队协作时,也要在项目 README 里写清楚推荐用哪种协议,避免一半人用 SSH 一半人用 HTTPS,遇到问题互相都说不清。
我在实际使用中的体会是:SSH 和 HTTPS 没有绝对的好用或不好用,只有适不适合当前环境。SSH 胜在认证体验顺滑,适合长期开发机;HTTPS + PAT 胜在通用性和权限可控,适合受限网络、CI/CD 和临时环境。把两者背后的原理和切换步骤吃透,在任何网络环境里都能从容应对 Git 的远程操作。下次再遇到 push 超时,先别急着怀疑网络,看一眼 git remote -v 输出的地址协议,也许改一行命令就解决了。
