内网 Jenkins HTTPS 部署:自签证书、节点连接与中文乱码

内网 Jenkins HTTPS 部署:自签证书、节点连接与中文乱码
乔阳内网部署 Jenkins 时一旦启用 HTTPS 并用自签证书,Windows 节点基本连不上。原因是 JVM 不读 Windows 系统证书库,自签证书必须手动导入 Java 自己的 trustStore。加上中文 Windows 下 PowerShell 和 JVM 默认都是 GBK 编码,构建日志里的中文很容易变成乱码。下面是我在生产环境用过的一套流程,包含背后的机制和踩过的坑。
为什么会出问题
三个问题都源于同一件事:Jenkins 节点是一个跑在 JVM 里的 Java 程序,而 JVM 的证书和编码体系跟操作系统是两套。
证书体系是独立的。 Windows 把自签证书放进「受信任的根证书颁发机构」后,浏览器和 .NET 程序就认了。但 Java 不读 Windows 证书库,它有自己的 trustStore,默认在 $JAVA_HOME/lib/security/cacerts(JDK 8 是 jre/lib/security/cacerts)。节点连 HTTPS 的 Jenkins 时,JVM 用这个 trustStore 验证证书链,自签证书不在里面就直接报 unable to find valid certification path。
trustStore 和 keystore 不是一回事。 keystore 存自己的私钥和证书,trustStore 存你信任的对方证书。导入自签证书用的是 trustStore 的语义,命令里写的 -keystore 只是指定文件路径,别被名字误导。
证书库格式有版本差异。 JDK 9 之前默认 JKS(Java 专有格式),JDK 9 起(JEP 226)默认改为 PKCS12(行业标准)。导入和启动时 -storetype / trustStoreType 必须一致,否则会读到空库,表现为证书导入了但节点还是连不上。
编码冲突。 中文 Windows 控制台默认 codepage 936(GBK),JVM 在中文 locale 下默认 file.encoding 也是 GBK。Jenkins 流水线日志按 UTF-8 传输,节点按 GBK 解码就乱码。
准备工作
- Jenkins 服务已部署,反向代理已配置 HTTPS
- 自签证书文件(
.crt或.cer格式)已生成 - Windows 节点已装 JDK(建议 11 或 17)
- 已从 Jenkins 管理页面拿到节点密钥和
agent.jar
第一步:导入自签证书到 JVM trustStore
用 keytool 把证书导入一个 trustStore 文件。用户级证书库不需要管理员权限,推荐先用这个:
1 | keytool.exe -importcert -v -noprompt ` |
参数含义:
-importcert:导入受信任证书到 trustStore-noprompt:跳过「是否信任」的交互确认,脚本里必加-alias:证书在库里的别名,后续删除或查看靠它-file:自签证书文件路径-storepass:证书库密码,changeit是 cacerts 的默认密码,自定义库可以自己设-storetype:库格式,JDK 9+ 用PKCS12,JDK 8 用JKS-keystore:库文件路径
库文件放哪里有两种选择。
用户级(c:\users\<username>\.keystore):只影响当前用户跑的 Java 程序,不需要管理员权限,改动隔离。我倾向这个,单节点排查问题时不会污染其他服务。
全局(%JAVA_HOME%\lib\security\cacerts,JDK 8 是 jre\lib\security\cacerts):影响这台机器上所有 Java 程序,需要管理员权限。好处是启动参数里不用再指定 trustStore 路径,坏处是 JDK 升级或重装时这个库会被覆盖,证书会丢。生产环境如果要靠它,记得把证书导入写进部署脚本。
第二步:启动 agent 时指定 trustStore
导入之后,启动节点时用 JVM 参数告诉它去哪里找证书:
1 | java ` |
按实际环境改这些:trustStore 路径、Jenkins 地址、工作目录、节点密钥、节点名称。
几个关键点:
- 三个
trustStore*参数必须和导入时的-keystore/-storepass/-storetype完全对应,有一个对不上就读不到证书。 -url用 HTTPS 地址。-secret从 Jenkins 节点配置页拿。-webSocket这一项在内网反代场景很重要。JNLP 节点默认走固定 TCP 端口(通常 50000),反向代理一般只暴露 443,那个端口过不来。-webSocket让节点连接复用 HTTPS 通道走 WebSocket,反代只要正常转发 443 就行,不用再单独开端口。
第三步:处理中文乱码
乱码的根因是编码不一致,治本的办法是让 JVM 全程用 UTF-8。
设环境变量。 在「系统变量」里加一条:
1 | 变量名:JAVA_TOOL_OPTIONS |
JAVA_TOOL_OPTIONS 会被所有 JVM 进程自动读取,等于给每个 Java 程序都加了启动参数。设完之后重启 PowerShell 和节点代理,环境变量不会热生效。
如果还乱码,多半是 PowerShell 控制台本身的 codepage 还是 936。手动切到 UTF-8:
1 | chcp 65001 |
注意 chcp 只对当前会话有效,新开的窗口还会回到 936。要永久生效,可以把这行写进 PowerShell 配置文件,或者干脆让节点跑在一个固定的服务会话里。
也可以在启动命令里直接带参数,双保险:
1 | java -Dfile.encoding=UTF-8 -Djavax.net.ssl.trustStore=c:\users\test\.keystore -jar agent.jar ... |
验证
- 启动节点,看控制台有没有 SSL 报错(
SSLHandshakeException、unable to find valid certification path等)。 - Jenkins 管理页面里节点状态变成「在线」。
- 跑一个会输出中文的测试任务,看日志里中文是否正常。
- 检查工作目录有没有正常创建和写文件。
替代方案与取舍
上面这套是「自签证书 + 手动导入 trustStore」的路子,维护成本主要在证书续期和节点迁移时要重新导入。还有几种选择。
反代终止 TLS,内部走 HTTP。 反向代理上配真实证书(比如内网 CA 签的),代理到 Jenkins 用 HTTP。节点直接连 HTTP 的 Jenkins,完全没有证书信任问题。代价是 Jenkins 到代理这一段是明文,只适合代理和 Jenkins 在同一可信网络(同一台机器或同一内网)的情况。
用内网 CA 签发证书。 如果公司有内部 PKI,让 CA 给 Jenkins 签一张证书,再把 CA 根证书导入 trustStore。比自签规范,证书管理也更可控,前提是内部 CA 已经搭好。
禁用 TLS 验证。 网上能搜到设置 -Dcom.sun.net.ssl.checkRevocation=false 之类绕过验证的做法。不建议在生产环境用,等于把 HTTPS 退化成明文,中间人风险全开。
踩过的坑
- 导入和启动的 storeType 不一致。 用 JDK 8 的老机器导入时漏写
-storetype JKS,默认是 JKS 还能用;换到 JDK 17 的机器,默认变成 PKCS12,启动参数里还写着JKS,结果读不到证书。两边的类型一定要对齐。 - 环境变量改了没重启。
JAVA_TOOL_OPTIONS是进程启动时读的,改完不重启 PowerShell 和节点,看起来「配置对了还是乱码」,其实是旧进程还在跑。 - 全局 cacerts 被 JDK 升级覆盖。 早期把证书导进了全局 cacerts,后来 JDK 小版本升级,库文件被重置,节点集体掉线。后来全部改成用户级库加启动参数显式指定路径。
- 反代没透传协议头。 反向代理要把
X-Forwarded-Proto: https透给 Jenkins,否则 Jenkins 生成的回调 URL 可能是 HTTP,节点跟着 HTTP 去连就会失败。在 Jenkins 全局安全配置里把「Jenkins URL」也显式写成 HTTPS 地址,别让它自动猜。







