Git HTTPS 连接失败?从 GnuTLS 到 OpenSSL 的排查与解决
问题
在 Ubuntu 24.04 上通过 apt install git 安装 Git 后,执行 git fetch 或 git clone 访问 HTTPS 仓库时出现:
fatal: unable to access 'https://github.com/...':
gnutls_handshake() failed: The TLS connection was non-properly terminated.
尤其是在使用代理、企业内网或某些特定网络环境下,这个问题复现率很高。
根因
Ubuntu 默认 Git 使用 GnuTLS
Ubuntu 的 Git 包编译时默认链接的是 GnuTLS 而不是 OpenSSL:
$ git -c http.sslbackend=OpenSSL ls-remote https://github.com/git/git.git HEAD
fatal: Unsupported SSL backend 'OpenSSL'. Supported SSL backends:
gnutls
GnuTLS 在某些网络环境下的 TLS 握手兼容性不如 OpenSSL,导致连接失败。
为什么 Ubuntu 要用 GnuTLS?
- GnuTLS 使用 LGPL 许可证,而 OpenSSL 使用 OpenSSL License(与 GPL 不兼容)
- Ubuntu 为了许可证合规性,默认选择 GnuTLS
- 这不是 bug,而是发行版策略
验证方法
1. 查看 Git 支持的 SSL 后端
git -c http.sslbackend=help ls-remote
如果只输出 gnutls,说明你的 Git 不支持 OpenSSL。
2. 查看实际链接的 SSL 库
# 编译安装的 OpenSSL 版 Git
$ ldd $(which git) | grep -iE "ssl|gnutls"
libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 # 只有 OpenSSL
# apt 安装的 GnuTLS 版 Git
$ ldd $(which git) | grep -iE "ssl|gnutls"
libgnutls.so.30 => /lib/x86_64-linux-gnu/libgnutls.so.30 # 只有 GnuTLS
3. 跟踪 Git 实际加载的 SSL 库
用 strace 跟踪 Git 进程实际打开了哪些 SSL 相关的共享库:
# 跟踪 openat 系统调用,筛选 SSL 相关库
$ strace -f -e trace=openat git ls-remote https://github.com/git/git.git HEAD 2>&1 | grep -E "libssl|libgnutls|libcurl"
# Ubuntu (apt install git) — 加载的是 GnuTLS
[pid 123] openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgnutls.so.30", O_RDONLY|O_CLOEXEC) = 3
# 编译安装的 OpenSSL 版本 — 加载的是 OpenSSL
[pid 456] openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libssl.so.3", O_RDONLY|O_CLOEXEC) = 3
[pid 456] openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libcrypto.so.3", O_RDONLY|O_CLOEXEC) = 3
4. 代理环境下的 HTTP 流量跟踪
GIT_TRACE_CURL=1 git ls-remote https://github.com/git/git.git HEAD 2>&1
GnuTLS 版本会在代理场景下输出 gnutls_handshake() failed 错误。
解决方案
方案一:源码编译 Git + OpenSSL(推荐)
最彻底的方案,编译时显式指定 OpenSSL:
安装编译依赖
apt-get install -y \
autoconf gcc make gettext \
libexpat1-dev libcurl4-openssl-dev \
libssl-dev zlib1g-dev
下载并编译
GIT_VERSION=2.53.0
curl -fsSL https://mirrors.edge.kernel.org/pub/software/scm/git/git-${GIT_VERSION}.tar.xz \
| tar xvJ
cd git-*
make configure
./configure --prefix=/usr --with-openssl --with-curl
make && make install
验证
$ git -c http.sslbackend=OpenSSL ls-remote https://github.com/git/git.git HEAD
56a4f3c3a221adf1df9b39da69b8a6890f803157 HEAD
方案二:多阶段 Docker 构建
适合容器化部署场景:
# 第一阶段:编译
FROM ubuntu:24.04 AS builder
RUN apt-get update && apt-get install -y \
autoconf gcc make gettext \
libexpat1-dev libcurl4-openssl-dev \
libssl-dev zlib1g-dev
ARG GIT_VERSION=2.53.0
RUN curl -fsSL https://mirrors.edge.kernel.org/pub/software/scm/git/git-${GIT_VERSION}.tar.xz \
| tar xvJ && \
cd git-* && \
make configure && \
./configure --prefix=/usr --with-openssl --with-curl && \
make && make install
# 第二阶段:运行
FROM ubuntu:24.04
COPY --from=builder /usr/bin/git /usr/bin/git
COPY --from=builder /usr/libexec/git-core /usr/libexec/git-core
方案三:使用 git-core PPA(不推荐)
add-apt-repository ppa:git-core/ppa -y
apt-get install -y git
PPA 提供的 Git 同样使用 GnuTLS,问题依然存在。
Alpine Linux 是个特例
如果你换用 Alpine Linux,情况会完全不同:
$ apk add git
$ ldd $(which git)
/lib/ld-musl-x86_64.so.1
libpcre2-8.so.0
libz.so.1
libc.musl-x86_64.so.1
$ curl --version
curl 8.14.1 libcurl/8.14.1 OpenSSL/3.3.5 ...
Alpine 的 Git 不直接处理 TLS,而是通过 libcurl 做 HTTP 传输:
| 项目 | Alpine 3.20 | Ubuntu 24.04 |
|---|---|---|
| Git HTTP 方式 | 通过 libcurl | Git 自己处理 TLS |
| libcurl SSL 后端 | 纯 OpenSSL | OpenSSL + GnuTLS 混合 |
| Git SSL 后端 | 无(透传 libcurl) | GnuTLS |
http.sslbackend=openssl |
✅ 支持 | ❌ 不支持 |
| HTTPS 代理场景 | ✅ 正常 | ❌ gnutls_handshake 失败 |
所以如果条件允许,切换到 Alpine 基础镜像也是一个治本的办法——apk add git 默认就是 OpenSSL,无需自己编译。
性能对比
| 安装方式 | 发行版 | SSL 后端 | HTTPS 兼容性 | 构建速度 |
|---|---|---|---|---|
apt install git |
Ubuntu | GnuTLS | ❌ 部分环境有问题 | ✅ 秒级 |
git-core/ppa |
Ubuntu | GnuTLS | ❌ 同左 | ✅ 秒级 |
源码编译 --with-openssl |
Ubuntu | OpenSSL | ✅ 广泛兼容 | ❌ 约5-10分钟 |
| 多阶段 Docker 构建 | Ubuntu | OpenSSL | ✅ | ✅ 缓存后秒级 |
apk add git |
Alpine | OpenSSL (via libcurl) | ✅ | ✅ 秒级 |
总结
- Ubuntu 默认 Git 使用 GnuTLS 而非 OpenSSL,这是许可证策略导致
- GnuTLS 在某些网络环境(尤其代理)下 TLS 握手失败
- 最简单的解决方案是从源码编译 Git 并指定
--with-openssl - 在 Docker 中使用多阶段构建可以让最终镜像体积小且 OpenSSL 版本纯净
- 如果可以选择基础镜像,Alpine Linux 默认就是 OpenSSL,无需任何额外编译工作
- 不要被
curl --version显示的OpenSSL迷惑——Ubuntu 上 Git 的 HTTPS 不经过 curl
