谁还没在 Ubuntu 18.04 上装 Node.js 踩过坑?系统自带的 apt 源里默认 Node.js 版本老得掉渣,还是 8.x 时代的东西,稍微新一点的项目都跑不起来,Vue 3、Vite 这些工具链更是直接报错说不支持。我这几年在 Ubuntu 18.04 上装过十几次 Node.js,从最简单的 apt 直装到 NodeSource 仓库再到 nvm 方案全折腾过一遍,这里把经验整理出来,希望你能一步到位不踩坑。
先说结论:Ubuntu 18.04 上装高版本 Node.js,核心思路有三种——用 NodeSource 提供的 apt 仓库、用 nvm 做版本管理、直接下载官方二进制包。三种方案各有适用场景,没有绝对的好坏,关键是搞清楚自己的需求再选。要长期维护服务器就用 NodeSource,要本地开发频繁切换版本就上 nvm,一次性部署想省事就直接扒二进制包。
1. 为什么系统自带的 Node.js 不能用
1.1 Ubuntu 18.04 自带源的真实版本
Ubuntu 18.04 LTS 发行于 2018 年 4 月,它的默认软件源里 Node.js 版本停留在 8.10.0。这个版本放到今天来看已经非常老了——不支持 optional chaining(?.)语法,不支持空值合并运算符(??),ES Module 的支持也不完善,很多现代前端构建工具直接跑不起来。
关键是 apt 源里的版本不会主动更新。Ubuntu 的软件源策略是“冻结版本”,只在安全更新时打补丁,不会把 Node.js 升级到新的大版本。这意味着不管你什么时候执行apt install nodejs,拿到的都只会是 8.10.0。
我当时第一次在这系统上跑一个基于 Vite 2 的项目,执行npm install后直接报engines错误,说是需要 Node.js >= 12.2.0,而当前版本是 8.10.0。那一刻我就明白,老系统装新软件,第一件事就是不要用默认源。
1.2 简单升级 apt 源版本为什么不可行
有些人会尝试修改 apt 源指向更新的发行版,比如把源改成 Ubuntu 20.04 的,然后强制安装新版本 Node.js。这个方法在多数情况下会失败,或者说代价太大——不同发行版之间的依赖库版本差异很大,强制升级 Node.js 会连带拉入一堆 glibc、openssl 等底层库的更新,可能导致系统里其他软件直接崩掉。
另一个误区是只装nodejs包而忽略npm。在 Ubuntu 18.04 上,npm 是独立的包,直接apt install nodejs不会装 npm,还得手动apt install npm。但问题是这个 npm 版本也很老(6.x),和现代项目的兼容性同样堪忧。所以正确做法是:Node.js 和 npm 都通过专门维护的仓库或工具来安装,不用系统自带的。
注意:无论用哪种方案,在 Ubuntu 18.04 上装完 Node.js 后,第一件事就是确认
node -v和npm -v的实际输出,别盲目相信安装过程的提示信息。
2. 方案选型:NodeSource、nvm、二进制包怎么选
2.1 三种方案的对比分析
在动手之前,先把三种方案的核心差异理清楚。
| 维度 | NodeSource 仓库 | nvm 版本管理 | 官方二进制包 |
|---|---|---|---|
| 安装方式 | apt 包管理,系统级 | 脚本安装,用户级 | 解压即用,手动配路径 |
| 版本切换 | 不支持,需重装 | 支持,一条命令切换 | 手动改软链接 |
| 适用场景 | 服务器、生产环境 | 本地开发、多项目并行 | 一次性快速部署 |
| 权限要求 | 需要 root | 不需要 root | 需要写 /usr/local |
| Ubuntu 18.04 兼容性 | 需注意 Node 18+ 的 GLIBC 问题 | 完全兼容 | 需注意 GLIBC 版本 |
| 升级方式 | apt upgrade 即可 | nvm install 新版本 | 重新下载解压 |
从表格能看出来,没有完美的方案,只有最合适的场景。
如果你是在服务器上部署应用,追求的是“装一次就不用管”,那 NodeSource 仓库最合适——它把 Node.js 纳入 apt 管理体系,之后apt upgrade就能同步升级,和系统其他软件包的管理方式完全一致。缺点是版本切换不方便,想换版本得先卸了重装。
如果你是在本地开发,衣带几个项目跑的 Node.js 版本还不一样——一个项目要 14,另一个要 18——那 nvm 就是你的救星。它把每个版本的 Node.js 单独放在~/.nvm目录下,用软链接切换当前使用的版本,还能针对每个项目单独锁定版本。缺点是每次打开新终端有时需要手动激活 nvm 环境。
如果你只是想快速装一个能用的 Node.js,不关心版本切换,也不想引入额外的包管理逻辑,那直接下载官方二进制包最省事。解压就能用,删掉目录就卸载,没有任何依赖。
2.2 Ubuntu 18.04 特有的兼容性隐患
这里必须单独提一个 Ubuntu 18.04 的重磅问题:GLIBC 版本太老。
Node.js 从 18 版本开始,官方要求的 GLIBC 最低版本是 2.28,而 Ubuntu 18.04 的 glibc 是 2.27。这意味着 Node.js 18 及更高版本在 Ubuntu 18.04 上不能直接用 NodeSource 或二进制包的方式运行,启动时会报version GLIBC_2.28 not found错误。
NodeSource 针对这个问题有专门的应对——它在仓库里为 Ubuntu 18.04 提供了兼容处理,可以安装 Node 18,但运行起来是否稳定取决于具体功能模块。实际测试下来,Node 18 的基础运行没问题,但某些依赖原生模块的包会出问题。
我的建议是:在 Ubuntu 18.04 上优先选用 Node 16 或 14,这是兼容性和功能之间的最佳平衡点。如果你非要跑 Node 18+,那就得小心了,可以先测试一遍再上生产。
注意:检查 GLIBC 版本的方式是
ldd --version,输出的最后一行就是当前系统的 glibc 版本。务必在装 Node.js 之前确认这一点,别等报错了才回头看。
3. NodeSource 仓库安装 Node.js 高版本的完整步骤
3.1 安装前的环境和依赖准备
先用 SSH 登录服务器或者打开终端,确认你有 sudo 权限。然后执行以下命令更新系统基础包:
sudo apt update sudo apt upgrade -y接着安装一些基础工具,包括curl(等下下载 NodeSource 安装脚本要用):
sudo apt install -y curl build-essentialbuild-essential这个包组包含了 gcc、g++、make 等编译工具链。虽然 NodeSource 安装的是预编译好的二进制,不需要本地编译,但后边装 npm 包时如果涉及到原生模块(比如 node-sass、bcrypt 这类),编译工具链是必不可少的。提前装好能省很多事。
实际上很多人在 Ubuntu 18.04 上遇到的
node-gyp编译报错,都是因为没装build-essential。这是装任何 Node.js 环境之前的默认动作,别跳过。
3.2 添加 NodeSource 仓库并安装指定版本
NodeSource 官方提供了针对不同 Node.js 版本的安装脚本,直接在终端里执行。
以 Node.js 16 为例:
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -这个命令做的事情是:下载 NodeSource 的 apt 仓库配置脚本,然后以 sudo 权限执行。脚本会自动执行以下操作:
- 把 NodeSource 的 GPG 密钥添加到系统,用于验证包的签名。
- 在
/etc/apt/sources.list.d/目录下创建nodesource.list文件,写入 NodeSource 仓库地址。 - 运行
apt update,让新仓库的包列表生效。
脚本跑完后,直接安装:
sudo apt install -y nodejs这个命令会把 Node.js 和 npm 一起装好,不需要单独装 npm。装完检查版本:
node -v npm -v如果node -v输出v16.x.x,说明安装成功。
想要其他版本,把脚本里的版本号换掉就行:
# 安装 Node.js 14 curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash - sudo apt install -y nodejs # 安装 Node.js 18(注意 Ubuntu 18.04 的 GLIBC 风险) curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt install -y nodejs3.3 验证安装并配置 npm 镜像加速
安装完成后,建议做一个基础功能验证。创建一个临时目录,初始化一个 npm 项目,试装一个包:
mkdir ~/node-test && cd ~/node-test npm init -y npm install lodash如果安装成功且没有报错,说明 npm 的下载和安装流程正常。
由于 npm 官方源的服务器在国外,国内环境下下载速度可能较慢甚至超时。我习惯把 registry 切换到国内镜像源:
npm config set registry https://registry.npmmirror.com验证切换结果:
npm config get registry输出应该显示为https://registry.npmmirror.com。
切换镜像源之后,npm install的速度通常能从几分钟降到十几秒。但注意,镜像源同步官方包有一定延迟,极个别新发布的包可能暂时找不到,遇到这种情况把 registry 临时切回官方源就行。
注意:NodeSource 仓库安装的 npm 归属权是 root,如果你用普通用户执行
npm install -g全局安装包,可能会遇到EACCES: permission denied的权限错误。解法有两条:要么sudo npm install -g,要么用 nvm 方案在前面提到的用户级目录下安装。
3.4 NodeSource 方案的卸载和版本切换
想卸载 NodeSource 装的 Node.js,先移除 npm 全局装的包(可选),然后:
sudo apt remove -y nodejs sudo apt purge -y nodejs sudo rm -f /etc/apt/sources.list.d/nodesource.list sudo apt updatepurge会把配置文件也删掉,remove只删程序文件。如果想换版本,先卸载当前版本,再重新执行对应版本的安装脚本。
这个方案的版本切换确实不够优雅,但胜在稳定、统一、好维护,适合生产环境的部署习惯。
4. nvm 方案:灵活切换多版本的实践
4.1 nvm 的工作原理和安装过程
nvm(Node Version Manager)的原理很有意思——它把每个版本的 Node.js 安装在~/.nvm/versions/node/目录下,然后通过修改 PATH 环境变量和软链接来决定当前终端里node命令指向哪个版本。每次切换版本,本质上是更新这几个符号链接。
安装 nvm 很简单,执行官方安装脚本:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash脚本执行完,会在~/.bashrc或~/.zshrc文件末尾追加 nvm 的环境初始化代码。你需要让配置立即生效:
source ~/.bashrc如果你用的是 zsh,那就source ~/.zshrc。执行后验证:
nvm --version输出版本号说明 nvm 已经可用。
注意:如果你在终端里输入
nvm提示command not found,大概率是source步骤没执行,或者 nvm 的环境变量没写入当前 shell 的配置文件中。重新执行source ~/.bashrc一般能解决。
4.2 使用 nvm 安装和切换多个 Node.js 版本
安装特定版本:
nvm install 14.21.3 nvm install 16.20.2 nvm install 18.20.0安装完成后查看已安装的版本:
nvm ls切换版本:
nvm use 16.20.2设置默认版本(新终端打开后自动使用):
nvm alias default 16.20.2针对某个项目锁定版本:
cd ~/my-project nvm use 16.20.2然后在项目根目录执行:
nvm ls # 查看当前版本更专业的做法是在项目根目录创建一个.nvmrc文件,里面写上版本号:
16.20.2之后进入项目目录执行nvm use,nvm 会自动读取.nvmrc并切换到对应版本。配合nvm install(不指定版本时自动读.nvmrc),团队协作时每个成员都能在同样的 Node 版本下开发,避免因为环境不一致导致的奇怪问题。
4.3 nvm 方案的注意事项和坑
nvm 虽然灵活,但有几个坑是实际使用中比较容易踩到的。
第一,nvm 切换版本只对当前终端生效。开了新终端窗口,如果没有显式执行nvm use,默认用的是 alias default 指定的版本。如果你忘了设置 default,新终端里可能是没有 Node.js 可用的。
第二,sudo 下 nvm 失效。因为 nvm 改的是当前用户的 PATH 环境变量,用sudo node执行时,sudo 环境不继承当前用户的 PATH,所以会提示找不到 node。遇到需要 root 权限跑 Node.js 的场景,要么用绝对路径,要么考虑用 NodeSource 方案。
第三,服务器上有其他用户读不到 nvm 装的 Node.js。nvm 装在用户目录下,别的用户自然无法使用。服务器部署时如果用了 CI/CD 工具,工具通常以专用用户身份运行,就得确保那个用户也装了 nvm 并配置好了环境变量。
第四,某些原生模块在不同 Node 版本之间切换后需要重新编译。比如npm rebuild可能需要执行一下,否则模块会报NODE_MODULE_VERSION mismatch的错误。
我之前在一个项目里用 nvm 从 Node 14 切到 Node 16,启动服务时报了Error: The module was compiled against a different Node.js version,就是原生模块没重新编译导致的,执行npm rebuild后就好了。
提示:nvm 方案的最优实践是,日常开发装好一个 LTS 版本作为 default,然后在具体项目里用
.nvmrc锁定版本。这样既不会全局混乱,又能每个项目都精确对应。
5. 官方二进制包方案:最直接的高版本安装方式
5.1 下载和安装流程
如果不想引入 NodeSource 仓库,也不想用 nvm,还有一个最直接的办法——从官方网站下载预编译好的二进制压缩包,解压后配置 PATH 就能用。
先到 Node.js 官网(nodejs.org)找到对应版本的下载链接。以 Node.js 16.20.2 为例,Linux x64 的压缩包文件名是node-v16.20.2-linux-x64.tar.xz。
cd /tmp wget https://nodejs.org/dist/v16.20.2/node-v16.20.2-linux-x64.tar.xz sudo mkdir -p /usr/local/lib/nodejs sudo tar -xJvf node-v16.20.2-linux-x64.tar.xz -C /usr/local/lib/nodejs解压后得到一个目录node-v16.20.2-linux-x64。接下来配置 PATH 环境变量。编辑/etc/profile.d/nodejs.sh(系统级配置,所有用户生效):
sudo vim /etc/profile.d/nodejs.sh写入以下内容:
export NODEJS_HOME=/usr/local/lib/nodejs/node-v16.20.2-linux-x64 export PATH=$NODEJS_HOME/bin:$PATH保存后执行:
source /etc/profile.d/nodejs.sh验证:
node -v npm -v5.2 二进制的版本管理技巧
如果直接用固定路径解压,以后升级版本就得手动删除旧目录、解压新目录、改 PATH 文件,确实麻烦。我习惯用软链接做一层抽象:
先解压,但不用把 PATH 指向具体版本,而是维护一个current软链接:
sudo ln -s /usr/local/lib/nodejs/node-v16.20.2-linux-x64 /usr/local/lib/nodejs/current把/etc/profile.d/nodejs.sh里的路径简化成:
export NODEJS_HOME=/usr/local/lib/nodejs/current export PATH=$NODEJS_HOME/bin:$PATH以后升级版本,解压新版后,先删掉旧软链接,再重新指向新版:
sudo rm -f /usr/local/lib/nodejs/current sudo ln -s /usr/local/lib/nodejs/node-v18.20.0-linux-x64 /usr/local/lib/nodejs/current这样 PATH 不用改动,切换版本只需要换软链接。比起 nvm 的自动切换,这种方式属于“半自动”,但足够简单可靠。
注意:二进制包方案的核心痛点是“卸载容易弄脏系统”。如果解压后不小心删错了目录,或者手动改坏了 PATH 文件,系统的其他命令也可能受影响。操作前先备份
/etc/profile.d/nodejs.sh是个好习惯。
5.3 二进制方案的兼容性判断
二进制包方案受 GLIBC 版本的影响最直接。Node.js 官方网站提供的所有版本压缩包,都是基于某个最低 glibc 版本编译的。Ubuntu 18.04 的 glibc 是 2.27,所以:
- Node.js 12、14、16:均可正常运行。
- Node.js 18:启动时报
GLIBC_2.28 not found,不能直接运行。 - Node.js 20:同样不行,需要系统 glibc 2.28+。
遇到这个问题,两条出路:一是装 Node 16 或更低的版本;二是用一个基于较新 glibc 的容器环境(比如 Docker 容器跑 Ubuntu 20.04/22.04)来运行 Node 18+。
我的实践建议是:如果必须在 Ubuntu 18.04 宿主机上跑 Node 18+,优先考虑 Docker 方案,把 Node.js 环境容器化,就不受宿主系统 glibc 版本的限制了。而且 Docker 方案带来的环境隔离,对部署的稳定性也有明显好处。
6. 常见问题与排查技巧实录
6.1 问题速查表
实际安装过程中遇到的问题,我把能想到的都整理成了一张表,按问题现象分列。
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
node: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version GLIBCXX_3.4.22 not found | 系统 libstdc++ 版本太老 | 升级 libstdc++6:sudo apt install libstdc++6 |
Error: The module was compiled against a different Node.js version | 原生模块与新 Node 版本不匹配 | 执行npm rebuild或删掉 node_modules 重新安装 |
EACCES: permission denied | npm 全局目录权限不足 | 使用sudo npm install -g或用 nvm 方案 |
nvm: command not found | 环境变量未加载 | 执行source ~/.bashrc |
node: error while loading shared libraries: libcrypto.so.1.1 | OpenSSL 版本不匹配 | 回退安装兼容版本,或升级系统 OpenSSL |
GLIBC_2.28 not found | Node 18+ 要求的 glibc 太高 | 换 Node 16 以下,或用 Docker 容器 |
| npm install 速度极慢 | 连接官方源延迟高 | 切换 npm 镜像源 |
6.2 GLIBC 相关问题的深入排查
上面表格里反复出现 GLIBC,值得单独展开说。
首先确认你的系统 glibc 版本:
ldd --version输出第一行会显示版本号。如果是2.27开头,那就是 Ubuntu 18.04 的特征。
当运行某个程序提示GLIBC_X.XX not found时,意思是程序编译时依赖了这个 glibc 版本以上才有的符号,而系统当前的 glibc 缺少这个符号。glibc 是系统级的基础库,不建议也不支持手动单独升级——强升 glibc 是 Linux 系统里著名的“自杀式操作”之一,可能导致几乎所有动态链接的程序都跑不了。
所以面对 GLIBC 问题,正确策略不是“想办法装新 glibc”,而是“让程序跑在它要求的系统环境里”。这个环境要么是老版本 Node.js,要么是更新的操作系统镜像。在实际操作中,后者往往更省事。
6.3 npm 权限问题的两种解法
EACCES: permission denied这个问题在 Ubuntu 18.04 上出现频率很高,因为 NodeSource 安装的 npm 全局目录/usr/lib/node_modules归属于 root,普通用户没有写权限。
解法一:临时提升权限。每次全局安装都用 sudo:
sudo npm install -g some-package这个办法能用,但有两个副作用:一是要记住每次都加 sudo,二是某些包会依赖当前用户目录下的缓存,用 sudo 跑时缓存归属变成 root,容易产生奇怪的权限问题。
解法二:改 npm 的全局目录到用户目录下。先建目录:
mkdir -p ~/.npm-global然后配置 npm:
npm config set prefix '~/.npm-global'再把全局目录加入 PATH,编辑~/.bashrc末尾添加:
export PATH=~/.npm-global/bin:$PATH激活配置:
source ~/.bashrc之后所有全局包的安装都会落在~/.npm-global下,不需要 sudo,也不会有权限问题。这个方法我用过很久,比较省心。
6.4 Ubuntu 18.04 EOL 之后的应对建议
Ubuntu 18.04 在 2023 年已经结束了标准支持期(EOL),这意味着它不再接收常规的安全更新。即便如此,大量生产环境仍然跑在 18.04 上,特别是那些跑着老业务没法随意迁移的系统。
对于这类系统的 Node.js 安装,我的心态是“能不动就不动”——装一个稳定兼容的 Node 版本(比如 16.x 的最后一个版本),然后尽量别随便升级。安全补丁的缺失已经让系统处在一个相对脆弱的状态,再频繁折腾环境,只会增加更多变数。
如果条件允许,还是建议逐步把生产系统迁移到更新的 LTS 版本(比如 Ubuntu 20.04 或 22.04),再用 NodeSource 安装 Node 18 或 20。迁移过程虽然前期麻烦,但换来的是更省心的长期维护体验。
在我实际给某台老服务器部署 Node.js 服务时,最终选了 NodeSource 方案装 Node 16,并在 Docker 容器里跑 Node 18 的新业务模块,两层策略互相补充,才把新旧业务的兼容问题都解决了。
最后再说一个我一直保留的小习惯:不管用哪种方案装完 Node.js,都顺手执行一遍node -v、npm -v、which node三个验证命令。which node的作用是确认当前终端实际使用哪个目录下的 node 可执行文件——有时候 PATH 里存在多个 node,不检查的话,你以为在用新版本,实际上可能无意间调到了旧版本,这才是最隐蔽的坑。