在企业 Java 项目中,如果所有开发机和 CI 节点都直接从 Maven Central 下载依赖,会逐渐暴露出以下问题:
外部仓库访问速度和稳定性不可控;
相同依赖在不同机器上被重复下载;
企业内部组件缺少统一发布、版本管理和权限控制;
无法对外部依赖来源进行集中治理;
构建过程过度依赖公网,难以支持内网、弱网或离线环境;
Release、Snapshot 和第三方 JAR 容易混放,制品生命周期混乱。
Maven 私服本质上是企业内部的制品仓库中心 。它既可以代理 Maven Central 等远程仓库,也可以托管企业内部构建产物。本文基于 Rocky Linux 10,使用 Sonatype Nexus Repository 3 搭建 Maven 私服,并分别给出原生部署和 Docker Compose 部署方案。
本文编写时的版本基线为 Rocky Linux 10、Nexus Repository 3.94.1。Nexus 版本更新较快,正式部署前应再次查看官方 Release Notes,并将文中的版本变量替换为经过验证的版本。
一、整体架构 建议将 Maven 私服设计为统一入口,而不是让项目分别连接 Maven Central、私有 Release 仓库和 Snapshot 仓库。
flowchart LR
DEV[开发者 Maven] -->|下载依赖/插件| NGINX[Nginx HTTPS]
CI[CI/CD 节点] -->|下载与发布制品| NGINX
NGINX --> NEXUS[Nexus Repository 3]
NEXUS --> GROUP[maven-public<br/>Group]
GROUP --> RELEASE[maven-releases<br/>Hosted]
GROUP --> SNAPSHOT[maven-snapshots<br/>Hosted]
GROUP --> CENTRAL[maven-central<br/>Proxy]
CENTRAL --> MC[Maven Central]
NEXUS --> BLOB[(Blob Store)]
NEXUS --> DB[(H2 或 PostgreSQL)]
推荐的职责划分如下:
组件
职责
Nginx
HTTPS、域名入口、上传大小限制、反向代理
Nexus Repository
仓库管理、权限控制、代理缓存、制品发布
Maven Group 仓库
为开发者和 CI 提供统一下载地址
Hosted 仓库
保存企业自己生产的 Release 和 Snapshot 制品
Proxy 仓库
代理 Maven Central 等外部仓库
Blob Store
保存 JAR、POM、校验文件等二进制内容
H2/PostgreSQL
保存用户、权限、仓库配置、组件元数据等信息
二、部署方案选择 Nexus Repository 当前支持 H2 和外部 PostgreSQL 两种数据库。
2.1 小型环境 适合个人实验室、少量开发者、临时测试:
单节点 Nexus;
内置 H2;
本地 SSD;
每日备份;
2 核 CPU、8 GB 内存作为较稳妥的起点。
2.2 正式生产环境 适合公司研发团队、CI/CD 高频构建或关键基础设施:
Nexus 使用独立服务账户运行;
外部 PostgreSQL;
Blob Store 与系统盘分离;
Nginx HTTPS;
关闭匿名访问;
独立备份与恢复演练;
对 Nexus、磁盘和数据库进行监控;
使用固定版本,不直接追踪 latest。
Nexus 的性能通常更容易受到磁盘和网络 I/O 限制,而不是单纯受 CPU 限制。堆内存也不是越大越好,Blob 下载依赖 Linux Page Cache,因此必须给操作系统留下足够内存。
三、环境规划 本文使用以下示例参数:
配置项
示例值
操作系统
Rocky Linux 10 x86_64
主机名
nexus01
Nexus 域名
maven.example.com
Nexus 版本
3.94.1-06
安装目录
/opt/sonatype/nexus-3.94.1-06
软链接
/opt/sonatype/nexus
数据目录
/data/nexus-data
Nexus 内部端口
8081
对外端口
443
运行用户
nexus
检查系统信息:
1 2 3 4 cat /etc/rocky-releaseuname -runame -m hostnamectl
设置主机名和时区:
1 2 3 4 5 hostnamectl set-hostname nexus01 timedatectl set-timezone Asia/Shanghai timedatectl chronyc tracking
更新系统并安装基础工具:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 dnf clean all dnf makecache dnf upgrade -y dnf install -y \ curl \ wget \ tar \ gzip \ unzip \ vim \ jq \ lsof \ bind-utils \ policycoreutils-python-utils \ firewalld \ nginx
四、原生方式安装 Nexus Repository 4.1 创建专用用户 不要使用 root 直接运行 Nexus。创建专用系统用户,并为其保留可用 Shell:
1 2 3 4 5 6 useradd \ --system \ --create-home \ --home-dir /opt/sonatype \ --shell /bin/bash \ nexus
确认用户:
1 2 id nexus getent passwd nexus
4.2 创建目录 1 2 3 4 5 6 7 8 9 10 mkdir -p /opt/sonatypemkdir -p /data/nexus-datamkdir -p /data/nexus-backupchown -R nexus:nexus /opt/sonatypechown -R nexus:nexus /data/nexus-datachown -R nexus:nexus /data/nexus-backupchmod 750 /data/nexus-datachmod 750 /data/nexus-backup
生产环境建议为 /data 使用独立 SSD、逻辑卷或云盘,不要让 Blob Store 与 Rocky Linux 根分区争抢空间。
检查文件系统:
1 2 3 4 lsblk -fdf -hTdf -ih findmnt /data
4.3 下载并校验安装包 Nexus 3.94.1 的 Linux x86_64 安装包已内置所需 Java 运行时,不需要额外安装系统 JDK。
1 2 3 4 5 6 7 8 9 10 11 12 13 export NEXUS_VERSION="3.94.1-06" export NEXUS_ARCHIVE="nexus-${NEXUS_VERSION} -linux-x86_64.tar.gz" export NEXUS_DOWNLOAD_URL="https://download.sonatype.com/nexus/3/${NEXUS_ARCHIVE} " cd /tmp curl -fL \ "${NEXUS_DOWNLOAD_URL} " \ -o "${NEXUS_ARCHIVE} " curl -fL \ "${NEXUS_DOWNLOAD_URL} .sha256" \ -o "${NEXUS_ARCHIVE} .sha256"
校验 SHA-256:
1 2 3 4 5 6 7 8 9 EXPECTED_SHA256=$(awk '{print $1}' "${NEXUS_ARCHIVE} .sha256" ) ACTUAL_SHA256=$(sha256sum "${NEXUS_ARCHIVE} " | awk '{print $1}' )if [[ "${EXPECTED_SHA256} " != "${ACTUAL_SHA256} " ]]; then echo "SHA-256 校验失败" exit 1fi echo "SHA-256 校验通过:${ACTUAL_SHA256} "
解压:
1 2 3 4 5 6 7 8 tar -xzf "${NEXUS_ARCHIVE} " -C /opt/sonatypeln -sfn \ "/opt/sonatype/nexus-${NEXUS_VERSION} " \ /opt/sonatype/nexuschown -h nexus:nexus /opt/sonatype/nexuschown -R nexus:nexus "/opt/sonatype/nexus-${NEXUS_VERSION} "
检查目录:
1 2 ls -lah /opt/sonatypels -lah /opt/sonatype/nexus
4.4 配置运行用户 Nexus 3.80 及之后的安装包可能不再默认提供 nexus.rc,因此直接创建:
1 2 3 4 5 6 cat > /opt/sonatype/nexus/bin/nexus.rc <<'EOF' run_as_user="nexus" EOFchown nexus:nexus /opt/sonatype/nexus/bin/nexus.rcchmod 640 /opt/sonatype/nexus/bin/nexus.rc
4.5 修改数据目录 Nexus 的安装目录与数据目录应该分离:
安装目录可以在升级时整体替换;
数据目录必须持久化;
Blob、数据库、配置、日志和临时文件均位于数据目录。
先备份 JVM 配置:
1 2 3 cp \ /opt/sonatype/nexus/bin/nexus.vmoptions \ /opt/sonatype/nexus/bin/nexus.vmoptions.bak
修改 /opt/sonatype/nexus/bin/nexus.vmoptions 中的数据路径:
1 2 3 4 -Dkaraf.data=/data/nexus-data -Dkaraf.log=/data/nexus-data/log -Djava.io.tmpdir=/data/nexus-data/tmp -XX:LogFile=/data/nexus-data/log/jvm.log
可以使用命令替换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sed -i \ 's#^-Dkaraf.data=.*#-Dkaraf.data=/data/nexus-data#' \ /opt/sonatype/nexus/bin/nexus.vmoptions sed -i \ 's#^-Dkaraf.log=.*#-Dkaraf.log=/data/nexus-data/log#' \ /opt/sonatype/nexus/bin/nexus.vmoptions sed -i \ 's#^-Djava.io.tmpdir=.*#-Djava.io.tmpdir=/data/nexus-data/tmp#' \ /opt/sonatype/nexus/bin/nexus.vmoptions sed -i \ 's#^-XX:LogFile=.*#-XX:LogFile=/data/nexus-data/log/jvm.log#' \ /opt/sonatype/nexus/bin/nexus.vmoptions
创建必要目录:
1 2 mkdir -p /data/nexus-data/{log ,tmp,etc}chown -R nexus:nexus /data/nexus-data
/data/nexus-data/tmp 所在文件系统不能以 noexec 方式挂载,否则 Nexus 可能因为本地动态库无法加载而启动失败。
4.6 JVM 内存调优 Nexus 的主要内存不只有 Java Heap,还包括 Direct Memory、Metaspace、线程栈以及 Linux Page Cache。
可以近似理解为:
1 2 3 4 5 6 Nexus 进程内存 ≈ Java Heap + Direct Memory + Metaspace + 线程栈 + JVM Native Memory
对于一台专用于 Nexus、总内存为 8 GB 的服务器,可以从以下配置开始:
1 2 3 -Xms2g -Xmx2g -XX:MaxDirectMemorySize=2g
修改:
1 2 3 4 5 6 7 8 sed -i 's/^-Xms.*/-Xms2g/' \ /opt/sonatype/nexus/bin/nexus.vmoptions sed -i 's/^-Xmx.*/-Xmx2g/' \ /opt/sonatype/nexus/bin/nexus.vmoptions sed -i 's/^-XX:MaxDirectMemorySize=.*/-XX:MaxDirectMemorySize=2g/' \ /opt/sonatype/nexus/bin/nexus.vmoptions
建议起点:
主机内存
Heap
Direct Memory
适用场景
8 GB
2 GB
2 GB
小型团队、低并发
16 GB
4 GB
4 GB
中型团队、CI 构建较频繁
32 GB
8 GB
8 GB
大型实例,需结合监控验证
注意事项:
-Xms 和 -Xmx 保持一致,避免运行期间反复调整堆;
不要把 80% 以上物理内存都分给 Heap;
Direct Memory 也会消耗真实内存;
给 Linux Page Cache、Nginx 和系统服务留出空间;
调优必须根据 GC、请求延迟、磁盘 I/O 和缓存命中率验证,而不是“内存越大越快”。
4.7 配置 Nexus 应用参数 首次启动前创建配置文件:
1 2 3 4 5 6 7 8 9 10 11 mkdir -p /data/nexus-data/etccat > /data/nexus-data/etc/nexus.properties <<'EOF' application-port=8081 nexus-context-path=/ nexus.security.password.algorithm=PBKDF2WithHmacSHA256 nexus.security.secrets.algorithm=PBKDF2WithHmacSHA256 EOFchown -R nexus:nexus /data/nexus-data/etcchmod 640 /data/nexus-data/etc/nexus.properties
建议让 Nexus 只通过 Nginx 对外暴露。若需要进一步限制监听地址,可以通过防火墙仅允许本机或管理网访问 8081。
4.8 配置 systemd 创建 /etc/systemd/system/nexus.service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [Unit] Description =Sonatype Nexus RepositoryDocumentation =https://help.sonatype.com/After =network-on line.targetWants =network-on line.target[Service] Type =forkingUser =nexusGroup =nexusExecStart =/opt/sonatype/nexus/bin/nexus startExecStop =/opt/sonatype/nexus/bin/nexus stopExecReload =/opt/sonatype/nexus/bin/nexus restartRestart =on -failureRestartSec =10 sTimeoutStartSec =600 TimeoutStopSec =600 LimitNOFILE =65536 LimitNPROC =65535 TasksMax =infinityNoNewPrivileges =true PrivateTmp =true [Install] WantedBy =multi-user.target
加载并启动:
1 2 systemctl daemon-reload systemctl enable --now nexus
查看状态:
1 2 3 systemctl status nexus --no-pager -l journalctl -u nexus -n 200 --no-pagertail -f /data/nexus-data/log/nexus.log
检查端口:
1 2 ss -lntp | grep 8081 curl -I http://127.0.0.1:8081/
读取初始管理员密码:
1 cat /data/nexus-data/admin.password
浏览器访问:
首次登录:
1 2 用户名:admin 密码:/data/nexus-data/admin.password 文件中的内容
登录后立即完成以下操作:
修改管理员密码;
配置基础 URL;
根据实际需要关闭匿名访问;
创建日常管理账户;
不要让 CI 使用 admin 账户。
五、配置 Nginx HTTPS 反向代理 5.1 配置 DNS 将域名解析到 Nexus 服务器:
1 maven.example.com -> Nexus 服务器 IP
内网环境也可以使用 dnsmasq、CoreDNS 或企业 DNS。
5.2 创建 Nginx 配置 创建 /etc/nginx/conf.d/nexus.conf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 upstream nexus_backend { server 127.0.0.1:8081 ; keepalive 32 ; }server { listen 80 ; server_name maven.example.com; return 301 https://$host $request_uri ; }server { listen 443 ssl; http2 on ; server_name maven.example.com; ssl_certificate /etc/nginx/ssl/maven.example.com.crt; ssl_certificate_key /etc/nginx/ssl/maven.example.com.key; ssl_protocols TLSv1.2 TLSv1.3 ; ssl_session_cache shared:NEXUS_SSL:20m ; ssl_session_timeout 1d ; client_max_body_size 2g ; client_body_timeout 300s ; location / { proxy_pass http://nexus_backend; proxy_http_version 1 .1 ; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto https; proxy_connect_timeout 30s ; proxy_send_timeout 300s ; proxy_read_timeout 300s ; proxy_request_buffering off ; } }
检查配置:
1 2 3 nginx -t systemctl enable --now nginx systemctl reload nginx
5.3 SELinux 配置 不要为了省事直接关闭 SELinux。允许 Nginx 连接后端端口:
1 setsebool -P httpd_can_network_connect 1
若证书或数据目录使用了自定义路径,应检查并设置正确的 SELinux Context,而不是把整个系统改为 Permissive。
5.4 防火墙 启动 firewalld:
1 systemctl enable --now firewalld
仅开放 SSH、HTTP 和 HTTPS:
1 2 3 4 firewall-cmd --permanent --add-service=ssh firewall-cmd --permanent --add-service=http firewall-cmd --permanent --add-service=https firewall-cmd --reload
确认:
不建议直接向公网开放 8081:
1 2 firewall-cmd --permanent --remove-port=8081/tcp firewall-cmd --reload
六、设计 Maven 仓库 一个可维护的 Maven 私服至少应包含以下仓库:
仓库名
类型
用途
maven-releases
hosted
企业正式版本
maven-snapshots
hosted
企业开发快照版本
maven-central
proxy
代理 Maven Central
maven-public
group
为开发者提供统一下载入口
6.1 Blob Store 规划 建议至少拆分两类 Blob Store:
1 2 maven-hosted-blob maven-proxy-blob
这样做的好处:
自研制品与可重新下载的外部缓存物理隔离;
Proxy 缓存可按策略清理;
恢复时优先保护 Hosted 制品;
更容易统计真实企业制品容量。
推荐目录:
1 2 /data/nexus-blob/maven-hosted /data/nexus-blob/maven-proxy
若使用文件型 Blob Store,先创建目录:
1 2 3 4 5 mkdir -p /data/nexus-blob/maven-hostedmkdir -p /data/nexus-blob/maven-proxychown -R nexus:nexus /data/nexus-blobchmod -R 750 /data/nexus-blob
在 Nexus 管理界面中进入:
1 Settings -> Repository -> Blob Stores -> Create file blob store
6.2 创建 maven-releases 进入:
1 Settings -> Repository -> Repositories -> Create repository
选择:
建议配置:
1 2 3 4 5 6 Name: maven-releases Version policy: Release Layout policy: Strict Content disposition: Inline Deployment policy: Disable redeploy Blob store: maven-hosted-blob
Release 一旦发布,通常不应覆盖。相同 GAV 如果允许反复覆盖,会导致构建结果不可复现。
6.3 创建 maven-snapshots 选择:
建议配置:
1 2 3 4 5 Name: maven-snapshots Version policy: Snapshot Layout policy: Strict Deployment policy: Allow redeploy Blob store: maven-hosted-blob
Snapshot 本身就是开发过程中的可变版本,因此允许重新部署。
6.4 创建 maven-central 选择:
建议配置:
1 2 3 4 5 6 7 Name: maven-central Remote storage: https://repo.maven.apache.org/maven2/ Version policy: Release Layout policy: Strict Blob store: maven-proxy-blob
可根据网络质量配置:
1 2 3 Maximum component age: 1440 分钟 Maximum metadata age: 1440 分钟 Not found cache TTL: 60~1440 分钟
不要盲目将缓存时间调得极短,否则 Nexus 会频繁请求远端仓库,失去代理缓存价值。
6.5 创建 maven-public 选择:
建议配置:
1 2 3 4 5 6 Name: maven-public Blob store: maven-proxy-blob Member repositories: 1. maven-releases 2. maven-snapshots 3. maven-central
Group 仓库地址:
1 https://maven.example.com/repository/maven-public/
下载统一走 Group,发布则直接写入对应 Hosted 仓库。
flowchart TD
A[Maven 下载请求] --> G[maven-public]
G --> R{依次查找}
R --> HR[maven-releases]
R --> HS[maven-snapshots]
R --> P[maven-central]
P -->|本地未命中| C[Maven Central]
C -->|缓存制品| P
P --> G
G --> A
七、创建 Maven 用户和权限 不要让开发者或 CI 使用 admin。
推荐至少创建两个角色:
7.1 只读角色 角色名:
需要的典型权限:
1 2 3 4 5 6 nx-repository-view-maven2-maven-public-browse nx-repository-view-maven2-maven-public-read nx-repository-view-maven2-maven-releases-browse nx-repository-view-maven2-maven-releases-read nx-repository-view-maven2-maven-snapshots-browse nx-repository-view-maven2-maven-snapshots-read
7.2 发布角色 角色名:
在只读权限基础上增加:
1 2 3 4 nx-repository-view-maven2-maven-releases-add nx-repository-view-maven2-maven-releases-edit nx-repository-view-maven2-maven-snapshots-add nx-repository-view-maven2-maven-snapshots-edit
是否授予 delete 权限应谨慎决定。一般 CI 发布账户不需要删除 Release。
创建用户示例:
1 2 maven-reader maven-deployer
生产环境建议:
人员账户与机器账户分离;
CI 使用单独凭证;
定期轮换密码或 Token;
离职人员立即禁用;
管理员只做管理,不参与日常构建。
八、配置 Maven 客户端 8.1 settings.xml 编辑:
完整示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <?xml version="1.0" encoding="UTF-8" ?> <settings xmlns ="http://maven.apache.org/SETTINGS/1.2.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation =" http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd" > <localRepository > ${user.home}/.m2/repository</localRepository > <mirrors > <mirror > <id > nexus-maven-public</id > <name > Company Nexus Maven Group</name > <url > https://maven.example.com/repository/maven-public/</url > <mirrorOf > *</mirrorOf > </mirror > </mirrors > <servers > <server > <id > nexus-maven-public</id > <username > maven-reader</username > <password > ${env.NEXUS_READER_PASSWORD}</password > </server > <server > <id > nexus-maven-releases</id > <username > maven-deployer</username > <password > ${env.NEXUS_DEPLOY_PASSWORD}</password > </server > <server > <id > nexus-maven-snapshots</id > <username > maven-deployer</username > <password > ${env.NEXUS_DEPLOY_PASSWORD}</password > </server > </servers > <profiles > <profile > <id > nexus</id > <repositories > <repository > <id > nexus-maven-public</id > <url > https://maven.example.com/repository/maven-public/</url > <releases > <enabled > true</enabled > <updatePolicy > daily</updatePolicy > <checksumPolicy > fail</checksumPolicy > </releases > <snapshots > <enabled > true</enabled > <updatePolicy > always</updatePolicy > <checksumPolicy > warn</checksumPolicy > </snapshots > </repository > </repositories > <pluginRepositories > <pluginRepository > <id > nexus-maven-public</id > <url > https://maven.example.com/repository/maven-public/</url > <releases > <enabled > true</enabled > </releases > <snapshots > <enabled > true</enabled > </snapshots > </pluginRepository > </pluginRepositories > </profile > </profiles > <activeProfiles > <activeProfile > nexus</activeProfile > </activeProfiles > </settings >
设置环境变量:
1 2 export NEXUS_READER_PASSWORD='替换为只读账户密码' export NEXUS_DEPLOY_PASSWORD='替换为发布账户密码'
CI/CD 中应使用平台 Secret,不要把密码提交到 Git。
检查生效配置:
1 mvn help :effective-settings
8.2 mirrorOf 的选择 常见配置:
含义是所有 Maven 仓库请求都经过私服,适合企业统一治理。
也可以使用:
1 <mirrorOf > *,!special-repository</mirrorOf >
表示除 special-repository 外,其他仓库全部走 Nexus。
不推荐在每个业务项目的 pom.xml 中重复声明 Maven Central 镜像地址。仓库策略应尽可能由企业级 settings.xml 管理。
8.3 Maven 密码加密 生成 Master Password:
1 mvn --encrypt-master-password
将结果写入:
1 ~/.m2/settings-security.xml
示例:
1 2 3 <settingsSecurity > <master > {加密后的Master Password}</master > </settingsSecurity >
再加密 Nexus 密码:
把输出写入 settings.xml 的 <password>。
Maven 密码加密主要用于避免明文直接暴露,并不能替代严格的文件权限、Secret 管理和凭证轮换。
九、配置项目发布 在项目 pom.xml 中增加:
1 2 3 4 5 6 7 8 9 10 11 12 13 <distributionManagement > <repository > <id > nexus-maven-releases</id > <name > Company Maven Releases</name > <url > https://maven.example.com/repository/maven-releases/</url > </repository > <snapshotRepository > <id > nexus-maven-snapshots</id > <name > Company Maven Snapshots</name > <url > https://maven.example.com/repository/maven-snapshots/</url > </snapshotRepository > </distributionManagement >
这里的 <id> 必须与 settings.xml 中 <server><id> 完全一致,否则 Maven 无法找到正确凭证。
9.1 发布 Snapshot 项目版本:
1 <version > 1.0.0-SNAPSHOT</version >
执行:
制品将被上传到:
9.2 发布 Release 项目版本:
1 <version > 1.0.0</version >
执行:
制品将被上传到:
9.3 发布第三方 JAR 对于不是由当前 Maven 项目构建出来的 JAR,可使用 deploy-file:
1 2 3 4 5 6 7 8 mvn org.apache.maven.plugins:maven-deploy-plugin:3.1.4:deploy-file \ -DgroupId=com.example.thirdparty \ -DartifactId=legacy-sdk \ -Dversion=1.0.0 \ -Dpackaging=jar \ -Dfile=legacy-sdk-1.0.0.jar \ -DrepositoryId=nexus-maven-releases \ -Durl=https://maven.example.com/repository/maven-releases/
如果供应方提供了 POM:
1 2 3 4 5 mvn org.apache.maven.plugins:maven-deploy-plugin:3.1.4:deploy-file \ -Dfile=legacy-sdk-1.0.0.jar \ -DpomFile=legacy-sdk-1.0.0.pom \ -DrepositoryId=nexus-maven-releases \ -Durl=https://maven.example.com/repository/maven-releases/
十、验证 Maven 私服 10.1 清理本地测试依赖 1 rm -rf ~/.m2/repository/org/apache/commons/commons-lang3
重新下载:
1 2 mvn -U dependency:get \ -Dartifact=org.apache.commons:commons-lang3:3.17.0
10.2 查看实际请求地址 1 2 3 mvn -X dependency:get \ -Dartifact=org.apache.commons:commons-lang3:3.17.0 \ 2>&1 | grep -E 'Downloading|Downloaded'
输出应指向:
1 https://maven.example.com/repository/maven-public/
10.3 使用 curl 验证 1 2 3 curl -u 'maven-reader:密码' \ -I \ https://maven.example.com/repository/maven-public/
10.4 验证缓存 第一次构建时,Nexus 从 Maven Central 下载依赖并写入 Proxy Blob Store;第二次构建应直接命中本地缓存。
可在 Nexus 中进入:
检查对应组件是否已缓存。
十一、Rocky Linux 10 系统调优 11.1 文件句柄 Nexus 会同时打开大量组件、索引、日志和网络连接,文件句柄过低会导致严重故障。
systemd 中已经配置:
同时设置系统级上限:
1 2 3 4 5 6 cat > /etc/sysctl.d/99-nexus.conf <<'EOF' fs.file-max = 1048576 vm.swappiness = 1 EOF sysctl --system
检查:
1 2 systemctl show nexus -p LimitNOFILEcat /proc/$(pgrep -f 'nexus.*java' | head -n 1)/limits
11.2 磁盘与文件系统 推荐:
使用 SSD 或高性能云盘;
Blob Store 与系统根分区分离;
避免磁盘使用率长期超过 80%;
监控 inode;
保证临时目录至少有 1 GB 可用空间;
不要在 Nexus 运行期间手动移动或修改 Blob 文件;
不要把正在使用的数据目录当作普通文件目录随意 rsync --delete。
检查:
1 2 3 4 df -hT /datadf -ih /data iostat -xz 1 iotop
如果 await 持续偏高、磁盘利用率长期接近 100%,继续增加 CPU 或线程数通常没有意义,应该先解决存储瓶颈。
11.3 不要盲目调大 Jetty 线程池 更多线程并不等于更高吞吐量。
当磁盘、数据库或上游仓库已经达到瓶颈时,提高线程数只会造成:
更多并发 I/O;
更高的内存占用;
更长的请求排队;
更严重的上下文切换;
故障时形成请求雪崩。
除非已经通过线程转储、请求指标和官方建议确认线程池是瓶颈,否则保持默认配置。
11.4 合理设置 Maven 并行构建 Maven 可以使用并行构建:
但私服整体吞吐取决于:
1 2 3 客户端并发 × CI 节点数量 × 单次构建依赖数量
不要在所有 CI 节点同时无限提高并行度。更合理的方式是:
先确保 Nexus 本地缓存命中;
为 CI 节点复用本地 Maven 缓存;
控制流水线并发;
再根据 Nexus、PostgreSQL 和磁盘监控逐步提高并行度。
十二、Nexus 仓库调优 12.1 Hosted 与 Proxy 分离 企业自研制品不可替代,Proxy 缓存可以重新下载。两者应使用不同 Blob Store、不同备份优先级和不同清理策略。
12.2 Release 禁止覆盖 Release 仓库建议:
1 Deployment policy: Disable redeploy
这可以避免同一个版本号在不同时间指向不同二进制内容。
12.3 Snapshot 定期清理 为 Snapshot 创建 Cleanup Policy,例如:
1 2 3 4 名称:maven-snapshot-cleanup 格式:maven2 最后下载时间:30 天 最后更新:30 天
再创建定时任务:
1 Cleanup repositories using their associated policies
清理完成后,可在维护窗口执行:
注意:
Cleanup 是删除逻辑组件;
Compact 才会回收 Blob Store 中可回收的物理空间;
不要在业务高峰期同时运行大量清理、压缩和索引任务。
12.4 Proxy 缓存策略 对于 Maven Central:
Release 元数据变化较少,可适当延长缓存;
Snapshot 元数据变化快,应使用单独的 Snapshot Proxy 或更短缓存;
404 也会进入 Negative Cache;
若某个刚发布版本持续返回 404,可手动 Invalidate cache,不要第一反应就重启 Nexus。
12.5 Group 成员顺序 推荐顺序:
1 2 3 4 maven-releases maven-snapshots maven-central 其他可信 Proxy
企业内部仓库优先,有助于减少不必要的远程访问。
十三、基于 Docker 安装 Nexus Docker 部署分为两种:
单容器快速体验;
Nexus + PostgreSQL 的生产化 Compose 方案。
当前 Nexus 官方系统要求明确建议生产环境使用外部 PostgreSQL。对于容器化正式部署,不应把单容器 H2 当作默认生产架构。
十四、Rocky Linux 10 安装 Docker Engine 卸载可能冲突的软件包:
1 2 3 4 5 6 7 8 9 10 dnf remove -y \ docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine \ podman-docker
安装仓库管理工具:
1 dnf install -y dnf-plugins-core
添加 Docker 官方 RHEL 仓库:
1 2 3 dnf config-manager \ --add-repo \ https://download.docker.com/linux/rhel/docker-ce.repo
安装:
1 2 3 4 5 6 dnf install -y \ docker-ce \ docker-ce-cli \ containerd.io \ docker-buildx-plugin \ docker-compose-plugin
启动:
1 systemctl enable --now docker
验证:
1 2 3 docker version docker compose version docker info
不要开启未加密、未认证的 0.0.0.0:2375 Docker Remote API。它几乎等价于把主机 root 权限暴露到网络。
十五、Docker 单容器快速体验 创建数据目录:
1 2 3 mkdir -p /data/docker/nexus/nexus-datachown -R 200:200 /data/docker/nexus/nexus-datachmod 750 /data/docker/nexus/nexus-data
运行:
1 2 3 4 5 6 7 8 9 10 docker run -d \ --name nexus \ --restart unless-stopped \ --ulimit nofile=65536:65536 \ --memory=6g \ --cpus=4 \ -p 127.0.0.1:8081:8081 \ -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=2g -Djava.util.prefs.userRoot=/nexus-data/javaprefs" \ -v /data/docker/nexus/nexus-data:/nexus-data \ sonatype/nexus3:3.94.1
查看日志:
读取初始密码:
1 docker exec nexus cat /nexus-data/admin.password
该方案适合:
学习;
功能验证;
短期测试;
小型非关键 HomeLab。
正式环境继续使用下面的 PostgreSQL Compose 方案。
十六、Docker Compose + PostgreSQL 部署 16.1 创建目录 1 2 3 4 5 6 mkdir -p /data/docker/nexus/{nexus-data,postgres-data,initdb}cd /data/docker/nexuschown -R 200:200 nexus-datachmod 750 nexus-datachmod 700 postgres-data
16.2 创建数据库初始化脚本 创建 /data/docker/nexus/initdb/01-nexus.sql:
1 2 3 CREATE SCHEMA IF NOT EXISTS nexus AUTHORIZATION nexus;GRANT ALL PRIVILEGES ON SCHEMA nexus TO nexus;CREATE EXTENSION IF NOT EXISTS pg_trgm SCHEMA nexus;
16.3 创建 .env 1 2 3 4 5 6 7 8 cat > /data/docker/nexus/.env <<'EOF' POSTGRES_DB=nexus POSTGRES_USER=nexus POSTGRES_PASSWORD=请替换为高强度数据库密码 NEXUS_DB_PASSWORD=请替换为与POSTGRES_PASSWORD相同的密码 EOFchmod 600 /data/docker/nexus/.env
.env 不能提交到 Git。
16.4 创建 compose.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 services: postgres: image: postgres:17 container_name: nexus-postgres restart: unless-stopped environment: TZ: Asia/Shanghai POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - /data/docker/nexus/postgres-data:/var/lib/postgresql/data - /data/docker/nexus/initdb:/docker-entrypoint-initdb.d:ro healthcheck: test: - CMD-SHELL - pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} interval: 10s timeout: 5s retries: 10 shm_size: 256m networks: - nexus-net nexus: image: sonatype/nexus3:3.94.1 container_name: nexus restart: unless-stopped depends_on: postgres: condition: service_healthy environment: TZ: Asia/Shanghai NEXUS_DATASTORE_NEXUS_JDBCURL: >- jdbc:postgresql://postgres:5432/nexus?currentSchema=nexus&tcpKeepAlive=true&loginTimeout=5&connectTimeout=5&socketTimeout=30 NEXUS_DATASTORE_NEXUS_USERNAME: ${POSTGRES_USER} NEXUS_DATASTORE_NEXUS_PASSWORD: ${NEXUS_DB_PASSWORD} INSTALL4J_ADD_VM_PARAMS: >- -Xms2g -Xmx2g -XX:MaxDirectMemorySize=2g -Djava.util.prefs.userRoot=/nexus-data/javaprefs -Dnetworkaddress.cache.ttl=5 ports: - "127.0.0.1:8081:8081" volumes: - /data/docker/nexus/nexus-data:/nexus-data ulimits: nofile: soft: 65536 hard: 65536 mem_limit: 6g cpus: 4.0 networks: - nexus-net networks: nexus-net: name: nexus-net
YAML 中 JDBC URL 必须整体作为字符串处理,尤其要注意 &。使用折叠块可以降低引号和转义错误概率。
启动:
1 2 3 4 cd /data/docker/nexus docker compose config docker compose pull docker compose up -d
查看:
1 2 3 docker compose ps docker compose logs -f nexus docker compose logs -f postgres
读取管理员密码:
1 2 docker compose exec nexus \ cat /nexus-data/admin.password
验证 PostgreSQL 扩展:
1 2 3 docker compose exec postgres \ psql -U nexus -d nexus \ -c '\dx'
查看连接:
1 2 3 docker compose exec postgres \ psql -U nexus -d nexus \ -c 'select usename, application_name, client_addr, state from pg_stat_activity;'
16.5 Docker 方案接入宿主机 Nginx 由于 Compose 将 Nexus 绑定为:
宿主机 Nginx 可以继续使用前文配置:
1 2 3 4 upstream nexus_backend { server 127.0.0.1:8081 ; keepalive 32 ; }
外部用户只能通过 HTTPS 域名访问,不能绕过 Nginx 直接访问 Nexus。
十七、Docker 部署调优 17.1 固定镜像版本 生产环境使用:
1 image: sonatype/nexus3:3.94.1
不要长期使用:
1 image: sonatype/nexus3:latest
更严格的环境可以固定镜像 Digest,避免同名 Tag 被重新推送后得到不同镜像。
17.2 数据目录权限 官方镜像中的 Nexus 进程使用 UID 200,宿主机挂载目录必须可写:
1 chown -R 200:200 /data/docker/nexus/nexus-data
常见错误:
1 2 3 Permission denied Could not create directory Unable to write admin.password
优先检查:
1 2 namei -om /data/docker/nexus/nexus-datals -ldZ /data/docker/nexus/nexus-data
17.3 SELinux Volume Label Rocky Linux 默认启用 SELinux。若 bind mount 被拒绝,可为目录设置适当 Label,或者在 Compose 中使用 :Z:
1 2 volumes: - /data/docker/nexus/nexus-data:/nexus-data:Z
如果同一个目录需要被多个容器共享,需根据场景评估 :z。不要因为挂载失败就直接关闭 SELinux。
17.4 容器内存 以下配置:
1 2 -Xmx2g -XX:MaxDirectMemorySize=2g
并不表示容器最多只用 2 GB,而是至少可能使用:
1 2 3 4 5 2 GB Heap + 2 GB Direct Memory + Metaspace + 线程栈 + Native Memory
因此给容器设置:
比设置为 3 GB 更合理。
如果容器被 OOM Kill:
1 2 3 4 docker inspect nexus \ --format '{{.State.OOMKilled}}' journalctl -k | grep -i -E 'oom|killed process'
不要只提高 mem_limit,还应查看:
1 2 docker stats nexus docker exec nexus jcmd 1 VM.native_memory summary
17.5 PostgreSQL 连接池 Nexus 默认连接池不应随意调大。
连接池过大可能导致:
PostgreSQL max_connections 耗尽;
更多上下文切换;
活跃查询相互争抢磁盘;
故障恢复时间变长。
只有通过数据库监控确认连接池成为瓶颈后,才考虑配置:
1 2 environment: NEXUS_DATASTORE_NEXUS_ADVANCED: "maximumPoolSize=200"
并同步调整 PostgreSQL max_connections,给管理员、监控和维护连接保留余量。
十八、备份与恢复 备份不能只复制 /nexus-data,也不能只执行 pg_dump。
一个可恢复的 Nexus 至少包含:
1 2 3 4 5 6 1. 数据库 2. Blob Store 3. Nexus 配置 4. 密钥和证书 5. 版本信息 6. Nginx 配置
flowchart LR
N[Nexus 实例] --> C[配置与密钥]
N --> B[Blob Store]
N --> D[H2/PostgreSQL]
C --> BK[备份存储]
B --> BK
D --> BK
BK --> DR[恢复演练]
18.1 原生 H2 环境 在 Nexus 管理界面创建任务:
1 Admin - Backup H2 Database
备份目标目录:
1 /data/nexus-backup/database
此外还需要备份:
1 2 3 /data/nexus-data/blobs /data/nexus-data/etc /data/nexus-data/keystores
不要在数据库和 Blob Store 不一致的时间点随意拼凑备份。
18.2 Docker + PostgreSQL 冷备份示例 创建备份目录:
1 mkdir -p /data/backup/nexus
停止 Nexus 写入:
1 2 cd /data/docker/nexus docker compose stop nexus
备份 PostgreSQL:
1 2 3 4 5 6 docker compose exec -T postgres \ pg_dump \ -U nexus \ -d nexus \ -Fc \ > /data/backup/nexus/nexus-db-$(date +%F-%H%M%S).dump
备份 Nexus 数据目录:
1 2 3 4 5 6 tar \ --xattrs \ --acls \ -czf /data/backup/nexus/nexus-data-$(date +%F-%H%M%S).tar.gz \ -C /data/docker/nexus \ nexus-data
恢复服务:
1 docker compose start nexus
同时备份部署文件:
1 2 3 4 5 tar \ -czf /data/backup/nexus/nexus-compose-$(date +%F-%H%M%S).tar.gz \ -C /data/docker \ nexus/compose.yaml \ nexus/initdb
.env 中包含密码,应加密备份并限制权限。
18.3 恢复原则 恢复前必须确认:
Nexus 版本与备份版本兼容;
数据库与 Blob Store 来自同一备份窗口;
目录所有者和 UID 正确;
密钥、证书、Secrets 文件完整;
域名和 Base URL 正确;
恢复后执行真实 Maven 下载、Snapshot 发布和 Release 发布验证。
只备份不恢复测试,相当于给故障准备了一份心理安慰剂。
十九、升级 Nexus 19.1 原生部署升级 升级流程:
flowchart LR
A[阅读 Release Notes] --> B[备份数据库与 Blob]
B --> C[下载并校验新版本]
C --> D[停止 Nexus]
D --> E[解压到新目录]
E --> F[迁移必要配置]
F --> G[切换软链接]
G --> H[启动并验证]
H --> I{异常?}
I -- 是 --> J[停止并回滚]
I -- 否 --> K[完成升级]
使用软链接的价值:
1 /opt/sonatype/nexus -> /opt/sonatype/nexus-3.x.x
升级时只需要切换链接,不必覆盖旧安装目录。
不要直接把旧版本的整个 etc 或 bin 覆盖到新版本。自定义配置应尽量放在数据目录的 override 文件中。
19.2 Docker 升级 修改:
1 image: sonatype/nexus3:新版本
执行:
1 2 3 docker compose pull nexus docker compose up -d nexus docker compose logs -f nexus
升级前必须:
阅读目标版本 Known Issues;
备份数据库与 Blob;
检查 Java、数据库和镜像架构要求;
在测试环境验证;
保留旧镜像和回滚方案。
二十、常见问题 20.1 Nexus 启动很慢 检查:
1 2 3 4 5 tail -f /data/nexus-data/log/nexus.log iostat -xz 1 free -hdf -hdf -ih
常见原因:
首次初始化数据库;
磁盘 I/O 慢;
内存不足或频繁 Swap;
数据目录权限错误;
/tmp 或 Nexus tmp 挂载了 noexec;
PostgreSQL 无法连接;
DNS 解析慢;
文件句柄不足。
20.2 systemd 显示启动失败 1 2 3 systemctl status nexus -l journalctl -u nexus -b --no-pagersudo -u nexus /opt/sonatype/nexus/bin/nexus run
直接以前台方式运行通常能看到更完整错误。
20.3 Docker 容器反复重启 1 2 3 4 docker inspect nexus \ --format '{{json .State}}' | jq docker logs --tail 300 nexus
重点检查:
/nexus-data 是否归 UID 200 所有;
JVM 参数是否超出容器内存;
PostgreSQL 是否就绪;
JDBC URL 是否被 YAML 错误解析;
SELinux 是否阻止挂载;
文件句柄是否过低。
20.4 Maven 返回 401 检查三处 ID 是否一致:
1 2 3 settings.xml -> server.id pom.xml -> distributionManagement.repository.id deploy-file -> repositoryId
然后检查 Nexus 用户是否具有目标 Hosted 仓库的 add、edit 权限。
20.5 Maven 返回 400 常见原因:
把 Release 发布到 Snapshot 仓库;
把 Snapshot 发布到 Release 仓库;
仓库 Version Policy 不匹配;
Release 已存在且禁止重复部署;
URL 指向 Group 仓库,而不是 Hosted 仓库。
下载可以走 Group,上传必须走 Hosted:
1 2 3 4 5 6 7 8 下载: /repository/maven-public/ 发布 Release: /repository/maven-releases/ 发布 Snapshot: /repository/maven-snapshots/
20.6 下载到旧 Snapshot 执行:
仍未更新时,检查:
Maven 本地缓存;
Nexus Snapshot metadata cache;
Proxy Negative Cache;
项目版本是否真的带 -SNAPSHOT;
CI 是否复用了错误的 .m2 缓存。
20.7 Nginx 返回 413 错误:
1 413 Request Entity Too Large
调大:
1 client_max_body_size 2g ;
然后:
1 2 nginx -t systemctl reload nginx
20.8 Nginx 返回 504 检查:
1 2 3 proxy_connect_timeout 30s ;proxy_send_timeout 300s ;proxy_read_timeout 300s ;
但不要只调大超时时间,还要检查 Nexus、PostgreSQL 和磁盘是否已经出现性能瓶颈。
二十一、生产环境检查清单 操作系统
Nexus
网络与安全
备份
二十二、总结 一个真正可用的 Maven 私服,不只是“把 Nexus 跑起来”,而是要同时解决以下问题:
统一入口 :所有 Maven 下载统一经过 maven-public;
制品分层 :Release、Snapshot、Proxy 仓库职责明确;
权限最小化 :开发者、CI、管理员使用不同账户和角色;
存储可治理 :Hosted 与 Proxy Blob 分离,配置清理和压缩;
部署可维护 :原生安装使用 systemd 和版本软链接,Docker 使用固定镜像;
性能可验证 :围绕磁盘 I/O、内存、数据库和缓存命中率调优;
故障可恢复 :数据库、Blob、配置和密钥形成一致备份;
访问可控 :Nginx HTTPS、SELinux、firewalld 和内部端口隔离。
对于 HomeLab 或小团队,原生 Nexus + H2 可以快速落地;对于正式生产环境,更推荐 Nexus + PostgreSQL + 独立 Blob Store + Nginx HTTPS。不要把私服当成一个“装完就忘”的工具,它已经是 CI/CD 链路中的关键基础设施:一旦它倒下,构建流水线通常也会跟着集体罢工。
参考资料