Windows Nexus 3.30私服搭建与Maven/npm对接避坑

发布时间:2026/11/4 17:00:49
Windows Nexus 3.30私服搭建与Maven/npm对接避坑

简介:Nexus Repository Manager 3.30.0-01 的 Windows 64 位安装包,面向需要在本地或企业内网搭建私有 Maven 仓库的 Java 开发者与运维人员,重点解决中央仓库访问慢、依赖缓存分散、组件发布缺少统一入口以及团队权限难管控等问题。压缩包为 zip 格式,体积约 212.01MB;当前文件总数显示为 0,文件类型明细暂未提供,具体内容以解压结果为准,描述中提及的 sonatype-work 与 nexus-3.30.0-01 目录可用于理解运行数据与程序配置的划分。其价值在于可围绕代理仓库、存储库聚合、组件发布、安全控制、构件质量检查、搜索浏览及 SNAPSHOT 版本管理展开实践,也便于与 Jenkins、GitLab CI/CD 等流程衔接,帮助读者搭建较完整的依赖治理与发布链路。已有 1792 人学习或下载,适合希望系统掌握 Nexus 仓库配置、权限规划与生产备份策略的中级 Java 工程人员参考。

1. 版本选型:nexus-3.30.0-01-win64 这个 zip 我为什么没再往上升

内网私服这件事,十次踩坑里有八次栽在版本上。nexus-3.30.0-01-win64 这个包,是我在一台干净的 Windows Server 上反复装过的那一版:不用另外准备数据库,解压加一个 JDK 8 就能跑,8081 端口一亮,整个团队的 Maven、npm 就有了统一的依赖缓存出口。装不好的后果也很直接——构建机每次拉依赖都要走外网,拉一半超时;同一个 jar 在十台机器上传了十遍,磁盘和带宽一起遭殃。这套东西适合的场面很明确:机器是 Windows,团队用 Maven 或 npm,没有专职中间件运维,希望半天之内把私服立起来并验证能对外服务。下面按我给一台干净机器装机的真实顺序往下写,从环境清点讲到装坏了怎么救。

2. 装前清点:JDK 8、端口、磁盘和账号先定死

动手解压之前,有四件事必须先在纸上定下来,否则后面每一步都是返工:JDK 用哪个版本、JAVA_HOME 落到哪、数据目录放在哪个盘、服务用哪个账号跑。这四样里任何一样定错,表现都是「服务起来了但用不了」,比直接启动失败更费时间。

2.1 JDK 版本与 JAVA_HOME 的落点

3.30.0-01 这个版本按 JDK 8 准备最省事。我在机器上固定用 8u202 之后的小版本,路径固定成D:\jdk8u202这种不带空格、不带中文的短路径。别图新鲜上 JDK 17,这个版本的启动脚本和反射调用在 JDK 17 上会被模块系统拦下来,报错信息还特别不直观。

装之前先确认两件事:java -version输出的是 1.8,JAVA_HOME指向的是 JDK 根目录而不是bin也不是jre。

# 确认当前终端拿到的 Java 版本 java -version # 看 JAVA_HOME 落在哪,结尾不能带 \bin,也不能指到 jre 子目录 echo $env:JAVA_HOME # 把 JAVA_HOME 写成系统级变量,服务启动时才读得到 [Environment]::SetEnvironmentVariable("JAVA_HOME","D:\jdk8u202","Machine")

第一行只是确认 PATH 上那个 java 是几,第二行看变量落点,第三行才是关键:Machine表示写进系统级环境变量。这一步的取舍很容易被忽略——Nexus 注册成 Windows 服务之后,是以服务控制管理器拉起来的,读的是系统级变量,不是你在某个终端里set的临时值,也不是当前用户的变量。我见过最典型的翻车就是:命令行里.\nexus.exe /run跑得好好的,一注册服务就起不来,原因就是 JAVA_HOME 只配在了用户变量里。

JAVA_HOME指到jre子目录也会出问题。JDK 8 的目录结构里jre是运行时,缺tools.jar之类的东西,Nexus 启动脚本走的是 JDK 的完整路径,指错就会在启动日志里看到一类很模糊的类加载失败。

2.2 端口、数据目录与磁盘容量

8081 是默认端口,先查一遍有没有被占:

# 看 8081 被谁占了,最后一列是进程 PID netstat -ano | findstr :8081 # 拿到 PID 之后反查是哪个程序 Get-Process -Id <上一步的PID>

netstat -ano的-a是列全部连接,-n是以数字形式显示地址和端口(不做反解),-o是带上 PID。第二行把 PID 换成实际数字。如果占端口的是个不重要的小服务,改 Nexus 端口比停它更划算,改法在第 3 章。

磁盘这边,我的经验值是这样的:元数据库、blob 文件、日志、临时文件全在sonatype-work\nexus3下面,一个中等规模团队(十几个项目、依赖以 Maven 为主)跑一年,这个目录涨到 30~80 GB 很常见,逻辑是 proxy 仓库会把所有被拉过的包缓存一份,hosted 仓库还会存自己发布的东西。所以数据目录必须放在有独立空间的盘上,别跟系统盘挤。

三条硬性要求:路径不带空格、不带中文、不是网络映射盘。网络盘看着省事,实际在服务账号下经常映射不上,或者在高并发写 blob 的时候直接卡住。

2.3 账号与权限:别一路 Administrator 点到底

服务用哪个账号跑,这件事分两种场面。内网测试机图省事,用 LocalSystem 也就算了;只要这台机器还干别的活,我一般单独建一个普通本地账号,比如nexus_svc,把数据目录的 NTFS 权限给足:

# 建一个不登录的本地服务账号 $pwd = ConvertTo-SecureString "换成一串足够长的口令" -AsPlainText -Force New-LocalUser -Name "nexus_svc" -Password $pwd -PasswordNeverExpires # 把数据目录的完全控制权给这个账号 icacls "D:\nexus\sonatype-work" /grant "nexus_svc:(OI)(CI)F" /T

第一段建账号,-PasswordNeverExpires是必须的,否则到期那天服务会毫无征兆地起不来,而且日志里不会有任何提示。第二段(OI)(CI)F的意思是对象继承(OI)、容器继承(CI)、完全控制(F),/T表示对目录下已有文件一并应用。这一步做完再注册服务,服务属性里的「登录」页改成这个账号。

防火墙那条也顺手做掉,否则本机能访问、同事访问不了:

# 放行 8081 入站,只对当前配置文件生效 New-NetFirewallRule -DisplayName "Nexus 8081" -Direction Inbound ` -Protocol TCP -LocalPort 8081 -Action Allow

-Direction Inbound是入站,-Action Allow是放行,-Protocol TCP指定协议。如果这台机在生产网段,别图省事写-RemoteAddress Any,加上-RemoteAddress限一下网段,私服这种东西一旦全网可达,很可能会被扫描器当成暴露面。

3. 解压与首次启动:从 zip 到 8081 控制台亮起来

清点完环境,接下来是真正动手的部分。这一章的目标很单一:让控制台在浏览器里打开。中间会动两个文件——nexus.vmoptions和nexus-default.properties,改完再注册服务,顺序别颠倒。

3.1 目录结构和值得动的那几个参数

解压:

# 解压到 D:\nexus,解压完里面会是 nexus-3.30.0-01 目录 Expand-Archive -Path .\nexus-3.30.0-01-win64.zip -DestinationPath D:\nexus

-DestinationPath指到父目录就行,压缩包内部自带一层版本目录。解压完一定先看一眼目录结构:

路径作用要不要纳入备份
bin\nexus.exe启动器,注册服务也用它不备份,重装覆盖
bin\nexus.vmoptionsJVM 参数、数据目录、日志路径必须备份,改过的都在这里
etc\nexus-default.properties端口、监听地址、上下文路径必须备份
lib\system\运行时代码不备份
sonatype-work\nexus3元数据库、blob、日志、临时文件最重要的,全量备份

nexus.vmoptions是这一章的重点。我这份包里它大概长这样,值得改的是三行:

# 堆和直接内存,两个值给成一样大,避免运行中动态扩缩 -Xms2703M -Xmx2703M # 上传大构件、并发拉包时靠它撑住,一般跟堆同量级 -XX:MaxDirectMemorySize=2703M # 数据目录,服务方式启动时必须写绝对路径,原因见第 5 章 -Dkaraf.data=D:\nexus\sonatype-work\nexus3 -Dkaraf.log=D:\nexus\sonatype-work\nexus3\log -Djava.io.tmpdir=D:\nexus\sonatype-work\nexus3\tmp # 关掉本地控制台,省的没事开个窗口 -Dkaraf.startLocalConsole=false

堆给 2703M 这个数不是我编的,是 Nexus 3.x 长期沿用的推荐值,机器 8 GB 内存可以照抄;如果机器 16 GB 以上、仓库规模大,给 4G 也合理,但要把-XX:MaxDirectMemorySize一起加上去,单改堆不改直接内存,上传大 jar 的时候照样 OOM。-Dkaraf.data这三个路径写成绝对路径,是这份配置里最值钱的一条改动,相对路径在服务模式下会被解析到系统目录去,数据会写到你想不到的地方。

端口改在另一个文件:

# etc/nexus-default.properties application-port=8081 application-host=0.0.0.0 nexus-context-path=/

application-port换端口就改这一行;application-host=0.0.0.0表示所有网卡都监听,写成127.0.0.1就只有本机能访问;nexus-context-path是上下文路径,如果这台机前面要挂个反向代理、或者 8081 上还要跑别的应用,可以改成/nexus,那么访问地址就变成http://ip:8081/nexus/。

提示:这两处改完再启动。先把服务跑起来再去改端口,改完忘了重启,会误以为是端口配置不生效。

3.2 用 /run 前台跑通第一次启动

第一次启动我从来不用服务模式,一定用/run前台跑,因为启动失败的完整堆栈只会在控制台完整刷出来。

cd D:\nexus\nexus-3.30.0-01\bin # /run 前台运行,日志直接打在终端里,Ctrl+C 停 .\nexus.exe /run

/run是前台运行,/start是后台启动,/stop是停,/install、/uninstall是注册和删除服务,/status看状态。前台跑的好处是启动阶段每一行都看得见:Initializing Karaf、Starting Nexus、最后那行Started Sonatype Nexus出现,才说明真起来了。第一次启动比后来慢,一到三分钟很常见,因为在建元数据库和目录结构,别在三十秒的时候就开始怀疑人生。

跑起来之后换台机器验一下:

# 只要状态码,不看页面内容 curl.exe -s -o NUL -w "%{http_code}" http://127.0.0.1:8081/

-s静默不掉进度,-o NUL把正文丢掉,-w打出状态码,200 或 302 都算正常(没登录时多数会跳到登录页)。这一步过了再按 Ctrl+C 停掉,进入服务化。

3.3 注册成 Windows 服务并复查路径

确认前台能跑通,再注册服务:

cd D:\nexus\nexus-3.30.0-01\bin # 注册服务,nexus3 是服务名,不写默认叫 nexus .\nexus.exe /install nexus3 # 启动、看状态 .\nexus.exe /start nexus3 .\nexus.exe /status nexus3

/install后面的名字是 Windows 服务名,起个不会跟别的组件冲突的,我习惯带版本号语义的名字。注册完第一件事不是急着启动,是先去看一眼服务有没有读到你改过的nexus.vmoptions——如果服务启动后 8081 死活连不上,而前台/run一切正常,八成就是路径解析的问题,第 5 章第一条就是在讲这个。

还有一点:注册服务之后再改nexus.vmoptions,必须/stop再/start,光重启服务不够彻底的情况我也遇到过,最稳的是停掉服务、确认进程没了、再起。

4. 仓库规划与客户端对接:maven-public、npm-group 怎么配

控制台能打开只是「装完了」,离「能用」还差一步:仓库规划。这一步配错,最常见的后果是所有依赖都被一个 group 兜住,出了问题不知道是哪个 proxy 在拖后腿,排查起来跟拆黑匣子一样。

4.1 首次登录与 admin 口令

3.x 的初始口令是随机生成的,落在一个文件里:

# 初始口令文件,第一次登录后就会被删除 Get-Content D:\nexus\sonatype-work\nexus3\admin.password

登录http://<ip>:8081/,用户名admin,密码就是文件里那串。进去之后系统会强制改密,改完这个文件会自动消失。

接下来建一个专用发布账号,别拿 admin 去跑 CI。角色给nx-repository-view-maven2-*-*这类够用的权限就行,账号建完顺手在Realms里确认Local authorizing realm是启用的,不然新建的账号登不上。这一段看起来琐碎,但等到构建机要往私服推包的时候,你会庆幸当时没图省事用 admin。

4.2 proxy、hosted、group 三类仓库怎么组合

Nexus 的仓库模型其实就三种,理解这三种,配置就是填空题:

类型干什么典型命名是否可写
proxy代理远端仓库,拉过的缓存到本地maven-central只读
hosted存自己发布的构件maven-releases、maven-snapshots可写
group把上面两类聚成一个入口maven-public只读

maven-central是开箱就有的 proxy 仓库,Remote storage 指向公共中央仓库。内网如果出不去,把这个地址换成内网可达的镜像地址,其它不用动。maven-releases和maven-snapshots也是预置的 hosted 仓库,一个存正式版本,一个存快照,不要混着放,快照会被覆盖写,正式版本混进去之后版本号就不可信了。

maven-public是预置的 group,成员里通常包含上面三个,顺序很关键:把 hosted 的 releases 和 snapshots 放在 proxy 前面。因为 group 是按顺序查找的,自己发布的构件命中 hosted 就不会再往 proxy 走,少一次没必要的远端请求,也避免某些同坐标的包被远端版本顶掉。

npm 那边同理,建npm-proxy、npm-hosted,再建一个npm-group把两个塞进去。PyPI 按需,pypi-proxy加pypi-group。仓库不是建得越多越好,我见过一个私服上一口气建了三十几个仓库,最后没人说得清哪个还在用。

4.3 客户端怎么指过来:settings.xml 和 .npmrc

Maven 客户端这边,改~/.m2/settings.xml:

<settings> <mirrors> <mirror> <!-- id 随便起,但要和下面 servers 里的对应上 --> <id>nexus-public</id> <name>internal nexus group</name> <url>http://192.168.10.20:8081/repository/maven-public/</url> <!-- external:* 只代理外部仓库,本地 install 的构件不会被劫持 --> <mirrorOf>external:*</mirrorOf> </mirror> </mirrors> <servers> <server> <!-- 这里的 id 要和 deploy 时的 repositoryId 一致 --> <id>nexus-releases</id> <username>deployer</username> <password>换成一串应用口令</password> </server> </servers> </settings>

mirrorOf写external:*而不是*,是我踩过坑之后固定下来的写法:*会把所有仓库请求都拦下来,包括你本地install过的、以及一些临时用-DremoteRepositories指定的仓库,调试时会疯掉。external:*只拦外部仓库,语义更准确。servers里的id是给部署命令用的,跟 mirror 的 id 是两套东西,别搞混。

npm 这边就一行:

# 全局指到 npm-group,拉包会依次查 hosted 和 proxy npm config set registry http://192.168.10.20:8081/repository/npm-group/ # 看一眼生效了没 npm config get registry

如果要发私有包,.npmrc里再补一条//192.168.10.20:8081/repository/npm-hosted/:_authToken=xxx,token 从控制台的用户 token 生成页拿。项目里最好用项目级.npmrc,全局改会影响所有项目,别人机器上不一定有。

部署一个构件试试:

mvn deploy:deploy-file \ -DgroupId=com.demo.internal \ -DartifactId=demo-common \ -Dversion=1.0.0 \ -Dpackaging=jar \ -Dfile=demo-common-1.0.0.jar \ -Durl=http://192.168.10.20:8081/repository/maven-releases/ \ -DrepositoryId=nexus-releases

-Durl必须指到具体的 hosted 仓库,不能指 group,group 是只读的。-DrepositoryId要和 settings.xml 里servers的 id 完全一致,否则会报 401,而 Nexus 那边的日志只会记一条认证失败,看不到是谁。

5. 排查与避坑:五条踩出来的记录

这一章的每一条都是现象先出现、然后花掉半天、最后发现原因小得可笑的那种。按出现的阶段排,前面三条属于启动阶段,后面两条属于运行和客户端阶段。

5.1 启动阶段的三条

第一条:/run前台跑得好好的,注册成服务之后 8081 死活连不上。

现象是服务状态显示 Running,nexus.exe /status也说在跑,但浏览器连不上,日志目录里也找不到新写的nexus.log。

原因是nexus.vmoptions里的-Dkaraf.data=../sonatype-work/nexus3是相对路径。前台运行时工作目录是bin,..\sonatype-work正好指到解压目录;注册成服务之后,服务控制管理器给的工作目录是system32,相对路径就解析到别的盘符下,数据写进了那个地方,日志自然也写在那儿。

解决办法就是把-Dkaraf.data、-Dkaraf.log、-Djava.io.tmpdir三个全部改成绝对路径,/stop之后确认进程退干净,再/start。顺手去C:\Windows\System32附近翻一下,多半能找到一个没人认领的sonatype-work目录,删掉就行。

第二条:启动到一半退出,日志里只有一行Unable to access ...或者直接空日志。

现象是服务起不来说明,日志目录是空的,控制台刷几行就没了。

原因通常是 JVM 参数写错或者 JAVA_HOME 指错。nexus.vmoptions里每个-X、-D都必须单独一行,行首不能有空格,不能有注释跟在后面,多一个分号都能让 JVM 直接拒绝启动,而这种拒绝发生在 Nexus 自己的日志系统初始化之前,所以什么都不会留下。

解决办法是逐行比对:把改过的参数先全部注释掉(直接删行),确认能起,再一行一行加回来。还有一种情况是JAVA_HOME指向jre子目录,服务账号下读不到 JDK 的完整路径,表现也是启动即退。

第三条:改了端口,启动报Address already in use,但netstat看着不像被占。

现象是控制台里明确说端口绑定失败,而你用netstat -ano | findstr :8081查,看着没东西。

原因是查法不对:服务在系统启动早期就把端口占了,或者占端口的是 IPv6 上的监听,findstr :8081匹配不到。还有一种更常见的情况是前一次启动没退干净,nexus.exe的 java 子进程还在。

解决办法是先nexus.exe /stop,再开任务管理器确认没有残留的java.exe(以命令行里带nexus的为准),然后netstat -ano | findstr 8081不带冒号再查一遍,配合Get-Process -Id定位真正的占用进程。

5.2 运行与客户端阶段的两条

第四条:上传大构件失败,控制台返回 500,日志里是OutOfMemoryError: Direct buffer memory。

现象是小 jar 传得上去,几十兆以上必失败,重试几次之后整个服务变慢,甚至需要重启。

原因是堆内存和直接内存不同步。-Xmx加到了 4G,-XX:MaxDirectMemorySize还停在 2G,上传走的是 NIO 通道,吃的是直接内存,跟堆不是一回事。

解决办法是两个值一起调,我给的比例是一比一,-Xms4G -Xmx4G -XX:MaxDirectMemorySize=4G,改完重启服务。另外临时文件目录也别放在系统盘,-Djava.io.tmpdir指到数据盘,传大包的时候不会把 C 盘写满。

第五条:客户端mvn deploy报 401,但账号密码在浏览器里能登进控制台。

现象是拉包正常,一旦 deploy 就是 401 Unauthorized,或者 403。

原因有三个层次:一是settings.xml里server的id和命令行-DrepositoryId不一致,凭据根本没送出去;二是账号缺写权限,nx-repository-view-maven2-*-*这类角色名字看着全,实际只是个模板,得逐条勾对;三是密码里带了&、#这类字符,在 XML 里没转义。

解决办法是先对 id,再对权限,最后对特殊字符。我一般直接把settings.xml的密码换成用户 token,token 是纯字母数字,避开了转义这一类问题。

注意:这几条里凡是改过nexus.vmoptions或etc下配置的,都要走「停服务 → 确认进程退出 → 起服务」这个完整流程,只做一次重启有时候不生效,会让人误以为改错了地方。

6. 装完怎么验证:四条检查确认这套私服真的在干活

装完不等于能用,这一步我一般按四条从上往下过,每条都有明确的预期结果,任何一条对不上就回到对应章节找。

# 1. 服务状态 Get-Service nexus3 | Select-Object Name,Status,StartType # 2. REST 健康检查,带认证 curl.exe -s -u admin:你的口令 http://127.0.0.1:8081/service/rest/v1/status # 3. 仓库清单能列出来 curl.exe -s -u admin:你的口令 http://127.0.0.1:8081/service/rest/v1/repositories # 4. 客户端真拉一次 mvn -s settings.xml dependency:get -Dartifact=org.slf4j:slf4j-api:1.7.32

第一条看Status是不是Running、StartType是不是Automatic,两者都对说明服务化没问题。第二条会返回一段 JSON,里面有版本号、healthy字段,healthy 为 true 才算真健康——这比浏览器能打开页面靠谱得多,页面打不开但 REST 通,通常只是前端资源的问题。第三条能列出仓库清单,说明认证和数据访问都通了。第四条是端到端验证:关掉外网(或者把 settings 里的 mirror 指死),再跑一次同样的命令,能成功就说明依赖确实走了私服。

把这几条整理成一张可以贴在交接文档里的表:

验证项命令期望结果
服务自启Get-Service nexus3Running,StartType 为 Automatic
健康状态curl -u admin:pwd .../service/rest/v1/statushealthy: true
仓库可读curl -u admin:pwd .../repository/maven-public/200
离线拉包断外网后mvn dependency:get构建成功,控制台能看到缓存
发布构件mvn deploy:deploy-file ...200,Browse 里能看到构件

还有一个我每次都会顺手做的小检查:登录控制台,点开maven-central的 Browse,看缓存里有没有刚才拉过的那个包,再看看sonatype-work\nexus3\log\request.log有没有对应时间点的记录。这两处一对上,就说明缓存链路是真的在工作,而不是客户端自己走了外网你也毫不知情。

说到这我就想起几年前那次翻车——构建机拉包特别慢,我一直以为是网络问题,查了两天才发现私服的 mirror 配的是*,本地仓库里的东西全被拦下来走了一遍代理,其中还有几个包在 proxy 上反复超时重试。从那以后,我每次装完 Nexus,都会先在干净机器上断网跑一遍mvn dependency:get,再打开request.log亲眼确认请求落在这台私服上,才敢跟团队说「可以切了」。这套流程现在已经是肌肉记忆,希望你也能一次装通,少走那两天弯路。希望帮到你。

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