☰
内网代码托管平台自签名证书配置指南:从生成到客户端信任
2026/10/19 3:15:28 网站建设 项目流程

1. 为什么内网环境需要自签名证书

很多团队在内部网络搭建代码托管平台时,都会遇到一个很实际的问题:平台部署在内网,没有公网域名,也懒得去申请受信任的CA证书,于是直接用IP加HTTP访问。短时间用着没问题,但一旦涉及到账号密码传输、令牌调用、CI/CD流水线里的克隆操作,HTTP明文传输就变得非常刺眼。密码在网络上裸奔,任何一个能抓包的人都能看到,这在安全审计里基本是一票否决项。

自签名证书就是解决这个矛盾最直接的方案。它不需要向任何公共CA机构申请,不需要域名备案,也不需要花钱,自己生成一对密钥和证书,把证书装到服务端,再把根证书分发给所有需要访问的客户端,就能把HTTP升级成HTTPS。整个过程完全在内部闭环,不依赖外部网络,特别适合内网代码托管、内部测试环境、临时演示环境这类场景。

但自签名证书有个绕不开的坎:浏览器和Git客户端默认都不信任它。因为证书不是受信任CA签发的,客户端无法验证服务端身份,就会弹出安全警告,Git命令行则会直接报错“SSL certificate problem: self-signed certificate”。很多人第一次配的时候,看到这个报错就卡住了,要么放弃改用HTTP,要么直接关掉SSL验证,这两种做法都不太妥当。关掉验证等于把HTTPS的安全意义完全抹掉,只是换了个协议名而已。

所以这篇文章要讲清楚的是:怎么生成一套合理的自签名证书,怎么把它正确配置到代码托管平台上,怎么让浏览器和Git客户端都信任它,以及在这个过程中有哪些容易踩的坑。整套流程我反复做过很多次,从单机测试到小团队内部使用都跑通过,下面把完整的思路和操作细节拆开来讲。

2. 自签名证书的整体设计思路

2.1 证书链的基本结构

要理解自签名证书怎么配,先得搞清楚证书链是怎么回事。正常的HTTPS证书是一条信任链:根证书签发中间证书,中间证书签发服务器证书,客户端信任根证书,就能顺着链条验证服务器证书。自签名证书相当于自己扮演了CA的角色,自己生成根证书,再用根证书签发服务器证书,形成一条只有两级的短链。

这里有个关键点:服务器证书里必须包含正确的“使用者备用名称”,也就是SAN。早期证书用CN字段来标识域名,但现代浏览器和Git客户端已经不再依赖CN,而是强制检查SAN。如果SAN里没有你访问时用的地址,即使证书本身没问题,客户端也会报“证书名称不匹配”。所以生成证书时,SAN里要写清楚所有可能用到的访问方式,包括IP地址和主机名。

2.2 为什么推荐两级证书而不是单级自签名

有人图省事,直接生成一张自签名证书,既当根证书又当服务器证书,装到服务端就完事。这样做的问题是,客户端要信任这张证书,就得把这张服务器证书本身导入受信任列表。一旦服务器证书到期或者需要更换,所有客户端都得重新导入一遍,维护成本很高。

更合理的做法是生成两级:一张根证书,长期有效,只用来签发;一张服务器证书,短期有效,装到服务端。客户端只需要信任根证书,服务器证书换了也不影响客户端的信任关系。这个结构和公共CA的运作方式是一致的,只是把CA换成了自己。根证书的私钥要保管好,最好离线保存,服务器证书的私钥放在服务端即可。

2.3 证书参数的选择逻辑

生成证书时有一堆参数要填,很多人直接一路回车用默认值,结果后面出各种问题。这里把关键参数的选择逻辑说清楚。

密钥算法选RSA 2048位或4096位,2048位目前足够安全,4096位更稳妥但握手稍慢。椭圆曲线算法也可以,但兼容性在某些老客户端上不如RSA。有效期方面,根证书可以设长一些,比如十年,服务器证书建议不超过两年,方便轮换。SAN里要写全IP和域名,格式是IP:192.168.1.100和DNS:gitlab.internal这种。国家、省份、组织这些字段随便填,不影响功能,但建议填得规范一点,方便以后排查。

还有一个容易忽略的点:基本约束。根证书要标记为CA:TRUE,表示它可以签发其他证书;服务器证书要标记为CA:FALSE,表示它不能被用来签发。这个标记如果搞反了,客户端验证时会直接拒绝。

3. 生成自签名证书的完整操作

3.1 准备目录和配置文件

我习惯把所有证书相关文件放在一个独立目录里,比如/etc/gitlab/ssl或者项目目录下的certs文件夹。先建目录,然后创建一个OpenSSL配置文件,把SAN等信息写进去,避免命令行里拼一长串参数。

mkdir -p /etc/gitlab/ssl cd /etc/gitlab/ssl

创建openssl.cnf文件,内容如下:

[req] default_bits = 4096 prompt = no default_md = sha256 distinguished_name = dn req_extensions = req_ext [dn] C = CN ST = Internal L = Internal O = Internal Team OU = Dev CN = gitlab.internal [req_ext] subjectAltName = @alt_names [alt_names] DNS.1 = gitlab.internal DNS.2 = gitlab IP.1 = 192.168.1.100

这个配置文件里,CN写的是主机名,alt_names里把域名和IP都列上。实际使用时把IP换成你服务器的真实内网地址。如果还有其他访问方式,比如短主机名或者第二个网卡地址,都加进去。

3.2 生成根证书

根证书的私钥要加密码保护,虽然每次签发都要输密码稍微麻烦,但安全上值得。命令如下:

openssl genrsa -aes256 -out ca.key 4096

执行后会提示输入密码,输两遍记住。然后生成根证书,有效期设十年:

openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \ -subj "/C=CN/ST=Internal/L=Internal/O=Internal Team/OU=Dev/CN=Internal Root CA" \ -extensions v3_ca

这里-extensions v3_ca会应用默认的CA扩展,确保根证书带有CA:TRUE标记。生成完可以用下面的命令检查一下:

openssl x509 -in ca.crt -text -noout | grep -A1 "Basic Constraints"

应该看到CA:TRUE。

3.3 生成服务器证书

先生成服务器私钥,这次不加密码,因为服务端启动时没法交互输入密码:

openssl genrsa -out gitlab.key 4096

然后生成证书签名请求:

openssl req -new -key gitlab.key -out gitlab.csr -config openssl.cnf

再用根证书签发服务器证书,有效期设两年:

openssl x509 -req -days 730 -in gitlab.csr -CA ca.crt -CAkey ca.key \ -CAcreateserial -out gitlab.crt -extensions req_ext -extfile openssl.cnf

签发时需要输入根证书私钥的密码。完成后检查服务器证书的SAN:

openssl x509 -in gitlab.crt -text -noout | grep -A1 "Subject Alternative Name"

确认里面包含你配置的所有IP和域名。同时检查基本约束应该是CA:FALSE。

3.4 合并证书链

有些服务端配置需要把服务器证书和根证书合并成一个文件,形成完整的证书链。顺序是服务器证书在前,根证书在后:

cat gitlab.crt ca.crt > gitlab-chain.crt

这样客户端在握手时能拿到完整链条,验证起来更顺畅。虽然客户端本地已经信任了根证书,但带上链条是更规范的做法。

4. 在代码托管平台上配置HTTPS

4.1 放置证书文件

不同平台的配置方式略有差异,但核心都是指定证书和私钥的路径。以常见的自建代码托管平台为例,通常有一个专门的配置目录,比如/etc/gitlab/ssl。把gitlab.crt和gitlab.key放进去,权限设为600,属主设为服务运行用户:

chmod 600 gitlab.key chown git:git gitlab.key gitlab.crt

权限这块很多人不注意,私钥文件如果其他用户可读,等于把钥匙挂在门上。600权限是底线,有些安全要求高的环境还会用700目录权限再加一层。

4.2 修改服务配置

在平台的主配置文件里找到外部URL的配置项,把http://改成https://,同时指定证书路径。以某类配置文件为例:

external_url 'https://gitlab.internal' nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab-chain.crt" nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/gitlab.key" nginx['redirect_http_to_https'] = true

redirect_http_to_https这个选项建议打开,这样即使用户输入了http地址,也会自动跳转到https,避免有人误用明文访问。改完配置后重新加载服务:

gitlab-ctl reconfigure

这个命令会重新生成Nginx配置并重启相关服务,第一次执行时间稍长,耐心等它跑完。

4.3 验证服务端配置

服务起来后,先用curl在服务器本机测试:

curl -v --cacert /etc/gitlab/ssl/ca.crt https://gitlab.internal

如果返回正常的HTML内容,说明服务端证书配置没问题。如果报错,看具体错误信息:证书链不完整、私钥不匹配、SAN不包含访问地址,这几种是最常见的。

也可以用openssl直接测试握手:

openssl s_client -connect gitlab.internal:443 -CAfile /etc/gitlab/ssl/ca.crt

输出里会显示证书链和验证结果,Verify return code: 0 (ok)表示验证通过。

5. 让客户端信任自签名证书

5.1 浏览器端导入根证书

浏览器不信任自签名证书,需要把根证书ca.crt导入到系统的受信任根证书列表。不同操作系统操作方式不同,但思路一致:找到“受信任的根证书颁发机构”这个存储区,导入ca.crt,导入时确认信任该证书用于识别网站。

导入后重启浏览器,再访问平台地址,地址栏应该显示锁标志,不再有安全警告。如果还有警告,检查导入的位置是否正确,有时候导入到了“个人”存储区而不是“受信任的根证书颁发机构”,就不会生效。

5.2 Git客户端配置

Git客户端默认使用系统的证书存储,但不同平台和不同Git发行版行为有差异。最稳妥的方式是显式告诉Git使用我们指定的根证书:

git config --global http.sslCAInfo /path/to/ca.crt

这个配置对所有HTTPS仓库生效。如果只想对特定域名生效,可以用:

git config --global http."https://gitlab.internal/".sslCAInfo /path/to/ca.crt

配置完后测试克隆:

git clone https://gitlab.internal/group/project.git

如果还报SSL错误,用GIT_CURL_VERBOSE=1环境变量跑一次,看详细的握手过程,能定位到具体是哪一步失败。

5.3 命令行工具的证书配置

除了Git,其他命令行工具也可能需要访问平台,比如CI/CD里的runner、自动化脚本里的curl调用。curl可以用--cacert参数指定根证书,也可以把根证书放到系统默认的CA目录里。Linux下通常是/usr/local/share/ca-certificates/,放进去后执行update-ca-certificates更新。这样系统层面的工具都能信任这张根证书,不用每个工具单独配。

注意:把自签名根证书加入系统信任列表,意味着这台机器信任该根证书签发的所有证书。根证书私钥一定要保管好,一旦泄露,攻击者可以签发任意服务器证书来冒充内网服务。

6. 常见问题与排查技巧

6.1 证书验证失败的典型原因

报错信息可能原因排查方法
SSL certificate problem: self-signed certificate客户端未信任根证书检查根证书是否导入,Git的sslCAInfo是否指向正确路径
certificate has expired证书过期用openssl x509 -in cert.crt -noout -dates查看有效期
certificate subject name does not matchSAN不包含访问地址检查SAN里的IP和域名是否覆盖当前访问方式
unable to get local issuer certificate证书链不完整确认服务端配置的是合并后的证书链文件
key values mismatch证书和私钥不配对用openssl rsa -noout -modulus和openssl x509 -noout -modulus对比模数

6.2 证书更换后的处理

服务器证书到期更换后,客户端不需要做任何操作,因为信任的是根证书,根证书没变,新签发的服务器证书自动被信任。但如果根证书也换了,所有客户端都要重新导入新的根证书。所以根证书的有效期要设得足够长,减少更换频率。

更换服务器证书时,把新的证书和私钥放到配置路径,重新加载服务即可。如果用了合并的证书链文件,记得重新生成。更换后可以用openssl s_client验证新证书是否生效。

6.3 多台服务器共用证书

如果有多台内网服务器需要配置HTTPS,可以用同一张根证书签发不同的服务器证书,每台服务器证书的SAN里写各自的地址。这样客户端只需要信任一张根证书,就能访问所有服务器。管理起来比每台服务器单独生成自签名证书要清爽得多。

也可以给每台服务器签发包含多个SAN的证书,但这样一张证书泄露影响面更大,不推荐。按服务器分开签发,各自独立,更符合最小权限原则。

6.4 容器环境下的证书处理

在容器里跑Git客户端或者CI runner时,容器内可能没有宿主机的证书存储。解决办法是在构建镜像时把根证书复制进去并更新CA存储,或者运行时把根证书挂载到容器里,再配置Git使用它。Dockerfile里可以这样写:

COPY ca.crt /usr/local/share/ca-certificates/internal-ca.crt RUN update-ca-certificates

这样容器内的Git和curl都能信任自签名证书。如果容器基础镜像没有update-ca-certificates命令,可以手动把证书追加到/etc/ssl/certs/ca-certificates.crt文件末尾。

7. 实操中的经验与避坑要点

7.1 私钥权限和备份

私钥文件的权限一定要严格控制,600是底线。我见过有人把私钥放在Web可访问目录里,结果被扫描到直接下载走,等于把HTTPS的底裤都扒了。根证书私钥最好离线保存,不要放在服务器上,签发服务器证书时临时拷过来用,用完删掉。

备份方面,根证书和私钥要备份,服务器证书和私钥也要备份。但备份介质的访问权限同样要控制,不能随便扔在共享目录里。我习惯把根证书私钥加密压缩后存到独立的密码管理工具里,服务器证书私钥则随服务器配置一起备份。

7.2 SAN配置的常见遗漏

SAN里最容易漏的是短主机名。比如配置里只写了gitlab.internal,但用户习惯用gitlab直接访问,就会报名称不匹配。解决办法是把所有可能的访问方式都列进SAN,包括完整域名、短主机名、IP地址。多写几个不影响功能,漏写一个就可能导致部分用户访问失败。

另一个容易漏的是IPv6地址。如果内网启用了IPv6,客户端可能优先用IPv6访问,而SAN里只有IPv4地址,同样会报错。检查方法是用curl -6强制走IPv6测试一下。

7.3 时间同步的重要性

证书验证依赖系统时间,如果服务器和客户端时间偏差太大,会报“证书尚未生效”或“证书已过期”。内网环境如果没有配置NTP,机器时间跑偏是常有的事。配置证书前先确认所有相关机器的时间是同步的,可以用date命令对比,偏差超过几分钟就要先校准时间。

7.4 逐步替换而非一刀切

如果平台已经在用HTTP运行,不要直接改成HTTPS然后重启,那样所有正在进行的克隆和推送都会中断。更稳妥的做法是先配好HTTPS,让两种协议并行运行一段时间,通知团队成员更新远程地址,等所有人都切换过来后再关闭HTTP。平台配置里通常有选项可以同时监听HTTP和HTTPS,利用这个过渡期平滑迁移。

7.5 测试覆盖要全面

配置完成后,测试不能只测浏览器访问。要把所有使用场景都覆盖到:浏览器访问、Git命令行克隆、CI/CD流水线拉取代码、API调用、Webhook回调。每个场景用的客户端和证书验证逻辑可能不同,只测一个场景就上线,很容易在某个环节翻车。我一般会列一个清单,逐项打勾确认,全部通过才算配置完成。

8. 关于证书轮换和长期维护

自签名证书不是配一次就一劳永逸的。服务器证书有有效期,到期前要记得更换。我建议在日历上设个提醒,提前一个月开始准备轮换。轮换流程就是重新生成服务器证书,替换服务端文件,重新加载服务。因为根证书没变,客户端无感知,整个过程可以做到零中断。

根证书虽然有效期长,但也不是永久的。如果根证书私钥有泄露风险,或者组织架构调整需要更换CA,就要走完整的根证书轮换流程。这个流程麻烦一些,需要重新签发所有服务器证书,并通知所有客户端导入新的根证书。所以根证书的私钥保护要格外上心,避免被迫轮换。

还有一点,证书的算法和密钥长度也要跟上时代。现在2048位RSA还够用,但过几年可能就不够了。根证书有效期设十年的话,中间可能要评估一次是否升级到更长的密钥或换用椭圆曲线算法。这些都要在规划时留出余地。

我个人在实际操作中的体会是,自签名证书这套东西,原理不复杂,但细节特别多。每一个参数、每一个权限、每一个配置项,都可能成为后面排查问题的线索。第一次配的时候花点时间把每个步骤都理解透,后面维护起来就轻松很多。最怕的是一知半解,照着教程敲一遍命令,出了问题完全不知道从哪查起。把证书链的结构搞清楚,把SAN的作用搞明白,把客户端信任的逻辑理顺,剩下的就是熟练度问题了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询