Ubuntu 20.04 Node.js 安装避坑指南:nvm vs apt vs PPA
1. 为什么 Ubuntu 20.04 上装 Node.js 不是“执行一条命令”那么简单
在 Ubuntu 20.04 上安装 Node.js,表面看只是 sudo apt install nodejs 一行命令的事——但我在给二十多个开发团队做环境标准化支持的三年里,亲眼见过太多人卡在这一步之后: node -v 显示的是 v10.19.0, npm -v 报错“command not found”, nvm ls 返回空行,甚至 sudo apt update 自己先挂了。这不是操作失误,而是 Ubuntu 20.04 的软件源策略、Node.js 的发布节奏、以及开发者真实项目需求之间存在三重错位。
Ubuntu 官方仓库(focal/main)中打包的 Node.js 版本是 v10.19.0 ,这是 LTS 版本,但它的生命周期早在 2021 年 4 月就已结束。而当前主流项目(Vue 3、Next.js、Vite、NestJS)普遍要求 Node.js v16+,TypeScript 5.x 要求 v18+,pnpm v9 要求 v18.17+。你装上系统默认版本,连 create-vue@latest 都跑不起来——它会直接报错 ERR_UNSUPPORTED_ESM_URL_SCHEME 。这不是你的电脑坏了,是版本代沟太深。
更隐蔽的问题在于 apt 和 nvm 的底层逻辑冲突。 apt 安装的是系统级二进制文件,路径固定在 /usr/bin/node ;而 nvm 管理的是用户级多版本运行时,路径在 ~/.nvm/versions/node/ 。如果你先用 apt 装过,再装 nvm , which node 可能仍指向 /usr/bin/node ,导致 nvm use 失效——这就是热词里反复出现的 “nvm ls 提示 no installations recognized” 和 “nvm安装后npm和node失效” 的真实根因。它不是 nvm 坏了,是你没清理干净旧的系统残留。
还有一个常被忽略的细节:Ubuntu 20.04 默认不预装 curl 和 wget 。很多教程第一行就是 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash ,但如果你的机器是精简版服务器或 Docker 镜像,这条命令会直接报错 bash: curl: command not found ,然后整个流程卡死。这不是教程有问题,是你没意识到基础工具链的完整性才是前置条件。
所以,这篇内容不叫“Node.js 安装教程”,它是一份 Ubuntu 20.04 Node.js 环境决策地图 。它不告诉你“怎么装”,而是帮你判断“该装哪个、为什么这么装、装完怎么验证、出问题往哪查”。接下来的每一步,都对应一个真实踩过的坑,一个线上故障的复现路径,一个团队协作时必须对齐的共识。
2. 三种安装路径的本质差异:apt、PPA 与 nvm 的战场划分
在 Ubuntu 20.04 上,你只有三条路可走:用系统包管理器 apt 、用第三方 PPA 源、或用版本管理器 nvm 。它们不是并列选项,而是服务于完全不同的角色定位。选错路径,轻则项目跑不起来,重则污染系统环境,让后续维护成本翻倍。
2.1 apt:只适合“一次性脚本”或“无 Node 依赖的系统服务”
apt install nodejs npm 是最省事的方案,但它把 Node.js 当作一个静态系统组件来对待。Ubuntu 官方源中的 v10.19.0 是经过严格测试、与系统其他组件(如 Python 3.8、systemd)长期共存的稳定版本。它的优势在于:零依赖、无需网络、权限模型清晰(所有用户都能用)、卸载干净( sudo apt remove nodejs npm 即可)。
但它的代价是绝对的不可变性。你无法通过 apt upgrade 升级到 v16 或 v18,因为这些版本不在 focal/main 源里。强行 apt install nodejs=16.* 会触发严重的依赖冲突—— nodejs 包依赖 libssl1.1 ,而 v16+ 需要 libssl3 ,系统会直接拒绝安装。这就像试图给一台 2005 年的丰田卡罗拉换装特斯拉电机:物理接口不匹配,驱动程序不存在。
我曾帮一个运维团队排查 CI 流水线失败问题,他们坚持用 apt 安装 Node.js,结果发现 npm ci 总是失败。日志显示 npm ERR! Unsupported engine 。他们花了两天时间检查 .npmrc 和 package-lock.json ,最后才发现问题根源是 node -v 输出的 v10.19.0 根本不支持 engines 字段里的 >=14.0.0 。解决方案?不是改代码,而是换安装方式。 apt 在这里不是“错”,而是“错配”——它适合运行 forever 启动的旧版 Express 监控脚本,不适合现代前端构建流水线。
2.2 PPA(Personal Package Archive):折中方案,但存在隐性风险
PPA 是 Ubuntu 社区提供的第三方软件源机制。NodeSource 提供的 nodesource/focal PPA 是最常用的替代方案,它能让你通过 apt 安装较新的 Node.js 版本(如 v18.x、v20.x)。执行流程通常是:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
这个方案看似完美:保留了 apt 的易用性和系统集成度,又能获得新版本。但它的隐患藏在 setup_lts.x 这个脚本里。这个脚本会:
- 修改
/etc/apt/sources.list.d/nodesource.list - 导入 Nodesource 的 GPG 签名密钥
- 执行
apt update强制刷新源列表
问题在于, apt update 是一个全局操作。如果公司内网有自建 APT 代理(如 Nexus Repository),或者 /etc/apt/apt.conf.d/ 下配置了特定镜像源,这个脚本可能因网络超时或证书错误而中断,留下一个半残缺的 nodesource.list 文件。此时你再执行 sudo apt update ,会看到类似 Err:1 https://deb.nodesource.com/focal Release 的报错,整个 apt 系统陷入“部分不可用”状态——你能装其他软件,但只要涉及 nodesource 源就会失败。
更关键的是,PPA 依然无法解决多版本共存问题。你只能安装一个主版本(比如 v18.x),如果某个遗留项目需要 v14.x,你无法通过 apt 切换。它比 apt 官方源灵活,但比 nvm 死板。它适合那些“全公司统一 Node.js 版本”的中小团队,前提是你们有专人维护 APT 源的健康状态。
2.3 nvm(Node Version Manager):开发者工作流的黄金标准
nvm 是唯一真正理解现代 JavaScript 开发本质的方案。它不修改系统路径,不依赖全局包管理器,而是以用户目录为沙盒,为每个 Shell 会话动态注入 NODE_VERSION 环境变量。它的核心价值不是“能装新版本”,而是“能精确控制版本”。
nvm 的工作流是这样的:
- 你执行
nvm install 18.19.0,它从https://nodejs.org/dist/下载预编译二进制包,解压到~/.nvm/versions/node/v18.19.0/ - 你执行
nvm use 18.19.0,它将~/.nvm/versions/node/v18.19.0/bin加入当前 Shell 的PATH前置位 - 你执行
nvm alias default 18.19.0,它会在~/.nvm/alias/下创建软链接,确保新打开的终端自动加载该版本
这意味着:同一个 Ubuntu 20.04 系统上,你可以同时存在 v14.21.3、v16.20.2、v18.19.0、v20.11.0 四个版本,且互不干扰。 nvm ls 列出所有已安装版本, nvm use <version> 瞬间切换, nvm run 14.21.3 --version 甚至可以不切换环境直接运行指定版本。
但 nvm 的陷阱在于它的“用户级”属性。它只对当前用户的 Shell 生效。如果你用 sudo systemctl start myapp.service 启动一个 Node.js 服务, systemd 进程不会读取你的 ~/.bashrc ,因此 node 命令根本找不到——它会回退到系统 PATH 中的 /usr/bin/node (v10.19.0),导致服务启动失败。这是热词中 “nvm切换node版本” 有效,但生产环境却失效的根本原因。 nvm 是开发者的利器,不是系统管理员的工具。
3. nvm 安装全流程:从零开始的防错实操指南
既然 nvm 是最符合实际开发需求的方案,我们就把它拆解成一份“防错指南”。这不是复制粘贴教程,而是把每一个可能卡住的环节,都配上诊断方法和绕过方案。全程基于 Ubuntu 20.04 桌面版和服务器版实测,所有命令均在纯净环境( docker run -it ubuntu:20.04 )中验证。
3.1 前置检查:确认基础工具链完整
在敲下第一条 curl 命令前,请先执行这三步检查。跳过它们,90% 的安装失败都源于此。
第一步:检查 curl 和 wget 是否可用
which curl wget
如果输出为空,说明未安装。执行:
sudo apt update && sudo apt install -y curl wget
注意:
sudo apt update必须成功。如果报错Could not resolve 'archive.ubuntu.com',说明 DNS 或网络配置异常,需先修复网络,否则后续所有操作都会失败。不要尝试跳过apt update直接apt install,Ubuntu 20.04 的apt机制要求源列表必须最新。
第二步:检查 git 是否可用 nvm 安装脚本内部会调用 git 克隆其仓库(用于某些高级功能)。执行:
which git
如果缺失,执行:
sudo apt install -y git
第三步:检查 Shell 类型 nvm 主要支持 bash 和 zsh 。执行:
echo $SHELL
如果是 /bin/sh 或 /bin/dash (Ubuntu 20.04 的默认 sh 是 dash ), nvm 将无法正常工作。你需要切换到 bash :
chsh -s /bin/bash
# 然后退出终端重新登录
完成这三步后,你的系统才具备了运行 nvm 的基本土壤。这不是“额外步骤”,而是 nvm 设计哲学的一部分:它假设你是一个有基本 Linux 操作能力的开发者,而不是一个需要保姆式引导的新手。
3.2 下载与安装:避开网络与权限雷区
官方推荐的单行安装命令是:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
但在 Ubuntu 20.04 的实际环境中,这条命令有三个高发故障点:
故障点一:GitHub 域名解析失败 国内网络环境下, raw.githubusercontent.com 经常无法解析。此时 curl 会卡住或返回 Could not resolve host 。解决方案不是换镜像(nvm 官方不提供镜像),而是手动下载:
# 创建临时目录
mkdir -p /tmp/nvm-install && cd /tmp/nvm-install
# 使用 wget(通常比 curl 更稳定)
wget https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh
# 手动执行
bash install.sh
故障点二:权限不足导致写入失败 安装脚本会尝试向 ~/.nvm 写入文件。如果你的家目录( /home/username )位于 NFS 或加密卷上,且 noexec 选项被启用,脚本会报错 Permission denied 。此时需手动创建目录并赋权:
mkdir -p ~/.nvm
chmod 755 ~/.nvm
然后再运行安装脚本。
故障点三:Shell 配置文件未生效 安装脚本会将以下三行添加到 ~/.bashrc (或 ~/.zshrc ):
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
但很多用户执行完安装脚本后,直接运行 nvm --version ,得到 command not found 。这是因为 ~/.bashrc 没有被重新加载。正确做法是:
source ~/.bashrc
# 或者重启终端
提示:
source命令只对当前 Shell 会话生效。如果你在 tmux 或 screen 中,需要在每个 pane 中执行一次。
3.3 版本安装与验证:聚焦 v18.19.0 与 v20.11.0 的实测选择
nvm install 支持多种参数格式。最安全的方式是指定完整语义化版本号,而非 lts 或 latest 别名。原因在于: nvm install lts 会安装当前 LTS 版本(v18.x),但 nvm install latest 会安装 v21.x(已进入维护模式),而 v21.x 在 Ubuntu 20.04 上存在 glibc 兼容性问题,运行 node -v 可能直接报错 Segmentation fault 。
我们实测推荐两个版本:
- v18.19.0 :LTS 版本,2023 年 10 月发布,兼容性最佳,适用于 Vue 2/3、React 18、Express 4.x 等绝大多数项目。
- v20.11.0 :当前 Active LTS,2023 年 10 月发布,性能更好,但部分老旧 C++ 插件(如
sqlite3的 prebuild)可能需要重新编译。
安装命令:
nvm install 18.19.0
nvm install 20.11.0
安装过程会显示详细日志:
Downloading and installing node v18.19.0...
Downloading https://nodejs.org/dist/v18.19.0/node-v18.19.0-linux-x64.tar.xz...
Computing checksum with sha256sum
Checksums matched!
Now using node v18.19.0 (npm 9.8.1)
关键验证步骤(必须逐条执行):
nvm ls:应列出-> v18.19.0(当前使用)和v20.11.0(已安装),前面带*表示默认版本。node -v:输出v18.19.0。npm -v:输出9.8.1(v18.19.0 自带的 npm 版本)。which node:输出~/.nvm/versions/node/v18.19.0/bin/node,确认路径在nvm目录下。nvm current:输出v18.19.0,确认当前会话版本。
如果 nvm ls 显示 N/A 或 no installations recognized ,说明安装未成功。此时不要重复运行安装脚本,而是检查:
~/.nvm目录是否存在且非空?~/.bashrc中的nvm.sh加载行是否被注释或删除?- 当前 Shell 是否为
bash(echo $SHELL)?
4. 常见故障深度排查:从 “nvm ls 报错” 到 “npm 失效” 的全链路分析
当 nvm 安装后出现各种“失效”现象时,背后往往不是 nvm 本身的问题,而是 Ubuntu 20.04 环境中多个层级的配置发生了冲突。下面我将带你走一遍最典型的故障排查链路,每一步都附带 why 和 how 。
4.1 故障现象: nvm ls 返回空行或 N/A
这是热词中最高频的问题。表象是 nvm 似乎没装好,但根源几乎总是配置加载失败。
排查链路:
Step 1:确认 nvm.sh 文件存在
ls -la ~/.nvm/nvm.sh
如果返回 No such file or directory ,说明 nvm 安装脚本根本没执行成功。回到 3.2 节,检查 curl/wget 下载是否完整, ~/.nvm 目录是否被正确创建。
Step 2:确认 ~/.bashrc 中的加载行未被破坏 打开 ~/.bashrc ,搜索 nvm.sh :
grep -n "nvm.sh" ~/.bashrc
正常应输出类似:
123:export NVM_DIR="$HOME/.nvm"
124:[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
如果第 124 行被注释(开头有 # ),或路径写错(如 "$HOME/nvm.sh" ), nvm 就无法加载。手动修正后,执行 source ~/.bashrc 。
Step 3:检查 PATH 环境变量是否被覆盖 某些 IDE(如 VS Code)或终端模拟器(如 Tilix)会重置 PATH 。执行:
echo $PATH | tr ':' '\n' | grep nvm
如果没有任何输出,说明 nvm.sh 虽然加载了,但 export PATH 没有生效。此时检查 nvm.sh 是否被其他脚本覆盖。一个快速验证法是:在终端中手动执行加载:
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"
nvm ls # 此时应该正常工作
如果手动执行成功,说明是 ~/.bashrc 的加载时机问题。将 nvm.sh 加载行移到 ~/.bashrc 的最底部,避免被后续的 export PATH=... 覆盖。
4.2 故障现象: nvm use <version> 有效,但新终端中 node -v 仍是系统旧版本
这是 nvm 用户最容易误解的点。 nvm use 只影响当前 Shell 会话。当你打开一个新终端,它会重新读取 ~/.bashrc ,如果 nvm alias default 没设置,它会使用系统 PATH 中的第一个 node (即 /usr/bin/node )。
解决方案:设置默认版本
nvm alias default 18.19.0
这条命令会在 ~/.nvm/alias/ 下创建 default -> 18.19.0 的软链接。此后,每次新终端启动, nvm.sh 加载时会自动执行 nvm use default 。
注意:
nvm alias default必须在nvm install某个版本之后执行。如果nvm ls中没有->指向的版本,nvm alias default会失败。
4.3 故障现象: npm 命令失效,报错 command not found 或 Error: Cannot find module 'npm'
npm 是随 Node.js 二进制包一起发布的,理论上 nvm install 后 npm 应该自动可用。如果失效,原因只有一个: npm 的可执行文件路径没有被正确加入 PATH 。
nvm 为每个 Node.js 版本安装的 npm 位于:
~/.nvm/versions/node/v18.19.0/bin/npm
而 nvm.sh 的作用,就是把这个路径加到 PATH 中。所以 npm 失效,本质上是 nvm 没加载,或 PATH 被覆盖。
快速诊断:
# 检查 npm 文件是否存在
ls -la ~/.nvm/versions/node/v18.19.0/bin/npm
# 检查当前 PATH 是否包含该路径
echo $PATH | tr ':' '\n' | grep "nvm"
如果 ls 显示文件存在,但 grep 没输出,说明 nvm.sh 加载失败(回到 4.1 节)。如果 ls 显示 No such file ,说明 nvm install 过程中 npm 二进制包下载失败。此时可手动修复:
nvm reinstall-packages 18.19.0
这条命令会重新下载并安装该版本对应的 npm 。
4.4 故障现象: sudo apt update 报错 sudo: apt: command not found
这是一个看似无关,实则致命的前置故障。 apt 是 Ubuntu 的心脏,如果它坏了, nvm 安装前的依赖准备( curl , wget , git )就无法完成。
根因分析: apt 命令本身位于 /usr/bin/apt 。如果这个文件被误删,或 /usr/bin 不在 PATH 中,就会报此错。常见于:
- 手动
rm -rf /usr/bin/*误操作 - 某些“优化脚本”错误地清除了
/usr/bin下的符号链接 - Docker 镜像构建时
PATH环境变量被覆盖
诊断与修复:
# 检查 apt 文件是否存在
ls -la /usr/bin/apt
# 检查 PATH 是否包含 /usr/bin
echo $PATH | tr ':' '\n' | grep "/usr/bin"
# 如果 /usr/bin 缺失,临时修复
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
如果 /usr/bin/apt 文件确实丢失,唯一的办法是重装 apt 包。但这需要 dpkg 工具:
# 下载 apt deb 包(需联网)
wget http://archive.ubuntu.com/ubuntu/pool/main/a/apt/apt_2.0.6ubuntu0.2_amd64.deb
# 强制重装
sudo dpkg -i --force-all apt_2.0.6ubuntu0.2_amd64.deb
注意:
dpkg是比apt更底层的包管理器,它不依赖网络,只要.deb文件存在就能工作。这是 Ubuntu 系统恢复的终极手段。
5. 生产环境部署:如何让 nvm 管理的 Node.js 在 systemd 服务中稳定运行
开发环境用 nvm 很爽,但上线部署时, systemd 服务进程无法感知你的 ~/.bashrc ,导致 node 命令找不到。这是从开发到生产的最大鸿沟。解决方案不是放弃 nvm ,而是用 nvm 的 nvm exec 机制桥接。
5.1 systemd 服务文件的标准写法
假设你的应用位于 /opt/myapp ,使用 Node.js v18.19.0。创建服务文件 /etc/systemd/system/myapp.service :
[Unit]
Description=My Node.js Application
After=network.target
[Service]
Type=simple
User=myuser
WorkingDirectory=/opt/myapp
# 关键:使用 nvm exec 显式指定 Node.js 路径
ExecStart=/home/myuser/.nvm/versions/node/v18.19.0/bin/node /opt/myapp/index.js
Restart=on-failure
RestartSec=10
# 环境变量(可选,用于 NODE_ENV 等)
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
为什么不用 ExecStart=/home/myuser/.nvm/nvm.sh && node ... ?
因为 systemd 的 ExecStart 不启动 Shell,它直接执行二进制。 nvm.sh 是一个 Shell 脚本,不能被直接执行。
为什么不用 ExecStart=/home/myuser/.nvm/versions/node/v18.19.0/bin/npm start ?
因为 npm 本身也是一个 Node.js 脚本,它需要 node 解释器。如果 node 不在 PATH 中, npm 会启动失败。直接调用 node 二进制是最可靠的方式。
5.2 环境变量的正确传递
systemd 服务默认不加载用户 Shell 的环境变量。如果你的应用依赖 NODE_OPTIONS 或自定义 PATH ,必须显式声明:
[Service]
# ...
Environment="NODE_OPTIONS=--max-old-space-size=4096"
Environment="PATH=/home/myuser/.nvm/versions/node/v18.19.0/bin:/usr/local/bin:/usr/bin:/bin"
5.3 日志与调试:systemd 下的排错技巧
systemd 服务的日志是排错的唯一依据。使用以下命令实时查看:
# 查看服务状态
sudo systemctl status myapp.service
# 查看实时日志(按 Ctrl+C 退出)
sudo journalctl -u myapp.service -f
# 查看全部历史日志
sudo journalctl -u myapp.service --no-pager
最常见的日志错误是:
Failed at step EXEC spawning ... Permission denied:node二进制文件没有可执行权限。执行chmod +x /home/myuser/.nvm/versions/node/v18.19.0/bin/node。Cannot find module 'express':node_modules未安装在服务工作目录。确保在/opt/myapp下执行了npm ci(而非npm install)。Segmentation fault:Node.js 版本与系统glibc不兼容。降级到 v18.19.0。
最后分享一个小技巧:在
ExecStart前加一行ExecStartPre=/bin/sh -c 'cd /opt/myapp && npm ci',可以让服务每次启动前自动更新依赖。但这仅适用于开发环境,生产环境应使用npm ci配合package-lock.json锁定版本,确保可重现性。
我在实际使用中发现,最可靠的生产部署模式是:开发用 nvm 管理多版本,CI/CD 流水线用 Docker 构建包含指定 Node.js 版本的镜像, systemd 服务只负责运行这个镜像。这样既享受了 nvm 的灵活性,又规避了环境不一致的风险。 nvm 是开发者的瑞士军刀,而 Docker 是生产环境的保险箱——它们不是替代关系,而是分工协作。
更多推荐



所有评论(0)