Dev

Install Node.js 24 LTS on Ubuntu 24.04 / 22.04

Node.js 24 shipped with a handful of changes that actually matter in practice. The permission model graduated from experimental (no more --experimental-permission, just --permission), built-in SQLite works with transactions and aggregates, URLPattern is globally available without an import, and npm 11 installs packages noticeably faster. V8 13.6 brings Float16Array, RegExp.escape(), and Error.isError(). It entered Active LTS as “Krypton” in October 2025 with security patches through April 2028.

Original content from computingforgeeks.com - post 164322

This guide covers three ways to install Node.js 24 on Ubuntu 24.04 and 22.04. We tested every method on a fresh VM, built a working Express 5 API, exercised the built-in test runner with describe blocks and async tests, demonstrated the new permission model and SQLite module, and configured a systemd service for production (including a gotcha with NVM that catches people off guard).

Last verified: March 2026 | Tested on Ubuntu 24.04 LTS with Node.js 24.14.0, npm 11.9.0, V8 13.6.233.17-node.41

What You Need

  • Ubuntu 24.04 LTS or 22.04 LTS with sudo access
  • About 80 MB disk space for Node.js and npm
  • Port 3000/tcp open if running web applications behind a firewall

Check Your Current Node.js Version

Before installing, check whether Node.js is already on the system:

node -v 2>/dev/null || echo "Node.js not installed"
npm -v 2>/dev/null || echo "npm not installed"

Ubuntu 24.04 ships Node.js 22 in its default repositories, and Ubuntu 22.04 ships Node.js 18. Neither includes Node.js 24, so you need one of the install methods below.

Install Node.js 24 on Ubuntu

Three methods are available. Pick one based on your use case: NodeSource for production servers with automatic apt updates, NVM for development machines that need multiple versions, or the binary tarball for air-gapped or custom setups.

Method 1: NodeSource Repository (Recommended for Production)

NodeSource tracks upstream Node.js releases and packages them as .deb files that integrate with apt. Updates arrive within hours of each Node.js release.

Add the NodeSource repository:

curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash -

Install Node.js (npm and npx are bundled):

sudo apt install -y nodejs

Confirm the version:

node -v
npm -v

Output from our Ubuntu 24.04 VM:

v24.14.0
11.9.0

Check the V8 engine, OpenSSL version, and module ABI:

node -e "console.log('Platform:', process.platform, process.arch); console.log('V8:', process.versions.v8); console.log('OpenSSL:', process.versions.openssl); console.log('Modules:', process.versions.modules)"

Real output:

Platform: linux x64
V8: 13.6.233.17-node.41
OpenSSL: 3.5.5
Modules: 137

The module ABI version (137) matters when you upgrade from Node.js 22 (which uses 134), because native addons compiled for 22 won’t load on 24 without a rebuild.

Method 2: NVM (Best for Development)

NVM installs Node.js in your home directory and lets you switch between multiple Node.js versions per project. No sudo required for npm global installs.

Install NVM (auto-detects the latest release from GitHub):

NVM_VER=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d \" -f4)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VER}/install.sh | bash

Reload your shell:

source ~/.bashrc

Install Node.js 24 and set it as the default:

nvm install 24
nvm alias default 24

NVM downloaded and activated v24.14.0 on our test VM:

Downloading and installing node v24.14.0...
Now using node v24.14.0 (npm v11.9.0)
Creating default alias: default -> 24 (-> v24.14.0 *)

You can install multiple versions side by side. Here we have both 24 and 22:

nvm install 22
nvm ls

The version list shows both with their LTS codenames:

       v22.22.1
->     v24.14.0
default -> 24 (-> v24.14.0 *)
lts/jod -> v22.22.1
lts/krypton -> v24.14.0

Switch between them with nvm use 22 or nvm use 24. Each version gets its own isolated set of global npm packages. A package you install globally on Node.js 24 is not visible when you switch to 22.

One thing to keep in mind: NVM installs Node.js in ~/.nvm/versions/, which means only your user account can access it. If you need a systemd service or another user to run Node.js, NVM is the wrong choice. Use NodeSource instead (more on this in the systemd section below).

Method 3: Official Binary Tarball

Download the prebuilt binary directly from nodejs.org. This works on any Linux system regardless of package manager:

NODE_VER=$(curl -s https://nodejs.org/dist/latest-v24.x/ | grep -oP 'node-v\K[0-9.]+' | head -1)
curl -sLO https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz
sudo tar xJf node-v${NODE_VER}-linux-x64.tar.xz -C /usr/local --strip-components=1
rm node-v${NODE_VER}-linux-x64.tar.xz

Verify:

node -v
npm -v

Install Build Tools for Native Modules

Some npm packages (bcrypt, sharp, canvas, sqlite3) compile native C/C++ addons during install. If you see gyp ERR! build error, the fix is the build toolchain:

sudo apt install -y build-essential

Many popular packages now ship prebuilt binaries for common platforms, so you might not hit this. But when you do, build-essential is the answer 100% of the time on Ubuntu.

Build an Express 5 API

Express 5 is now the default when you npm install express (it shipped as 5.0 in late 2024 after years in beta). Let’s build a quick API to confirm everything works end to end.

mkdir -p ~/node-demo && cd ~/node-demo
npm init -y
npm install express

npm pulls in Express 5.2.1 with 22 packages:

added 22 packages, and audited 23 packages in 3s
found 0 vulnerabilities

Create the server file:

vi ~/node-demo/server.js

Add the following:

const express = require("express");
const os = require("os");
const app = express();
const PORT = 3000;

app.get("/", (req, res) => {
  res.json({
    message: "Node.js 24 LTS on Ubuntu 24.04",
    nodeVersion: process.version,
    v8Version: process.versions.v8,
    platform: process.platform,
    arch: process.arch,
    hostname: os.hostname(),
    uptime: Math.round(process.uptime()) + "s",
    memoryUsage: Math.round(process.memoryUsage().rss / 1024 / 1024) + " MB"
  });
});

app.get("/health", (req, res) => {
  res.json({ status: "ok", timestamp: new Date().toISOString() });
});

app.listen(PORT, () => console.log("Server running on port " + PORT));

Start the server and hit both endpoints:

node ~/node-demo/server.js &
curl -s http://localhost:3000/ | python3 -m json.tool

Real JSON response from our test VM:

{
    "message": "Node.js 24 LTS on Ubuntu 24.04",
    "nodeVersion": "v24.14.0",
    "v8Version": "13.6.233.17-node.41",
    "platform": "linux",
    "arch": "x64",
    "hostname": "test-ubuntu-24-computingforgeeks-com",
    "uptime": "2s",
    "memoryUsage": "63 MB"
}

The health endpoint returns a simple status check:

curl -s http://localhost:3000/health | python3 -m json.tool
{
    "status": "ok",
    "timestamp": "2026-03-24T17:55:44.632Z"
}

Stop the background server with kill %1 when you’re done testing.

Use the Built-in Test Runner

Node.js 24 ships a production-ready test runner in node:test. It supports describe/test blocks, async tests, setup/teardown hooks, and multiple reporters without installing anything. For smaller projects, it replaces Jest or Mocha entirely.

Create a test file:

vi ~/node-demo/test.mjs

Add a mix of sync and async tests organized in describe blocks:

import { test, describe } from "node:test";
import assert from "node:assert";

describe("Math operations", () => {
  test("addition", () => {
    assert.strictEqual(1 + 1, 2);
  });

  test("multiplication", () => {
    assert.strictEqual(3 * 7, 21);
  });
});

describe("String operations", () => {
  test("includes", () => {
    assert.ok("Node.js 24 LTS".includes("24"));
  });

  test("template literals", () => {
    const version = 24;
    assert.strictEqual(`Node.js ${version}`, "Node.js 24");
  });
});

describe("Array operations", () => {
  const data = [10, 20, 30, 40, 50];

  test("filter", () => {
    assert.strictEqual(data.filter(n => n > 25).length, 3);
  });

  test("reduce", () => {
    assert.strictEqual(data.reduce((a, b) => a + b, 0), 150);
  });

  test("find", () => {
    assert.strictEqual(data.find(n => n > 35), 40);
  });
});

describe("Async operations", () => {
  test("setTimeout resolves", async () => {
    const result = await new Promise(resolve =>
      setTimeout(() => resolve("done"), 50)
    );
    assert.strictEqual(result, "done");
  });

  test("fetch is available globally", async () => {
    assert.ok(typeof fetch === "function");
  });
});

Run the tests:

node --test test.mjs

All 9 tests across 4 suites pass in about 125ms:

▶ Math operations
  ✔ addition (0.814961ms)
  ✔ multiplication (0.220879ms)
✔ Math operations (2.268367ms)
▶ String operations
  ✔ includes (0.303307ms)
  ✔ template literals (0.2107ms)
✔ String operations (1.618383ms)
▶ Array operations
  ✔ filter (0.265355ms)
  ✔ reduce (0.191031ms)
  ✔ find (0.226293ms)
✔ Array operations (1.052684ms)
▶ Async operations
  ✔ setTimeout resolves (50.389199ms)
  ✔ fetch is available globally (0.293877ms)
✔ Async operations (50.986477ms)
ℹ tests 9
ℹ suites 4
ℹ pass 9
ℹ fail 0
ℹ duration_ms 125.107826

Node.js 24 Features Worth Testing

A few of the new APIs are worth trying hands-on to see how they work in practice.

URLPattern (no import needed)

URLPattern is now a global, similar to URL and URLSearchParams. No import required:

node -e "
const pattern = new URLPattern({ pathname: '/users/:id' });
const result = pattern.exec({ pathname: '/users/42' });
console.log('Match:', result.pathname.groups);
"
Match: { id: '42' }

Permission Model (–permission flag)

The permission model restricts what a Node.js process can access. Without the flag, everything works normally. With --permission, file system and network access are denied unless explicitly allowed:

echo 'const fs = require("fs"); console.log(fs.readFileSync("/etc/hostname", "utf8").trim());' > /tmp/perm-test.js
node /tmp/perm-test.js
node --permission /tmp/perm-test.js

The first run prints the hostname. The second throws an error because file system reads are blocked:

node:fs:440
    return binding.readFileUtf8(path, stringToFlags(options.flag));
                   ^
Error: Access to this API has been restricted

Grant specific access with --allow-fs-read:

node --permission --allow-fs-read=/etc /tmp/perm-test.js

Now the read succeeds because /etc is in the allow list. This is useful for sandboxing untrusted scripts or tightening production deployments.

Built-in SQLite

Node.js 24 includes SQLite through the node:sqlite module. It works with in-memory and file-based databases:

node -e "
const { DatabaseSync } = require('node:sqlite');
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
db.exec(\"INSERT INTO users VALUES (1, 'Alice', '[email protected]')\");
db.exec(\"INSERT INTO users VALUES (2, 'Bob', '[email protected]')\");
const rows = db.prepare('SELECT * FROM users').all();
console.log(JSON.stringify(rows, null, 2));
db.close();
"

Output:

[
  {
    "id": 1,
    "name": "Alice",
    "email": "[email protected]"
  },
  {
    "id": 2,
    "name": "Bob",
    "email": "[email protected]"
  }
]

Note: as of v24.14.0, the SQLite module still prints an ExperimentalWarning. It works, but the API could change before it’s fully stable.

Run Node.js as a systemd Service

For production, configure systemd to manage your Node.js app. This gives you automatic startup on boot, crash recovery, and log management through journald.

Important: if you installed Node.js via NVM, systemd services will fail with “Permission denied” because NVM installs to your home directory (~/.nvm/versions/) and the service user cannot access it. Use NodeSource or the tarball method for production services. We hit this exact error during testing.

Create a dedicated user for the application:

sudo useradd -r -s /sbin/nologin nodeapp

Copy your app to a system directory:

sudo mkdir -p /opt/myapp
sudo cp ~/node-demo/server.js /opt/myapp/
sudo cp -r ~/node-demo/node_modules /opt/myapp/
sudo cp ~/node-demo/package.json /opt/myapp/
sudo chown -R nodeapp:nodeapp /opt/myapp

Create the service unit file:

sudo vi /etc/systemd/system/nodeapp.service

Add the following:

[Unit]
Description=Node.js Express Application
After=network.target

[Service]
Type=simple
User=nodeapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now nodeapp

The service status confirms it’s running:

sudo systemctl status nodeapp
● nodeapp.service - Node.js Express Application
     Loaded: loaded (/etc/systemd/system/nodeapp.service; enabled; preset: enabled)
     Active: active (running)
   Main PID: 10803 (MainThread)
     Memory: 17.9M
        CPU: 135ms
     CGroup: /system.slice/nodeapp.service
             └─10803 /usr/bin/node /opt/myapp/server.js

The Restart=on-failure directive means systemd will automatically restart the app after a 5-second delay if it crashes. View logs with:

sudo journalctl -u nodeapp -f

For more advanced process management with cluster mode, log rotation, and zero-downtime reloads, look at PM2 process manager.

Upgrade from Node.js 22

If you are running Node.js 22 from NodeSource, swap the repository:

sudo apt remove -y nodejs
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash -
sudo apt install -y nodejs

Rebuild native modules in every project:

cd /path/to/your/project
npm rebuild

The NODE_MODULE_VERSION changed from 134 (Node.js 22) to 137 (Node.js 24), so all native addons need recompilation. If you still hit MODULE_NOT_FOUND errors after the rebuild, delete node_modules entirely and reinstall: rm -rf node_modules package-lock.json && npm install.

Node.js 22 vs Node.js 24

ComponentNode.js 22 (Jod)Node.js 24 (Krypton)
StatusMaintenance LTS (until Apr 2027)Active LTS (until Apr 2028)
V8 Engine12.413.6 (Float16Array, RegExp.escape(), Error.isError())
npm10.x11.9 (faster installs, improved security)
Test runnerStable (basic)Stable (describe blocks, global setup/teardown)
Permission model--experimental-permission--permission (promoted from experimental)
using keywordNot availableExplicit resource management
URLPatternRequires importGlobally available
SQLiteExperimentalWorks but still shows ExperimentalWarning
Undici6.x7.x with proxy support via NODE_USE_ENV_PROXY
Express (default)4.x5.x (npm install express now gets Express 5)
NODE_MODULE_VERSION134137

If your project is stable on Node.js 22 and you don’t need these features, staying on 22 through April 2027 is fine. Upgrade when you need npm 11, the V8 13.6 improvements, or when Node.js 22 enters end-of-life.

Install Yarn

Yarn Classic (1.x):

sudo npm install -g yarn
yarn --version

This installs Yarn 1.22.22 globally. For Yarn 4.x (Berry), enable Corepack and set the version inside your project:

sudo corepack enable
cd /path/to/your/project
corepack use yarn@stable

Note the sudo on corepack enable when using NodeSource. Without it, the symlink creation fails with EACCES because /usr/bin requires root.

Uninstall Node.js

NodeSource:

sudo apt remove --purge -y nodejs
sudo rm -f /etc/apt/sources.list.d/nodesource.list
sudo apt update

NVM:

nvm uninstall 24
rm -rf ~/.nvm

Node.js 24 on Ubuntu is straightforward regardless of which install method you pick. For the same guide on Debian 13/12 or Rocky Linux / AlmaLinux, the NodeSource and NVM methods are identical. The official Node.js 24 changelog has the full list of breaking changes if you’re upgrading a complex project.

Related Articles

Apache Configure Varnish Cache 7 on Ubuntu 22.04|20.04|18.04 Debian Install Java 21 LTS (OpenJDK) on Ubuntu 24.04 / Debian 13 Web Hosting Resolve “413 Request Entity Too Large Error” on Nginx / Apache Monitoring How To Install LibreNMS on Ubuntu 22.04|20.04|18.04

Leave a Comment

Press ESC to close