如果你在 Jenkins 里尝试装过中文语言包,大概率见过这样一条红字:
Plugin “Chinese (Simplified) Language Pack / 中文语言包“ was not installed: Invalid filename returned by https://...
第一次看到这个报错的人基本都懵了:网络明明是通的,插件列表也能刷出来,为什么偏偏这个包装不上?有人不死心,连续点“安装”,结果每点一次失败一次,系统日志里全是同一句话。今天这篇就把这个报错从头到尾拆干净,包括它背后的下载机制、排查思路、三种从快到稳的解决办法,再加上一份常见变体和避坑速查表。刚接触 Jenkins 的新手可以直接照做,被这个问题反复折磨过的运维也能从里面找到一些排查灵感。
1. 先把报错拆开看:Invalid filename returned by 到底是什么意思
1.1 一个再常见不过的失败现场
这个报错的完整形态通常长这样:
Plugin “Chinese (Simplified) Language Pack / 中文语言包“ was not installed: Invalid filename returned by https://updates.jenkins.io/download/plugins/localization-zh-cn/1.3.1/localization-zh-cn.hpi“Invalid filename returned by”后面跟的是一个完整的插件下载链接。很多人会误以为这是“插件不存在”或者“版本找不到”,实际完全不是这么回事。这个错误的核心信息是:Jenkins 去那个链接下载插件文件的时候,拿到的文件名不合法,不是它期望的.hpi或.jpi格式,于是整个安装过程被直接中断。
也就是说,Jenkins 不是下载不到东西,而是下载到了一个“它不认识的东西”。打个比方:你去餐厅点了一份红烧肉,服务员确实端了一盘东西过来,但打开盖子一看是个空盘子,里面还放了张纸条写着“菜单”。这时候你肯定会拒绝收货,对吧?Jenkins 也是这么个态度:我要的是.hpi插件包,你返回给我的却不是,那我就认栽,报错。
1.2 下载链路上到底哪一步出了差错
要真正理解这个报错,得先看 Jenkins 从“点击安装”到“插件出现”之间经历了什么。整个过程大致是四步:
- Jenkins 读取更新中心配置文件
update-center.json,拿到插件列表和每个插件的元数据。 - 你勾选插件并点击安装后,Jenkins 根据元数据里的下载地址,向远程服务器发起 HTTP 请求。
- 服务器返回插件文件
localization-zh-cn.hpi,Jenkins 把它保存到本地$JENKINS_HOME/plugins目录。 - Jenkins 校验文件名和文件完整性,确认无误后解压安装。
问题往往就出在第三步和第四步之间。正常情况下,服务器响应头里会带有Content-Disposition: attachment; filename=localization-zh-cn.hpi这样的信息,Jenkins 用这个来决定把文件保存成什么名字。但如果这条请求经过某些网络中间层时被拦截,比如企业网关、安全设备、云防火墙之类,返回的可能是一个 HTML 错误页面、一段 JSON 提示文本,甚至是一个登录跳转页面。
这个时候 Jenkins 拿到的是一个乱七八糟的文件名,比如error.html、login.jsp,或者干脆没有合法的.hpi后缀,它就会抛出Invalid filename returned by <URL>。
为什么报错给人的感觉是“网络是通的”?因为update-center.json这个文件能正常下载,插件列表能正常加载,页面也能正常刷出来。真正出问题的是后续的插件文件下载环节。很多人在这个坑里卡半天,就是因为没意识到“连接通”和“下载链路健康”是两回事。
还有一种常见情况是更新中心地址被换成了某个不完整的镜像站,镜像站只同步了 JSON 文件,没有同步插件文件目录,或者同步了一半。Jenkins 照样能拿到元数据,但下载时镜像返回的是 404 页面,最终也会被识别成“无效文件名”。
2. 排查三连:看日志、试下载、定位是哪一环在捣乱
2.1 别急着重试,先抓一条完整日志
很多人遇到这个报错,第一反应是再点一次安装,这其实是效率最低的做法。正确的第一步是去把日志翻出来,看看报错连带的那个完整 URL 到底是什么。
日志入口有两个地方:
- 系统管理 → 系统日志 → 所有 Jenkins 日志
- 或者直接看
$JENKINS_HOME/logs/目录下的日志文件
在 Linux 环境里,$JENKINS_HOME一般是/var/lib/jenkins。不确定的话,可以在系统管理 → 系统设置里找到“主目录”这个字段。
日志里搜关键词Invalid filename returned by,能看到完整的异常栈。重点看两件事:第一,报错里出现的 URL 是官方更新中心域名还是某个镜像域名;第二,URL 末尾的插件版本号和当前 Jenkins 版本是否匹配。这些信息直接决定后面用哪个方案处理。
grep -n "Invalid filename returned by" /var/lib/jenkins/logs/*.log有些情况下日志里还会同时看到Connection timed out、Premature end of Content-Length delimited message body等网络类异常,这些和Invalid filename的成因不同,处理方式也不一样。
2.2 用 curl 复现一次下载,看响应头
拿到报错里的 URL 之后,不要只在浏览器里打开看一眼,因为浏览器默认会渲染页面,HTML 页面看起来也像正常页面。更靠谱的做法是用命令行直接请求这个地址,观察 HTTP 响应头。
假设报错里的 URL 是:
https://updates.jenkins.io/download/plugins/localization-zh-cn/latest/localization-zh-cn.hpi执行:
curl -sIL "https://updates.jenkins.io/download/plugins/localization-zh-cn/latest/localization-zh-cn.hpi"参数说明:-I表示只拿响应头,-L表示跟随重定向。看几个关键字段:
HTTP/1.1 200 OK:请求成功。Content-Type: application/java-archive或application/zip:返回的是插件包,正常。Content-Disposition: attachment; filename=localization-zh-cn.hpi:文件名正确,正常。Content-Type: text/html或Content-Type: application/json:返回的是页面,异常。
只要发现Content-Type是text/html,基本就能确定是中间层拦截了下载请求,返回了一个页面给 Jenkins,Jenkins 不认识这个“文件名”,所以安装失败。
如果是 404,那就是镜像站没有同步这个插件目录,属于源的问题。如果是 403,那就是网关或安全策略直接拒绝了.hpi请求。
2.3 结果就三类,对号入座
根据上面两步的观察,这个报错基本可以归类成三种情况:
| 观察结果 | 问题类型 | 优先处理方式 |
|---|---|---|
响应头是text/html,或文件名不是.hpi | 下载链路被网络中间层干扰 | 换镜像源或离线安装 |
| 响应头是 404 | 更新中心镜像同步不完整 | 更换为官方源或其他镜像 |
| 下载正常但安装仍失败 | 本地缓存损坏或权限问题 | 清缓存后重试 |
这三种情况我全都遇到过,处理难度从低到高大概是:缓存问题最简单,换源稍麻烦一点,离线安装基本能解决前两种。下面一个个说。
3. 三种解法:换源、离线包、清理缓存
3.1 方案A:把更新中心换成国内镜像源
如果你的网络环境访问官方更新中心不太稳定,替换更新中心地址是最直接的在线安装方案。
操作路径:
- 进入 系统管理 → 插件管理 → 高级设置。
- 找到“更新站点”配置项,默认地址是
https://updates.jenkins.io/update-center.json。 - 把这个地址替换成国内开源镜像站的更新中心地址。我这边常用的是某个高校开源软件镜像站,地址格式类似
https://mirrors.某高校.org/jenkins/updates/update-center.json,你可以换成自己实际用的镜像站。 - 点击“立即检查”或“提交”,让 Jenkins 重新拉取一次插件元数据。
- 回到“可选插件”页面,重新搜中文语言包并安装。
为什么换镜像能解决一部分问题?因为镜像站在同步update-center.json的时候,会顺带把里面的插件下载路径改写成本镜像站的地址。这样你后续下载的插件文件全部走镜像站的下载链路,绕过了官方源的下载域名,也就绕开了那个返回无效文件名的中间环节。
需要注意的是:换源之后,插件管理页里显示的源地址变了,但“可选插件”里还是能搜到中文语言包。如果搜不到,先点“立即检查”强制刷新一次元数据。
3.2 方案B:手动上传 .hpi 离线安装(最稳)
换源后如果还是报同样错误,或者你不想在插件管理页上反复折腾,直接走离线安装。这个方法最适合那种“在线下载怎么都不对,但浏览器访问网络又没问题”的环境。
步骤拆解:
- 打开浏览器,进入镜像站的插件下载目录。对应中文化插件的目录名是
localization-zh-cn。 - 选择一个和当前 Jenkins 版本兼容的版本,下载
localization-zh-cn.hpi文件。 - 回到 Jenkins:系统管理 → 插件管理 → 高级设置。
- 找到“上传插件”区域,点击“选择文件”,选中下载好的
.hpi文件,点击“上传”。 - 上传完成后,Jenkins 会提示插件已安装,部分版本需要重启才能生效。
注意一个细节:下载插件文件时,如果直接在浏览器里点击下载,有些镜像站会给文件重命名,比如变成localization-zh-cn.hpi (1),或者因为浏览器默认安全策略问题,保存下来的是一个 HTML 文件而不是真正的插件包。上传之前最好确认一下文件类型。
Linux 环境可以用file命令快速验证:
file localization-zh-cn.hpi如果输出是Zip archive data,说明文件正常。如果输出是HTML document,说明你保存到的是个网页,赶紧删掉重新下载。
更省心的方式是用命令行下载,直接指定输出文件名:
wget -O localization-zh-cn.hpi "https://镜像站地址/localization-zh-cn.hpi"3.3 方案C:清理插件缓存后重试
如果你之前已经尝试过好几次在线安装,每次都以失败告终,那本地plugins目录里大概率残留了半截下载文件。这些残留文件有时候会干扰后续安装,导致你反复报同样的错误。
处理方式:
- 先彻底停掉 Jenkins 服务,或者用 系统管理 → 安全重启 的方式重启。
- 进入
$JENKINS_HOME/plugins目录。 - 查找文件名里带有
tmp、part、以及localization-zh-cn字样的文件。 - 把
localization-zh-cn.hpi.part这类临时文件删掉。 - 重启 Jenkins,重新执行在线安装。
ls -lh /var/lib/jenkins/plugins/ | grep -i localization这个方法不能解决所有问题,但如果你的报错是“第一次安装失败,第二次又失败,之后每次必然失败”,大概率就是残留文件在作怪。清干净之后原来的报错可能直接就消失了。
4. 一次真实排障:从报错到界面变中文的完整过程
4.1 现场信息还原
某开发团队的内网环境里跑着一个 Jenkins,版本是某 2.4xx。同事在“可选插件”里搜到“Chinese (Simplified) Language Pack”,点击安装,等了几分钟,页面上方出现一条红色错误提示:
Plugin “Chinese (Simplified) Language Pack / 中文语言包“ was not installed: Invalid filename returned by https://updates.jenkins.io/download/plugins/localization-zh-cn/latest/localization-zh-cn.hpi系统日志里留下了更具体的异常信息:
java.io.IOException: Invalid filename returned by https://updates.jenkins.io/download/plugins/localization-zh-cn/latest/localization-zh-cn.hpi at hudson.model.UpdateCenter$DownloadJob.run(UpdateCenter.java:...)同事问我怎么办,我的排查路径是这样的。
4.2 从报错到界面中文化的完整过程
第一步,先看报错里的 URL,域名是updates.jenkins.io,说明还没换过更新中心源。这个环境是内网,访问外网要走统一出口,中间有一层安全设备,怀疑下载请求被这层设备返回了拦截提示页。
第二步,用 curl 模拟下载,观察响应头:
curl -sIL "https://updates.jenkins.io/download/plugins/localization-zh-cn/latest/localization-zh-cn.hpi"响应结果里Content-Type是text/html,Content-Disposition也没有出现。这就证实了我的判断:Jenkins 下载到的不是插件包,而是一个页面。
第三步,准备换国内镜像源。把更新中心地址改成了某高校镜像站的update-center.json地址,点击“立即检查”后,插件列表可以正常刷新。但重新安装时又失败了,这次报错里的 URL 变成了镜像站地址,响应内容依然是text/html。说明这台机器的出口流量对所有外域名都走同一道安全网关,镜像域名也被拦了。
第四步,走离线安装。直接在浏览器里访问同一个镜像站的插件目录页面,手动下载localization-zh-cn.hpi,上传到 Jenkins 的“上传插件”区域。上传完成后 Jenkins 提示安装成功,并建议重启。
第五步,安全重启 Jenkins。重启完成后,进入 系统管理 → 外观(Appearance)→ 语言,把语言切换为“中文(简体)”,整个界面立刻变成中文。
整个排障过程大约十几分钟,真正卡时间的不是操作本身,而是“第一次没看响应头,差点又在那反复重试”。
4.3 几个当时最关键的判断依据
回头复盘,有几个信息点对快速定位起了决定性作用:
- 报错里的 URL 域名是哪个,决定了要不要先换源。
- 响应头的
Content-Type是text/html还是application/java-archive,直接区分了“链路拦截”和“下载正常”。 - 浏览器能打开页面,不代表 Jenkins 能下载成功,因为 Jenkins 要的是文件,浏览器要的是渲染后的页面,两者关心的东西完全不同。
如果你的环境和这个案例类似,我建议直接跳过在线安装的反复重试,第一个动作就去看响应头,第二个动作就直接准备离线包。这能省掉至少半小时的无效操作。
5. 这份报错的常见变体和避坑速查
5.1 报错变体速查表
Invalid filename returned by这个报错在 Jenkins 的插件安装场景里有一大群亲戚,表现形式略有不同,根因大同小异。我把常见变体整理成一张表:
| 报错内容 | 可能原因 | 首选处理 |
|---|---|---|
Invalid filename returned by https://...hpi | 下载请求被中间层拦截,返回非插件文件 | 换镜像源或离线安装 |
Failed to download from https://... | 网络连接超时或链路不通 | 检查网络出口,再决定换源还是重试 |
Checksum mismatch | 下载文件被篡改,或镜像同步不完整 | 清缓存,换版本重新下载 |
Plugin is not installed: ...且日志里无网络异常 | 本地文件残留,或插件依赖未安装 | 清理plugins目录残留文件 |
上传.hpi后提示版本不兼容 | 插件要求的最低 Jenkins 版本高于当前版本 | 下载旧版插件包 |
很多变体表面看着不一样,比如Checksum mismatch,经常被误认为是网络安全问题,实际上大多数情况是下载时文件被网关做了内容替换,或者镜像站同步到一半就把文件暴露出来了。处理方式仍然是“清缓存重下”或“离线安装”。
5.2 离线上传时最容易踩的四个坑
离线安装虽然是压舱石方案,但操作细节上有几个坑,不留意还是会浪费时间。
第一个坑是文件名不对。从浏览器下载插件,保存下来的文件可能是localization-zh-cn.hpi (1),这种文件上传时 Jenkins 一般能识别,但部分旧版本会把括号当非法字符直接拒绝。上传前把文件名改成localization-zh-cn.hpi这样的纯格式。
第二个坑是下载到假的hpi文件。镜像站如果要求登录,你在未登录状态点击下载,拿到的可能是登录页的 HTML。上传后 Jenkins 会报“文件不是有效插件包”,到这一步再去查文件类型就多绕了一圈。所以下载完立刻用file命令验证。
第三个坑是插件依赖没装。中文化插件不是孤立的,它可能依赖前端资源库、公共语言库等基础插件。离线上传主插件时,Jenkins 会提示缺少某个依赖。解决办法是在插件详情页把依赖列表一起下载下来,逐个离线上传。
第四个坑是上传后没重启。很多插件安装后只提示“安装完成”,但功能没生效。别急着点进“已安装插件”页面确认,直接在系统管理页面做一次安全重启,最省心。
5.3 一点经验小结
我自己在排这种问题的时候,已经不太愿意在“在线安装”上死磕了。凡是遇到Invalid filename returned by这种带下载环节的报错,第一反应就是去日志里把 URL 捞出来,第二反应是用 curl 看响应头,然后就根据结果决定是换源还是手动下载。在线安装按钮诚然方便,但前提是下载链路足够干净;一旦链路被中间层污染,离线安装往往是代价最小、成功率最高的那条路。
这个方法不只适用于中文语言包。其他 Jenkins 插件只要出现同样的Invalid filename returned by报错,处理思路完全一致:找到报错 URL、下载正确的.hpi文件、离线上传、重启。这套流程吃透之后,你在任何一台网络环境“不太好伺候”的 Jenkins 上排插件问题,心里都会更有底。