
1. 现场还原先看懂 “Blocked mirror” 到底拦住了什么1.1 一条完整的报错长什么样先说一下我当时的情况。项目一直用 Maven 3.6.3 跑得好好的某天为了统一构建环境把本机 Maven 升到了 3.8.8结果执行mvn clean package -DskipTests的时候依赖下载阶段直接红了一片报错里反复出现一个关键词Blocked mirror。实际报错信息类似下面这种[ERROR] Failed to execute goal on project demo-service: Could not resolve dependencies for project com.example:demo-service:jar:1.0.0: Failed to collect dependencies at org.springframework.boot:spring-boot-starter-web:jar:3.1.2: Could not transfer artifact org.springframework.boot:spring-boot-starter-web:jar:3.1.2 from/to aliyun (http://maven.aliyun.com/nexus/content/groups/public): Blocked mirror for repo [aliyun] (http://maven.aliyun.com/nexus/content/groups/public)仔细看最后一行重点是两个信息from/to aliyunMaven 解析到当前用的仓库地址是http://maven.aliyun.com/nexus/content/groups/publicBlocked mirror for repo [aliyun]这个镜像被标记为blocked也就是被禁用了我当时第一反应是去翻 pom.xml怀疑是不是某个依赖的 repository 写错了。结果项目里干干净净只有一个 spring-boot 父 POM 继承。后来把 Maven 版本切回 3.6.3问题立刻消失再切回 3.8.8问题复现。到这里基本可以肯定这不是项目配置的问题而是 Maven 自身版本升级带来的行为变化。1.2 哪些场景最容易踩中这个报错并不是所有人都能遇到它有几个比较明显的触发条件。第一种是本地开发环境升级 Maven 到 3.8.1 及以上版本同时~/.m2/settings.xml里配置了 HTTP 协议的镜像地址。比如老的阿里云镜像地址、某些公司内网 Nexus 地址只要协议是http://就极大概率命中。第二种是 CI/CD 流程里升级了 Maven 版本。GitLab Runner、Jenkins Agent 或者自建 Nexus 配套的构建机经常用基础镜像自带 Maven镜像一升级就是大版本跳跃历史构建设置里写的 HTTP 内网仓库地址就集体失效了。第三种比较隐蔽是 IDEA 这类 IDE 自带的 Maven 版本。IDEA 2023 之后自带的 Bundled Maven 已经是 3.9.x如果你之前一直用的是 IDEA 内置 Maven某天 IDEA 升级后自动切到了新版本同样会触发。很多人以为自己的 Maven 没动过其实 IDE 已经替你动了。1.3 为什么升级前没事升级后就炸了核心原因在于 Maven 3.8.1 之后的默认配置里官方主动加了一个 mirror 拦截规则把所有外部 HTTP 仓库统统标记为blocked。这不是你项目的问题也不是配置写错的问题而是 Maven 官方在安全层面上做的一次一刀切。具体来说这个默认配置写在 Maven 安装目录的conf/settings.xml里只要你用的是 3.8.1 及以上版本这个规则就存在。你的用户级settings.xml里配置的 HTTP 镜像地址在解析时和这个拦截规则产生了冲突于是 Maven 宁可报错也不往下走。2. 根因拆解settings.xml 里的 blocked 标签到底干了什么2.1 Maven 3.8.x 官方默认新增的拦截镜像Maven 3.8.1 的官方发布说明里提到了一个安全相关的变更默认配置中增加了一个maven-default-http-blocker镜像专门用于阻止 Maven 直接访问外部的 HTTP 仓库。这个配置写在 Maven 安装目录下的conf/settings.xml中内容如下settings xmlnshttp://maven.apache.org/SETTINGS/1.2.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd mirrors mirror idmaven-default-http-blocker/id mirrorOfexternal:http:*/mirrorOf urlhttp://0.0.0.0//url blockedtrue/blocked /mirror /mirrors /settings只要你的 Maven 版本是 3.8.1 及以上这个镜像规则就一定存在不管你有没有手动改过settings.xml。它的存在目的很简单强制所有外部 HTTP 仓库不可用促使大家使用 HTTPS 仓库。你可能会问Maven 为什么要做这种强制因为 HTTP 协议是明文传输依赖包在下载过程中有被篡改的风险而且历史上出现过不少通过 HTTP 仓库劫持注入恶意构件的事件。Maven 官方通过这个默认配置相当于把所有 HTTP 外部仓库默认拉黑。这个逻辑在 3.8.1 之后的 3.9.x 系列一直延续所以别想着升到 3.9 就能自动解决。2.2 mirrorOfexternal:http:* 的精确含义要理解自己为什么中招需要先看懂external:http:*这个通配符表达式。它并不是一个简单的*而是由三部分组合出的匹配规则external:前缀表示只匹配非本机的仓库地址。localhost、127.0.0.1这类本机地址不会被匹配。http:协议限定表示只匹配http://协议的仓库地址https://不会触发。*通配表示匹配所有符合前面条件的仓库。组合起来就是所有外部主机 HTTP 协议的仓库全部被这个镜像捕获。而blockedtrue则表示这个镜像虽然匹配到了仓库但它是一个禁用状态不能真的提供构件下载。于是 Maven 拿到依赖请求后发现仓库命中了一个标记为 blocked 的镜像直接抛出Blocked mirror for repo报错。有人可能觉得那我把仓库地址改成https://不就行了吗对这就是后面要讲的方案一。这也是为什么很多人切到 HTTPS 镜像地址后问题瞬间消失的原因。2.3 blocked 标签的生效机制与普通 mirror 的差异再说细一点。blocked标签并不是一个独立的配置节点它是mirror镜像配置的一个子标签。一个镜像配置有四件事需要关心id、mirrorOf、url、blocked。一个普通 mirror 的作用是把原本指向某个仓库的请求镜像到你指定的地址。比如你配置mirrorOfcentralurl 指向阿里云那么所有原本访问 Maven Central 的请求都会改道到阿里云。而blockedtrue的 mirror 则完全不同它不提供任何转发能力只是单纯地把匹配到的仓库标记为禁用。无论你 url 写的是什么最终结果都是请求被拦截。官方这个默认 blocker 把 url 写成http://0.0.0.0/本身就是个无效地址配合 blocked 标记就是为了让匹配到的仓库彻底不可用。这个机制有个很关键的行为特征Maven 在解析多个 mirror 时会按顺序取第一个匹配的镜像生效。官方默认配置在全局settings.xml里你的自定义镜像通常在用户级settings.xml里两者合并后匹配顺序不一定是你以为的那样所以经常出现我明明配了 HTTP 镜像却还是被默认 blocker 拦住的情况。3. 解决方案三种处理方式与选择建议3.1 方案一把 HTTP 镜像地址升级成 HTTPS首选这是最干净、也最推荐的处理方式。既然 Maven 官方在安全策略上已经明确不接受外部 HTTP 仓库从根源上把镜像地址升级为 HTTPS是最符合长期维护方向的做法。以阿里云 Maven 镜像为例老配置经常是这样mirror idaliyun/id mirrorOfcentral/mirrorOf urlhttp://maven.aliyun.com/nexus/content/groups/public/url /mirror这个地址就是 HTTP 协议在 Maven 3.8.1 下必然被maven-default-http-blocker拦截。改成新的 HTTPS 地址mirror idaliyun/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror改完之后仓库地址变成 HTTPS不会匹配external:http:*规则问题自然消失。除了阿里云腾讯云、华为云等公共镜像也都提供了 HTTPS 地址。如果你用的是公司内网 Nexus建议优先推动 Nexus 开启 HTTPS 支持因为现在主流 Nexus 3.x 配置 HTTPS 并不复杂挂上证书后一劳永逸。这个方案的核心思路是不跟 Maven 的安全策略对着干而是顺势而为。同时HTTPS 镜像还能避免被中间人劫持的风险构建产物安全性更高。3.2 方案二重新定义 maven-default-http-blocker 放行内网 HTTP 镜像如果内网 Nexus 因为种种原因暂时没法上 HTTPS比如证书审批周期长、设备老旧不支持那就需要绕开官方默认配置。最稳妥的做法是在用户级settings.xml里重新定义一个同 id 的镜像覆盖官方默认的 blocker。覆盖配置如下settings xmlnshttp://maven.apache.org/SETTINGS/1.2.0 mirrors mirror idmaven-default-http-blocker/id mirrorOfexternal:http:*/mirrorOf urlhttp://nexus.internal.example.com/repository/maven-public//url blockedfalse/blocked /mirror /mirrors /settings这段配置的核心操作有两步用了和官方默认配置相同的 idmaven-default-http-blocker触发配置覆盖逻辑。把blocked显式设置为false同时把url改为内网实际可用的 Nexus 地址。这样一来Maven 仍然会对所有外部 HTTP 仓库做镜像转发但转发目标是你的内网 Nexus而不是一个永远连不上的0.0.0.0。如果你希望只放行特定仓库可以把mirrorOf写成更精确的表达式比如external:http:*,!central但一般情况下按上面的配置就够用了。注意在用户级settings.xml里覆盖全局配置依赖的是同名 id 覆盖机制。如果你自定义镜像的 id 和官方默认的完全不同则无法覆盖会被默认规则继续拦截。这一点写配置的时候一定要检查清楚。3.3 方案三注释掉官方默认拦截器应急手段如果你只是本地临时调试不想动镜像地址也不想改用户级配置可以直接修改 Maven 安装目录下的conf/settings.xml把maven-default-http-blocker这个镜像注释掉。具体操作是打开apache-maven-3.8.x/conf/settings.xml找到最底部的镜像配置块注释或者删掉!-- mirror idmaven-default-http-blocker/id mirrorOfexternal:http:*/mirrorOf urlhttp://0.0.0.0//url blockedtrue/blocked /mirror --改完后保存重新执行构建你的 HTTP 镜像就能恢复使用了。这个方案虽然简单直接但有一个明显的隐患它属于修改全局配置如果哪天 Maven 升级重装或者换了一台机器这个改动就会丢失。如果是 CI 环境每次拉取官方 Maven 基础镜像时改动也会被重置CI 流水线需要持续维护这个配置。所以我个人的定位是应急手段适合本地排查不适合作为团队长期方案。3.4 三种方案对比与选型建议把三种方案放在一起对比看会更直观方案改动位置是否根治维护成本适用场景镜像地址改 HTTPS用户级 settings.xml是低公共镜像、支持 HTTPS 的 Nexus同名覆盖默认 blocker用户级 settings.xml是中内网 HTTP Nexus、无法快速升级 HTTPS注释默认 blockerMaven 安装目录 conf否高本地应急、临时验证我的建议非常简单能上 HTTPS 就上 HTTPS上不了就用同名覆盖注释全局配置只作为最后手段。如果你在公司团队里最好统一把方案一或方案二固化成团队规范避免每个人各改各的最后维护成本集中在运维身上。4. 实操验证改完怎么确认 “Blocked mirror” 真的消失4.1 查看当前生效的合并配置修改完settings.xml之后不要急着直接构建。先用 Maven 自带命令确认一下当前生效的配置到底是什么样这一步能帮你少踩很多隐形的坑。执行mvn help:effective-settings这个命令会输出全局配置和用户配置合并后的最终结果包括所有生效的 mirror。如果你想精确确认maven-default-http-blocker是否存在、blocked 状态是什么可以配合 grep 过滤mvn help:effective-settings | grep -A 5 -B 1 blocker输出里如果是方案一之后的场景你应该能看到自己配置的 HTTPS 镜像如果是方案二之后的场景应该能看到 id 为maven-default-http-blocker的镜像且blocked为 false。确认这块没问题再继续下一步。4.2 用 mvn -X 跟踪镜像命中与依赖下载effective-settings只能说明配置层面没问题真正要看的是运行时 Maven 到底把请求发给了谁这时候需要开调试日志。执行mvn -X clean compile -DskipTests日志会非常长建议直接输出到文件里再检索mvn -X clean compile -DskipTests build.log 21 grep -i blocked build.log grep -i using mirror build.log正常情况下日志中会出现类似下面的内容[DEBUG] Using mirror aliyun (https://maven.aliyun.com/repository/public) for repo central (https://repo.maven.apache.org/maven2)这句话的意思是Maven 在解析中央仓库时成功地映射到了你配置的镜像。此时没有再出现Blocked mirror for repo之类的字样说明问题已经解决。如果日志里还是出现Blocked mirror那大概率是配置覆盖没生效回头检查一下镜像 id 是否写对、blocked 是否真的改成了 false。4.3 IDEA 内嵌 Maven 的差异化排查IDEA 用户有一个很常见的现象命令行里mvn构建没问题IDEA 导入项目却依旧报Blocked mirror。这种情况通常不是配置没改而是 IDEA 用的 Maven 根本不是你在命令行里用的那个。IDEA 内置的 Bundled Maven 有自己的conf/settings.xml它和你的用户级配置合并后的行为可能和你预期的不同。排查步骤分三步。第一步确认 IDEA 当前使用的 Maven 版本和配置文件路径。打开 Settings - Build Tools - Maven看 Maven home path 是 Bundled还是你自己安装的路径再看 User settings file 指向哪里。如果 Maven home path 是 Bundled说明 IDEA 用的是内置版本命令行里改的 Maven 配置对它不生效。第二步把 User settings file 显式指定为你自己的~/.m2/settings.xml。这一步能让 IDEA 至少加载你修改过的用户配置。第三步设置完后点击 Maven 设置面板里的刷新按钮然后重新导入项目。如果之前已经导入过建议执行一次mvn clean把损坏的缓存清掉。还有一个细节IDEA 内置 Maven 的版本通常比你自己装的要新3.9.x 很常见所以不要拿自己机器的 Maven 行为去推断 IDEA 里的行为一切以 IDEA 显示的配置为准。5. 常见问题速查升级 Maven 路上容易连环踩的坑5.1 报错说 blocked但项目 pom.xml 里根本没有 http 仓库这是最让人困惑的场景之一。明明 pom.xml 里配置的都是 HTTPS 地址repositories标签也没写过 http 地址为什么还会报Blocked mirror真正的根源往往在settings.xml的镜像配置里。如果你的全局或者用户配置里有一个 mirror 的 url 是http://开头那么在运行时所有命中这个镜像的仓库请求都会变成 HTTP 请求从而被默认 blocker 拦截。比如下面的配置mirror idinternal/id mirrorOf*/mirrorOf urlhttp://nexus.internal.example.com/repository/maven-public//url /mirrormirrorOf*把所有仓库都劫持到这个 HTTP 地址external:http:*规则自然命中。遇到这种情况建议先用mvn help:effective-settings看一看最终生效的镜像配置基本一眼就能定位。5.2 明明镜像已经是 HTTPS还报 Blocked mirror这种情况我见过一次排查了好一会儿。最后发现镜像本身确实是 HTTPS但 pom.xml 或者父 POM 的repositories里有一个仓库地址写的是http://而且仓库 id 恰好匹配了某个 mirror 规则。换句话说Maven 的仓库解析是多维度的镜像规则、仓库地址、仓库 id 三者都会影响最终行为。一个 HTTP 直连仓库即使你的镜像本身是 HTTPS只要它单独命中了external:http:*拦截规则同样会报 blocked。排查方法还是先看effective-settings和mvn -X日志找到报错的那条仓库是从哪个配置里冒出来的。不要想当然日志不会骗人。5.3 CI 容器里改好了下次构建又复现在 CI 环境里这个问题有很强的迷惑性。你在一台 Jenkins Agent 上改了 Maven 配置构建恢复正常但第二天执行新的构建任务时问题又出现了。原因通常是 Jenkins Agent 或 GitLab Runner 每次构建都会重新拉取基础镜像Maven 安装目录里的conf/settings.xml被重置成官方默认配置你之前做的修改全部丢失。针对这种情况正确的做法是把自定义的settings.xml作为构建配置的一部分管理起来。具体可以有三条路把自定义 settings.xml 提交到 Git 仓库在构建命令里用mvn -s path/to/settings.xml显式指定。在 Dockerfile 里通过COPY把 settings.xml 放进去确保镜像构建时已经包含自定义配置。如果是 Kubernetes 部署的 CI 集群可以把 settings.xml 挂载为 ConfigMap 或 Secret避免每次构建都重新配置。这其实也反映了一个更深层的建议团队应该把 Maven 版本和 settings.xml 配置一并固化不能靠人工在机器上手工调整。5.4 改了配置后从 Blocked mirror 变成 401 / 404Blocked mirror 解决之后下一个常见的坑就是 HTTP 状态码错误。401 说明认证信息不对404 说明请求的资源在目标仓库里不存在。如果是公司内网 Nexus401 通常是账号密码或者权限配置有问题需要在 mirror 里加上server配置指向 settings 文件里的 server 节点。如果是公共镜像404 常常是因为镜像仓库聚合的仓库组不对。比如阿里云老地址http://maven.aliyun.com/nexus/content/groups/public在某些迁移节点上已经不再维护切到https://maven.aliyun.com/repository/public就能解决。另外有些公共镜像只同步了 Maven Central如果项目还依赖了 JCenter 或 Google Maven就需要单独配置对应镜像。从排查角度看401 和 404 其实是比 blocked 更好定位的问题因为日志里会直接给出 HTTP 状态码和目标 URL按照 URL 去检查仓库配置即可。5.5 一个容易被忽略的细节localRepository 与仓库镜像的互相影响最后提一个平时很难第一时间想到的点。settings.xml里的localRepository只是改变本地仓库落盘的路径它不影响远程仓库的选择。但它会影响一个排查方向如果本地仓库已经缓存了某个旧版本的构件即使 blocked 报错被解决你依然可能看不到新版本的下载行为因为 Maven 优先使用本地仓库的缓存。如果你改完配置后发现依赖版本并没有按预期更新记得加-U参数强制执行远程检查mvn clean install -U另外如果本地仓库里残留了之前 HTTP 仓库下载的半成品文件.lastUpdated后缀的文件Maven 有时候会直接报错而不重新下载。遇到这种情况把本地仓库对应目录清掉再构建是最快的方式。我在实际维护构建环境时最深的体会是大多数 Maven 相关的问题都不是什么高深的技术而是配置从哪里来、生效的是哪一份没理清楚。Blocked mirror 这个报错也一样弄清楚官方默认配置、你的用户配置、构建时用的 Maven 版本这三者的关系问题基本就解决了一大半。最后再分享一个小技巧如果你负责团队构建平台建议把 Maven 升级步骤固化成一份 checklist里面至少包含三件事——确认 Maven 版本、确认conf/settings.xml里的默认 blocker、确认~/.m2/settings.xml里的镜像地址协议。这三件事确认完Blocked mirror 相关的坑基本不会再踩第二次。