分类
devops

gnutls_handshake() failed: The TLS connection was non-properly terminated.

Git HTTPS 连接失败?从 GnuTLS 到 OpenSSL 的排查与解决

问题

在 Ubuntu 24.04 上通过 apt install git 安装 Git 后,执行 git fetchgit 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