☰
Windows 上部署 Nexus Maven 私服:安装、仓库划分与避坑实战
2026/10/29 2:48:27 网站建设 项目流程

简介:nexus-3.45.0-01-win64 是面向 Java 开发团队与 Maven 项目维护者的 Windows 64 位 Nexus 私服安装包,用于在内网集中管理内部组件、第三方依赖及公共仓库缓存,缓解直连中央仓库的延迟与安全问题。压缩包共 690 个文件,约 240.62MB,以 420 个 jar、82 个 dll、29 个 xml、22 个 exe 及 properties、cfg、脚本等配置和运行文件为主,涵盖程序主体、依赖库与运行参数,目录中同时保留 sonatype-work 运行时数据与 Nexus 程序文件。已有 417 人学习或下载。读者可借助它搭建受控的 Maven 仓库服务,理解仓库分组、代理缓存、发布部署、权限控制与索引搜索等核心能力,并据此维护本地私服的目录结构和运行配置。

1. 为什么在 Windows 上折腾 maven 私服:从一次二十分钟的构建说起

团队里有人抱怨构建一次要等二十分钟,多数时候问题出在每个开发者都在对着公网仓库重复下载同一批构件,网络本身并不是主因。nexus windows版本之所以被反复搜索,是因为很多中小团队的内网机器本来就是 Windows Server 或者一台闲置工作站,装个 maven 私服最省事的路径就是拿nexus-3.45.0-01-win64这个包解压跑起来。它干的事很朴素:把中央仓库的构件缓存到本地,把团队自己的 release 和 snapshot 收进来,再用一个 group 仓库对客户端只暴露一个地址。手上一到几十个 Java 项目、构建频繁、又不想在 Linux 运维栈上耗时间的团队,最吃这套。下面按装、划仓库、接客户端、踩坑的顺序走一遍。

2. 拆包到能跑:nexus.vmoptions 的关键参数与首次启动的验证路径

2.1 包解开之后先认目录

压缩包解压出来是两层结构,nexus-3.45.0-01是程序本体,sonatype-work是运行时生成的数据目录,默认跟它同级。我一般固定成这个布局:

D:\nexus\ ├── nexus-3.45.0-01\ │ ├── bin\ # nexus.exe 与 nexus.vmoptions │ ├── etc\ # nexus-default.properties、karaf 配置 │ ├── lib\ │ ├── public\ │ └── system\ └── sonatype-work\ └── nexus3\ # blob 库、日志、临时文件、admin.password
路径作用要不要单独备份
bin/nexus.exe服务安装/启停的唯一入口随安装目录整体备份
bin/nexus.vmoptions堆内存、数据目录、日志路径要
etc/nexus-default.properties端口、监听地址、context path要
lib/、system/程序自带 bundle,不要手改不用
sonatype-work/nexus3/全部业务数据必须

安装目录这一条要注意:路径里不要带空格、中文和括号。nexus.vmoptions里大量使用相对路径,启动器在解析带空格的参数时容易出问题,日志里报的错还往往指向别处,排查起来很费劲。

2.2 用 nexus.vmoptions 固定堆内存和数据落盘位置

bin/nexus.vmoptions是启动时真正生效的 JVM 参数集合,改这里比改别的地方更直接:

# 位置:D:\nexus\nexus-3.45.0-01\bin\nexus.vmoptions # 初始堆和最大堆设成一样,避免运行期反复扩容造成停顿 -Xms2703m -Xmx2703m # 直接内存上限,大体积构件的上传/下载走 NIO 通道时用得到 -XX:MaxDirectMemorySize=2703m -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=../sonatype-work/nexus3/log/jvm.log -XX:-OmitStackTraceInFastThrow # 容器根目录固定为程序目录,所以安装目录别随便改名 -Dkaraf.home=. -Dkaraf.base=. -Dkaraf.etc=etc/karaf -Djava.util.logging.config.file=etc/karaf/java.util.logging.properties # 数据真正存的地方,换盘就改这三行 -Dkaraf.data=../sonatype-work/nexus3 -Dkaraf.log=../sonatype-work/nexus3/log -Djava.io.tmpdir=../sonatype-work/nexus3/tmp -Dkaraf.startLocalConsole=false

几个参数的含义值得说清楚。-Xms和-Xmx设成相等是常规做法,Nexus 启动后很快会吃满大部分堆,让 JVM 早期就把内存要到手,比运行期反复扩容稳。-XX:MaxDirectMemorySize是很多人漏掉的一项,只调-Xmx不调它,传几百兆的大包照样会在 NIO 层报内存不足,这类报错在nexus.log里往往只写一句 OOM,容易误判成磁盘问题。-Dkaraf.data是数据目录的唯一依据,想迁到独立盘,这三行一起改,改完重启服务,然后把老的sonatype-work整个目录搬到新位置。

注意:-Dkaraf.data改动前先停服务再搬目录,搬完用nexus.exe /run前台跑一次,确认新目录下生成了db、blobs、log三个子目录,再装回服务。

端口和监听地址不在nexus.vmoptions里,而是在etc/nexus-default.properties:

# 位置:D:\nexus\nexus-3.45.0-01\etc\nexus-default.properties application-port=8081 application-host=0.0.0.0 nexus-context-path=/ nexus-work=${bundleBasedir}/../sonatype-work/nexus3

application-host=0.0.0.0是让同网段的其他机器能连进来,默认值在很多版本里只绑本机回环地址,装完发现只有本机能开、同事一律超时,八成是这里没改。nexus-work要跟-Dkaraf.data指向同一处,两个地方不一致时会看到服务起来了但仓库列表是空的。

2.3 前台跑通再装服务,报错才有地方看

装成 Windows 服务之前,先用前台模式跑一遍。这一步省不了,直接/install再/start,进程一起来就退,日志还没刷完就断了,反而难查。管理员身份打开 CMD,先确认 Java 环境:

cd /d D:\nexus\nexus-3.45.0-01\bin # 启动器读的是 JAVA_HOME,不是 PATH 里那个 java echo %JAVA_HOME% java -version

JAVA_HOME指向 JDK 而不是 JRE,版本上 3.45 这一代用 JDK 8 最省心,手上只有更高版本就先别急,先用 JDK 8 把链路跑通再考虑升级。确认无误后前台启动:

nexus.exe /run

控制台刷到Started Sonatype Nexus一类字样就说明起来了。浏览器开http://127.0.0.1:8081/,能出登录页即通。接着从数据目录取初始密码:

# 首次启动后生成,登录后台改掉后这个文件会被删除 type D:\nexus\sonatype-work\nexus3\admin.password

前台验证通过,再装服务:

nexus.exe /install nexus.exe /start nexus.exe /status

/status输出Running再去做后续配置。日志两处:业务日志sonatype-work\nexus3\log\nexus.log,JVM 日志sonatype-work\nexus3\log\jvm.log,前者看业务异常,后者看内存和启动参数是否被正确读到。

# 非本机验证一下监听是否真的对外 curl -s -o NUL -w "%{http_code}\n" http://<本机内网IP>:8081/

返回200或302都算正常,返回000基本是防火墙或者监听地址的问题,这个放到后面避坑章节细说。

3. 仓库怎么划:proxy / hosted / group 的职责边界与 REST 建库脚本

3.1 四类仓库各管一段,别混着来

新手最容易犯的错是直接把构件推到maven-central,或者在maven-public上开写入。Nexus 3 的仓库分工很明确,按用途划开之后,权限、清理、备份都能独立做:

仓库名类型关键参数用途
maven-centralproxyremoteUrl=https://repo1.maven.org/maven2/,versionPolicy=RELEASE缓存公网构件,只读
maven-releaseshostedversionPolicy=RELEASE,writePolicy=ALLOW_ONCE团队正式版本,防重复覆盖
maven-snapshotshostedversionPolicy=SNAPSHOT,writePolicy=ALLOW开发中的快照,允许反复推
maven-publicgroupmemberNames按顺序排对客户端只暴露这一个地址

maven-releases的ALLOW_ONCE是刻意选的。正式版本被覆盖是事故级的,一旦有人mvn deploy推重了,下游拉到的 jar 和昨天不一致,问题能查一整天。snapshots反过来,开发阶段就该允许覆盖,所以写策略设为ALLOW。

Blob store 这一层,十几人的团队用默认的default就够。项目多到几百 GB 之后,我一般按 release 和 snapshot 拆两个 blob store,理由是清理策略和 compact 任务能分开跑,备份也能只备 release。这个小决定到后期能省不少事。

3.2 用 REST 建库,别在界面里点二十遍

Nexus 3 的仓库管理有完整 REST 接口,路径是/service/rest/v1/repositories/maven/{proxy|hosted|group}。JSON 直接塞命令行在 CMD 里转义很折磨,写成文件用--data-binary @文件名最干净。

先建代理仓库maven-central.json:

{ "name": "maven-central", "online": true, "storage": { "blobStoreName": "default", "strictContentTypeValidation": true, "writePolicy": "ALLOW" }, "proxy": { "remoteUrl": "https://repo1.maven.org/maven2/", "contentMaxAge": 1440, "metadataMaxAge": 1440 }, "negativeCache": { "enabled": true, "timeToLive": 1440 }, "httpClient": { "blocked": false, "autoBlock": true, "connection": { "retries": 3, "timeout": 60 } }, "maven": { "versionPolicy": "RELEASE", "layoutPolicy": "PERMISSIVE", "contentDisposition": "ATTACHMENT" } }

这里四个参数值得单独拎出来。contentMaxAge是构件缓存的保鲜期,单位分钟,1440意味着一天内同一个包不会再去公网问一遍,内网带宽就是这么省下来的。metadataMaxAge控制maven-metadata.xml的刷新频率,它跟contentMaxAge是两回事,调小了会频繁回源,调大了会发现公网新出的版本好几天都看不到。negativeCache.timeToLive是失败结果的记忆时间,公网抖动拉不到包时会记一笔,一小时内重复请求直接返回失败,这个机制能挡住雪崩,但也会让人误以为私服坏了。autoBlock打开后,远端连续失败会临时把仓库置为不可用,防止拖垮整个 group 的响应。

再建maven-releases.json:

{ "name": "maven-releases", "online": true, "storage": { "blobStoreName": "default", "strictContentTypeValidation": true, "writePolicy": "ALLOW_ONCE" }, "maven": { "versionPolicy": "RELEASE", "layoutPolicy": "PERMISSIVE", "contentDisposition": "ATTACHMENT" } }

maven-snapshots.json只改两处:name换成maven-snapshots,writePolicy改成ALLOW,versionPolicy改成SNAPSHOT。

最后建maven-public.json:

{ "name": "maven-public", "online": true, "storage": { "blobStoreName": "default", "strictContentTypeValidation": true }, "group": { "memberNames": ["maven-releases", "maven-snapshots", "maven-central"] } }

三条命令依次执行:

# Windows 下要写 curl.exe,PowerShell 里 curl 是 Invoke-WebRequest 的别名 curl.exe -u admin:你的密码 ^ -X POST "http://127.0.0.1:8081/service/rest/v1/repositories/maven/proxy" ^ -H "Content-Type: application/json" ^ --data-binary @maven-central.json curl.exe -u admin:你的密码 ^ -X POST "http://127.0.0.1:8081/service/rest/v1/repositories/maven/hosted" ^ -H "Content-Type: application/json" ^ --data-binary @maven-releases.json curl.exe -u admin:你的密码 ^ -X POST "http://127.0.0.1:8081/service/rest/v1/repositories/maven/group" ^ -H "Content-Type: application/json" ^ --data-binary @maven-public.json

返回 HTTP201就是建成功了。想确认建了哪些,查一遍:

curl.exe -s -u admin:你的密码 "http://127.0.0.1:8081/service/rest/v1/repositories" | findstr /C:"\"name\""

注意:curl.exe必须带后缀。Windows 的 PowerShell 把curl映射成了Invoke-WebRequest,参数语法完全不一样,直接敲curl会看到一堆莫名其妙的参数错误。

3.3 group 的成员顺序不是随便排的

memberNames的数组顺序决定查找优先级:Nexus 按顺序问,先命中先返回。把maven-releases和maven-snapshots排在maven-central前面,是为了让内部构件优先命中,避免同坐标的内部包被远端缓存抢先返回。实际踩过一次:某内部库跟公网某个包 groupId 撞了,central 排前面的时候,构建拉到的永远是公网那个老版本,本地代码改了半天不生效,翻车翻得莫名其妙。

另外两个可以提前想好的点。一是routing rule和content selector,能按包名前缀把请求分流到不同仓库,比如内部包统一用某个私有前缀,就只让它命中 hosted 仓库,公网请求一律走代理,这套在仓库多起来之后很省心。二是 cookie 和重定向,httpClient里那两个开关默认关着,遇到某些镜像站需要跟随重定向时才打开,平时别动。

4. 客户端接进来:settings.xml、distributionManagement 与 CI 凭据的三段式配置

4.1 settings.xml:镜像拦 central,别一刀切用通配

客户端侧第一份文件是~/.m2/settings.xml。核心是三段:servers存发布凭据,mirrors把 central 指到私服,profiles把仓库地址注册进去。

<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"> <servers> <!-- id 必须跟 pom 里 distributionManagement 的 repository id 一模一样 --> <server> <id>nexus-releases</id> <username>deployer</username> <password>{加密后的密码}</password> </server> <server> <id>nexus-snapshots</id> <username>deployer</username> <password>{加密后的密码}</password> </server> </servers> <mirrors> <!-- 只拦 central,别写 * --> <mirror> <id>nexus-public</id> <mirrorOf>central</mirrorOf> <url>http://192.168.1.20:8081/repository/maven-public/</url> </mirror> </mirrors> <profiles> <profile> <id>nexus</id> <repositories> <repository> <id>nexus-public</id> <url>http://192.168.1.20:8081/repository/maven-public/</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>true</enabled></snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>nexus-public</id> <url>http://192.168.1.20:8081/repository/maven-public/</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>false</enabled></snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> <activeProfiles> <activeProfile>nexus</activeProfile> </activeProfiles> </settings>

mirrorOf写central而不是*,这个选择有代价也有收益。写*看起来一劳永逸,实际会把构建里所有显式声明的仓库全部劫持到 group 上,一旦某个第三方仓库没有加进 group 成员,构建就报找不到构件,而报错信息只说在私服上没找到,不会告诉你是被镜像拦掉的。写central覆盖了绝大多数场景,特殊情况还能直连,出问题时排查范围小很多。团队规模大了要求全部走私服,再把成员补全后改成*。

这里还有个常见误解要说清楚:mirror 不影响 deploy。mvn deploy的推送地址来自 pom 里的distributionManagement,不走镜像。想改发布目标只能改 pom 或者用命令行参数覆盖,改 settings 的镜像没用。

4.2 pom.xml 里的 distributionManagement 与 id 对应关系

发布方要在 pom 里声明目标仓库:

<distributionManagement> <repository> <id>nexus-releases</id> <url>http://192.168.1.20:8081/repository/maven-releases/</url> </repository> <snapshotRepository> <id>nexus-snapshots</id> <url>http://192.168.1.20:8081/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement>

<id>是整条链路的钥匙。Maven 推包时按这个 id 去settings.xml的servers里找凭据,找不到就用匿名身份请求,Nexus 返回401。这是发布失败里出现频率最高的原因,账号密码完全正确也没用,因为根本没被读进去。

不想改 pom 的项目(比如第三方维护的模块),用命令行覆盖:

mvn deploy \ -DaltDeploymentRepository=nexus-releases::default::http://192.168.1.20:8081/repository/maven-releases/

格式是id::layout::url,中间那个default是布局类型,别漏。这里的id同样要在settings.xml的servers里存在,否则一样401。

4.3 CI 环境下的凭据注入

本地开发用加密密码没问题,CI runner 是干净环境,没有settings-security.xml,加密串解不开,构建会卡在解析凭据这一步。这时候用环境变量更实际:

<server> <id>nexus-releases</id> <username>${env.NEXUS_USER}</username> <password>${env.NEXUS_PASS}</password> </server>

流水线里注入NEXUS_USER和NEXUS_PASS两个变量,构建脚本用-s指定一份专用 settings:

mvn -s ci-settings.xml clean deploy -DskipTests

本地想用加密方案的话:

# 生成主密码,结果写进 ~/.m2/settings-security.xml mvn --encrypt-master-password "你的主密码串" # 用主密码加密仓库密码,输出放到 settings.xml 的 password 节点里 mvn --encrypt-password "仓库实际密码"

注意:加密密码只有在settings-security.xml存在时才解得开。CI 里要么把这个文件用安全变量还原出来,要么老老实实用环境变量,两条路选一条,别混着用。

5. 避坑排查:Windows 上 Nexus 3 的五个高频翻车现场

5.1 本机能打开 8081,同事一律超时

现象:浏览器在本机访问http://127.0.0.1:8081/正常,换成内网 IP 就转圈,telnet也不通。

原因:etc/nexus-default.properties里的application-host还是默认的127.0.0.1,只绑了回环地址;或者 Windows 防火墙没有放行入站端口。

解决:把application-host改成0.0.0.0,重启服务。防火墙加一条入站规则:

New-NetFirewallRule -DisplayName "Nexus 8081" -Direction Inbound ` -Protocol TCP -LocalPort 8081 -Action Allow

改完用netstat -ano | findstr :8081确认监听是0.0.0.0:8081而不是127.0.0.1:8081。

5.2/start之后进程秒退,日志里找不到 Java

现象:nexus.exe /start返回成功,/status一看没在跑,nexus.log里只有一行Unable to find java之类。

原因:nexus.exe启动子进程时读的是JAVA_HOME,PATH 里那个java它不认。装过 JRE 之后JAVA_HOME没设,或者设成了C:\Program Files\Java\jre...。

解决:把JAVA_HOME指到 JDK 根目录,路径里别带空格:

setx JAVA_HOME "D:\jdk\jdk8" /M

设置完新开一个管理员 CMD,老的窗口环境变量不会刷新。然后先用nexus.exe /run前台确认能起来,再/install装服务。

5.3 部署返回 401,账号密码反复确认没输错

现象:mvn deploy报401 Unauthorized,浏览器用同一个账号能登录后台。

原因:pom 里distributionManagement的repository id跟settings.xml里servers/server/id对不上,Maven 找不到凭据就发匿名请求。第二种可能是账号没有nx-repository-view-maven2-maven-releases-*这组权限。

解决:两边 id 逐字符对齐,大小写和连字符都算。用-X看 Maven 到底带了什么:

mvn -X deploy 2>&1 | findstr /C:"nexus-releases" /C:"401"

确认是权限问题的话,在 Nexus 后台建个角色,给nx-repository-view-maven2-maven-releases-add和nx-repository-view-maven2-maven-releases-read两条,别直接给nx-admin。

5.4 上传 release 报 400,提示仓库不允许更新构件

现象:mvn deploy到maven-releases返回400 Bad Request,报文里带Repository does not allow updating assets。

原因:maven-releases的writePolicy是ALLOW_ONCE,同一个groupId:artifactId:version已经存在,再推一次就被拒。这是刻意设计的防覆盖机制。

解决:正规做法是升版本号,正式版本本来就不该覆盖。真到了需要强制覆盖的场合(比如某个内部包第一次推错了),在后台把maven-releases的写策略临时改成ALLOW,重新推完再改回ALLOW_ONCE。千万别长期留着ALLOW,下游构建的确定性就没了。

5.5 代理仓库一直卡在 Downloading,最后超时

现象:构建日志反复刷Downloading from maven-central,几十秒后失败,改动配置后重试还是同样的包失败。

原因:两种。一是remoteUrl在内网根本不通,DNS 解析不了或者出网策略拦了;二是上一次失败被negativeCache记下来了,timeToLive内直接返回失败,压根没重试。

解决:先绕开 Nexus 直接测远端连通性:

curl.exe -I -m 10 https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.pom

返回200说明出网没问题,那多半是 DNS 或者代理策略。通的话去看nexus.log里带Proxy关键字的行。要快速清缓存,把negativeCache.timeToLive改小(比如1),或者直接重启服务让缓存失效,验证完再调回1440。

6. 上线前的一次快速验证:清理策略、备份口径与四条验收命令

装完不等于能用,交出去之前我会跑一遍固定动作。先给 snapshot 仓库挂清理策略,通过 REST 建:

{ "name": "snapshot-30d", "format": "maven2", "notes": "保留最近 30 天的 snapshot", "criteria": { "lastBlobUpdated": 30, "lastDownloaded": 90 } }
curl.exe -u admin:你的密码 -X POST "http://127.0.0.1:8081/service/rest/v1/cleanup-policies" ^ -H "Content-Type: application/json" --data-binary @snapshot-30d.json

lastBlobUpdated是按最后写入时间算天数,lastDownloaded是按最后被拉取时间算,两个条件同时满足才删。挂完策略再去建一个Admin - Compact blob store的定时任务,跟清理任务错开时间跑,否则删掉的 blob 空间不会立刻还给磁盘,会看到目录体积一直涨。

四条验收命令,按顺序走:

# 1. 匿名拉一个已知构件(前提是开了匿名访问) curl.exe -s -o NUL -w "%{http_code}\n" ^ "http://192.168.1.20:8081/repository/maven-public/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.pom" # 2. 用一个干净本地仓库跑一次拉取,验证不走公网 mvn -B -Dmaven.repo.local=D:\tmp\m2test dependency:resolve -f 某个示例pom.xml # 3. 推一个 SNAPSHOT 上去,确认写权限链路通 mvn -B deploy -DskipTests # 4. 查清理任务是否注册成功 curl.exe -s -u admin:你的密码 "http://127.0.0.1:8081/service/rest/v1/tasks" | findstr /C:"cleanup"

第 2 条最容易被跳过,但它的价值最高——用独立本地仓库跑一遍,能暴露出所有「本机缓存掩盖掉的配置错误」。我见过太多次:开发者本机~/.m2里早就有一堆包,构建从来不走私服,等到换机器或者上 CI 才炸。

备份这块,口径就一句话:sonatype-work\nexus3整个目录,加上etc\nexus-default.properties和bin\nexus.vmoptions两个文件。停服务、复制、启服务,简单粗暴但不会错。热备份要用 blob store 的备份任务,前提是数据量足够大、停服窗口拿不出来,小团队没必要上。

从那以后我每次装完 Nexus 都强制走一遍这四条命令,哪怕只是换台机器重装。有一次图省事跳过了第 2 步,结果上线当天才发现 group 里漏了一个成员仓库,所有用到某个内部工具包的模块全部构建失败,回滚加补配置折腾到半夜。私服这种基础设施,出问题的时候是全团队一起等,验收环节多花二十分钟,比事后救火便宜太多。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询