Linux Tutorials

Install Claude Code on Ubuntu 26.04 LTS (Native, apt, npm)

You want Claude Code on an Ubuntu box without fighting Node versions, sudo npm or a sandbox that refuses to start. Since the CLI now ships as a single native binary, you can install Claude Code on Ubuntu with one curl line and no Node.js at all. The apt repository and npm still exist, and each one behaves a little differently on 26.04, which is what this guide is about.

Original content from computingforgeeks.com - post 167458

We cover all three install paths, then auth, a sane settings.json, a project CLAUDE.md and an MCP server. We also reproduced the /sandbox warning that blames the AppArmor user-namespace restriction in Ubuntu, and tested the fixes that clear it. I ran through all of it again on a fresh Ubuntu 26.04 server VM in October 2026 with Claude Code 2.1.312, and every output below comes from that box.

Prerequisites

Our lab box was a 2 vCPU, 4 GB Proxmox VM with a regular sudo user called kibet. Anthropic lists 4 GB of RAM as the floor, and that held up fine for the CLI plus one MCP server. You need:

  • Ubuntu 26.04 LTS (24.04 and 22.04 take the same installer)
  • A sudo user and at least 4 GB RAM
  • A paid Claude plan (Pro, Max, Team, Enterprise) or a Console API key with billing
  • Outbound HTTPS to claude.ai, platform.claude.com, downloads.claude.ai and api.anthropic.com
  • registry.npmjs.org as well for the npm path and npx MCP servers

You cannot use the free Claude.ai plan here, because it does not include Claude Code, so sort out the plan or the API key before you start. Everything up to the auth step works without one, so you can prepare a box and hand it over to the person who holds the account. That is how the lab run went: every capture before Step 3 came from a VM with no Claude login at all, which shows that neither the installers nor claude doctor need credentials to do their job.

Step 1: Install Claude Code with the native installer

The native installer is the method Anthropic recommends, and it is the one we would pick on any workstation. It drops a self-contained binary under ~/.local/share/claude/versions, points ~/.local/bin/claude at it and keeps it updated in the background. There is no Node.js, no npm and no root involved. Run it as your normal user:

curl -fsSL https://claude.ai/install.sh | bash

On a brand new user the script finishes with a PATH note, because ~/.local/bin did not exist when the shell started:

✔ Claude Code successfully installed!

  Version: 2.1.312

  Location: ~/.local/bin/claude


  Next: Run claude --help to get started

⚠ Setup notes:
  ● Native installation exists but ~/.local/bin is not in your PATH. Run:

    echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc

This is not a broken install. The default ~/.profile on Ubuntu adds ~/.local/bin at login only when the directory already exists, so the shell that ran the installer simply started too early to see it. A new login fixes it by itself, which is why the note is easy to ignore on a desktop. If you want the command in the current shell right now, add the line the installer suggests:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Now confirm the binary runs and ask it to check itself. claude doctor is read-only and does not start a session, so it works before you have signed in:

claude --version
claude doctor

The lines that matter are Running: native, Auto-updates: enabled and the last one. We left out the Remote Control and organization-policy lines that an unsigned box adds, because they only say you have not logged in yet and are not install problems:

2.1.312 (Claude Code)
Claude Code doctor

Running: native (2.1.312)
Commit: f375cd85d4f7
Platform: linux-x64
Path: /home/kibet/.local/share/claude/versions/2.1.312
Config install method: native
Search: OK (bundled)
Auto-updates: enabled
Auto-update channel: latest
Last update attempt: none recorded
No installation issues found.

The screenshot below is the same run on the lab VM, from the install script through to the doctor checkup:

Claude Code native installer, claude --version and claude doctor output on Ubuntu 26.04

From here claude works from any directory and the background updater keeps it on the latest channel. If your updates go through the system package manager instead, Step 2 covers apt and npm.

Step 2: Install Claude Code with apt or npm instead

The two other paths matter on servers and fleets where every package goes through one tool. They install the same CLI, but they do not update the same way, and on our box they did not even land on the same build. This is how the three compared on the same VM on the same morning:

  • Native installer: newest build, updates itself, lives in ~/.local
  • apt stable: a few builds behind, updates with apt
  • npm: newest build, needs Node.js 22+, updates with npm

Our pick depends on the machine. On a laptop or a single dev VM, use the native installer, because it stays current without you thinking about it. On a server managed by Ansible or Landscape, use apt, because the version is pinned by the repo and changes only when you run an upgrade. npm only makes sense if your team already pins tools through a Node toolchain.

Signed apt repository

Anthropic publishes a GPG-signed apt repository, which is the cleanest choice when Ansible or unattended-upgrades already manage your packages. Add the key and the stable repo, then install:

sudo install -d -m 0755 /etc/apt/keyrings
sudo curl -fsSL https://downloads.claude.ai/keys/claude-code.asc -o /etc/apt/keyrings/claude-code.asc
echo "deb [signed-by=/etc/apt/keyrings/claude-code.asc] https://downloads.claude.ai/claude-code/apt/stable stable main" | sudo tee /etc/apt/sources.list.d/claude-code.list
sudo apt update
sudo apt install -y claude-code

The signed-by option means apt will trust anything signed with that key for this one repository, so the key itself deserves a check before the first apt update pulls packages with it. Compare its fingerprint with the value in the Anthropic setup docs:

gpg --show-keys /etc/apt/keyrings/claude-code.asc

Our copy of the key reads 31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE, which matches the published value:

pub   rsa4096 2026-03-30 [SCE]
      31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE
uid                      Anthropic Claude Code Release Signing <[email protected]>

Then check which build apt actually installed. The install log does not say which release you got, and the channel decides it, so this is the only quick way to know whether the box matches the version your team tested:

apt-cache policy claude-code | head -3
/usr/bin/claude --version

On the same morning that the native installer pulled the newest build, the stable channel sat a few releases behind it:

claude-code:
  Installed: 2.1.308-1
  Candidate: 2.1.308-1
2.1.308 (Claude Code)

That gap is by design. The stable channel trails the tip by about a week and skips builds with known regressions, which is what you want on a shared server. If you would rather ride the newest build, use apt/latest latest main in the source line instead. Note that apt installs never auto-update, so claude doctor on this binary reports Auto-updates: Managed by package manager and you upgrade with sudo apt update && sudo apt upgrade claude-code.

npm (for existing Node setups)

The npm package @anthropic-ai/claude-code needs Node.js 22 or newer. The nodejs package in the Ubuntu 26.04 archive is new enough, so there is no need for NodeSource on this release (the Node.js on Ubuntu 26.04 guide covers the other options):

sudo apt install -y nodejs npm
node --version

You should see a 22.x release from the archive build:

v22.22.1

Do not install the package with sudo npm install -g. Anthropic advises against it, and with the npm from the Ubuntu archive the default global prefix is /usr/local, which your user cannot write to. Give npm a prefix in your home directory first, so every global package lives under your user and later upgrades never need root. The PATH line matters as much as the prefix, because without it the shell cannot find the new claude binary:

mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
npm install -g @anthropic-ai/claude-code

npm fetches the same native binary through a per-platform dependency, so the result is the newest build again, and doctor names the method:

added 3 packages in 16s
Running: npm-global (2.1.312)
Path: /home/kibet/.npm-global/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe

Upgrade an npm install with npm install -g @anthropic-ai/claude-code@latest, not npm update -g, which sticks to the original semver range. Pick one method per user and stay with it, because the CLI notices when two of them live side by side (see the errors section).

Step 3: Pick an auth path

These are the auth backends most Ubuntu installs use (the docs also cover Microsoft Foundry). The right one depends on who pays for the tokens:

BackendBest forSetup
Anthropic account (OAuth)Pro, Max and Team subscribersBrowser flow via claude auth login
API keyConsole pay-per-token, CI runnersANTHROPIC_API_KEY env var
Amazon BedrockAWS-hosted Claude billingCLAUDE_CODE_USE_BEDROCK=1 plus AWS creds
Google Vertex AIGCP-hosted Claude billingCLAUDE_CODE_USE_VERTEX=1 plus ADC

Path A: OAuth login (Anthropic account)

Sign in once. The command prints a URL and waits for the code you get back from the browser:

claude auth login

Open the URL on any machine, sign in, and paste the code back into the terminal. The token is stored under ~/.claude and reused on every later launch. claude auth status prints the state as JSON, and on our fresh box it read "loggedIn": false with "authMethod": "none" until the login was done.

Path B: API key (best for servers)

Headless servers and CI runners should never need a browser. Create a key in the Claude Console at platform.claude.com/settings/keys and export it:

echo 'export ANTHROPIC_API_KEY="your-anthropic-api-key-here"' >> ~/.bashrc
source ~/.bashrc

Then start claude interactively once. It finds the key and asks whether to use it, and the highlighted default is No (recommended), so pressing Enter out of habit throws the key away. Move up to Yes first:

  Detected a custom API key in your environment
  Do you want to use this API key?
    Yes
  ❯ No (recommended)
  Enter to confirm · Esc to cancel

Once the key is approved, claude --print "say hello in one sentence" (alias -p) is the quickest auth test. It runs without the TUI, prints the reply to stdout and exits, which is also the building block for scripting Claude into cron jobs and pipelines.

Step 4: Start your first interactive session

Open the REPL inside a project directory, because that directory becomes the workspace Claude may read and edit:

mkdir -p ~/projects/my-app
cd ~/projects/my-app
claude

On the first launch you get a theme picker, a short security note and a folder trust prompt. Read that last one before you press anything. Its default is No, exit, and we closed our own session on the lab box by hitting Enter through the screens. Pick Yes, I trust this folder and the prompt appears:

 ▐▛███▛█   Claude Code v2.1.312
▝▜██████▀  Opus 5.5 · API Usage Billing
 ▝▝   ▝▝   ~/projects/my-app

❯ Try "create a util logging.py that..."

  ⏸ manual mode on · ? for shortcuts · ← for agents

The banner tells you two useful things. The model name shows what this account defaults to (an API key gave us Opus, while subscription seats can default to a different model), and manual mode on means Claude asks before it runs a tool or edits a file. Keep it that way until you have written the permission rules in Step 5. A good first prompt:

What does this repo do? Walk me through the main entry points.

Claude reads the README, the manifest (package.json, pyproject.toml, Cargo.toml) and the file tree, then answers in the terminal. /help lists the slash commands, /model switches models, and Ctrl+C twice exits.

Step 5: Configure settings.json and CLAUDE.md

Two files turn the CLI into an agent you can trust on a real machine. ~/.claude/settings.json holds your global preferences (model, permissions, sandbox, hooks), and CLAUDE.md holds the rules for one project. Create the settings file:

mkdir -p ~/.claude
vim ~/.claude/settings.json

This is the file we ran on the lab box. It pins the sonnet alias, draws a line around what the agent may run, and turns on the bash sandbox:

{
  "model": "sonnet",
  "permissions": {
    "allow": ["Bash(git:*)", "Bash(npm:*)", "Bash(pytest:*)", "Read", "Edit", "Write"],
    "deny": ["Bash(rm -rf:*)"]
  },
  "sandbox": {
    "enabled": true
  }
}

allow lists tool patterns that skip the prompt, and deny lists patterns Claude refuses even if you approve them later. They are prefix matches, so Bash(rm -rf:*) catches rm -rf but not rm -fr. That is why the sandbox block is there too: with sandbox.enabled set, bash commands run inside bubblewrap with filesystem and network limits, which covers what a prefix rule misses. On Linux the sandbox needs two packages:

sudo apt install -y bubblewrap socat

One side effect to know about: the sandbox defaults to auto-allow, so sandboxed Bash commands stop asking for approval even in manual mode, while your deny rules still apply. The panel calls this “Sandbox commands, with auto-allow”, and you can switch it to regular permissions there. Lock the file to your user with chmod 600 ~/.claude/settings.json once hooks or MCP tokens land in it. Run claude doctor after any edit, since it validates the settings file and reports a bad key there instead of failing at session start. Next, type /sandbox in a session. If its Dependencies tab shows an AppArmor message, jump to the sandbox error below; a stock 26.04 server opened straight into the Mode tab for us. Now give the project its own rules:

vim CLAUDE.md

Keep it short and specific. Every line is loaded into context on every session, so a long file costs tokens each time and vague advice gets ignored. Commands, limits and stack facts work best:

# Project context

Stack: FastAPI + Postgres + Redis behind Caddy.
Run tests with pytest -x. Lint with ruff check .
Keep changes under 200 lines per PR. Prefer dataclasses over dicts.

Claude loads this at session start and keeps it in context. The Claude Code hooks guide adds checks that run around every tool call, and the sub-agents guide shows how to fan work out once one session is not enough.

Step 6: Add an MCP server

MCP servers let Claude read live data from your stack: a database, an API, a docs folder. claude mcp add takes a name and then the launch command after a -- separator. The filesystem server is the simplest one to prove the wiring. It runs through npx, so on the native or apt path install Node first with sudo apt install -y nodejs npm, or mcp list will report the server as failed:

claude mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem $HOME/projects
claude mcp list

mcp add writes to the local config for the current project, and mcp list starts each server to check it, so a green Connected means the command really launches:

Added stdio MCP server filesystem with command: npx -y @modelcontextprotocol/server-filesystem /home/kibet/projects to local config
File modified: /home/kibet/.claude.json [project: /home/kibet/projects/my-app]
Checking MCP server health…

filesystem: npx -y @modelcontextprotocol/server-filesystem /home/kibet/projects - ✔ Connected

Because the server was added to the local config, it only loads when you start Claude inside ~/projects/my-app, so another project on the same box does not inherit it. Remove it with claude mcp remove filesystem. For real work, swap it for the Postgres or GitHub servers from the MCP servers setup guide, or package your own tools with the plugins guide.

Step 7: Learn the flags you will use first

Of the dozens of flags, these are the ones we reach for on day one, checked against claude --help on the lab build:

FlagWhat it does
-p, --printPrint the reply and exit (scripts, cron)
-c, --continueContinue the most recent conversation in this directory
--add-dir DIRAllow tool access to more directories
--model opusModel for this session (aliases such as fable, opus, sonnet, or a full name)
--permission-mode MODEmanual, acceptEdits, auto, plan, dontAsk or bypassPermissions
--dangerously-skip-permissionsBypass every permission check; throwaway VMs only
--mcp-config PATHLoad extra MCP servers from a JSON file
--settings FILELoad a settings file or inline JSON for this run
--bareMinimal mode for CI: skips hooks, plugins, auto-memory, CLAUDE.md and MCP servers; Anthropic API key or apiKeyHelper, no subscription login

Combined, they make a cheap one-shot task. This one has a small model read a week of commits and draft a changelog. Note that --bare ignores subscription logins, so it needs ANTHROPIC_API_KEY set in the shell that runs it:

cd ~/projects/site && claude --bare --model haiku --print "review the diff from the last 7 days and write a CHANGELOG entry"

We ran nothing like this from cron in the lab, so treat a scheduled version as your own job to wire up. Cron reads neither ~/.bashrc nor ~/.profile, which means the crontab line has to call the binary by its full path in your home directory (ours was /home/kibet/.local/bin/claude) and set the API key itself, for example by sourcing a 0600 env file. You will find the full slash-command and flag reference in the Claude Code cheat sheet, and the cheat sheets hub has the printable PDF versions.

Common errors and what they mean

The first three errors below came up on the 26.04 lab box during this run, with the exact text the CLI printed. The last one, an expired login, needs a real account, so we took its wording from the official error reference.

Sandbox: “Ubuntu’s AppArmor restriction on user namespaces stops bubblewrap here”

Ubuntu restricts unprivileged user namespaces through AppArmor (kernel.apparmor_restrict_unprivileged_userns = 1), and bubblewrap needs one to build the sandbox. On a stock 26.04 server this is already handled: the apparmor package ships /etc/apparmor.d/bwrap-userns-restrict, which grants /usr/bin/bwrap the userns rule. The /sandbox panel was clean on our first test because that profile was loaded. When it is not loaded (we unloaded it to reproduce the case, and the Claude Code docs say 24.04 and later can hit it), bubblewrap fails and /sandbox shows this on its Dependencies tab:

   ripgrep (rg): found
   bubblewrap (bwrap): installed
   socat: installed
   seccomp filter: installed
   Ubuntu's AppArmor restriction on user namespaces stops bubblewrap here ("loopback: Failed RTM_NEWADDR: Operation not permitted"). Add bubblewrap's
   AppArmor profile from https://code.claude.com/docs/en/sandboxing, or run `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0` (holds until
   reboot), then restart Claude Code

Before touching anything, confirm both halves of the diagnosis, because the fix below only applies to this cause. The sysctl should read 1, the bwrap profiles should be missing from aa-status, and a plain bubblewrap call should fail the same way outside Claude. If you are inside a Docker container rather than on a VM or bare metal, the failure has a different cause, and the sandboxing docs handle that case with enableWeakerNestedSandbox:

sysctl kernel.apparmor_restrict_unprivileged_userns
sudo aa-status | grep -E "^\s+(bwrap|unpriv_bwrap)$"
bwrap --ro-bind / / --unshare-user --dev /dev --proc /proc true

With the profile missing, aa-status prints nothing for bwrap and the last command fails:

kernel.apparmor_restrict_unprivileged_userns = 1
bwrap: setting up uid map: Permission denied

There are three ways out, and we tested all three on the lab VM. In order of preference:

  1. Reload the bwrap profile that Ubuntu ships
  2. Add the bwrap profile from the Claude Code docs
  3. Turn the sysctl off until the next reboot

The first option is the one to use on 26.04, because the file is already on disk and it keeps the restriction on for every other program. It only lets bwrap create the namespace, and it still drops capabilities for the commands bwrap starts:

sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict
sudo aa-status | grep -E "^\s+(bwrap|unpriv_bwrap)$"

Both profiles should now be listed, and after a Claude restart /sandbox opens on the Mode tab with no warning:

   bwrap
   unpriv_bwrap

Our lab VM capture shows the whole sequence, from the blocked bwrap call through the warning to the reloaded profile:

Claude Code /sandbox AppArmor user namespace warning on Ubuntu 26.04 and the bwrap profile fix

If /etc/apparmor.d/bwrap-userns-restrict does not exist on your system, use the profile from the Claude Code sandboxing docs instead. Do not add it on a box that already has the Ubuntu file, because both files define a profile named bwrap and whichever loads last replaces the other. Open the new file:

sudo vim /etc/apparmor.d/bwrap

Paste the profile exactly as we tested it:

abi <abi/4.0>,
include <tunables/global>

profile bwrap /usr/bin/bwrap flags=(unconfined) {
  userns,
  include if exists <local/bwrap>
}

Load it with sudo apparmor_parser -r /etc/apparmor.d/bwrap. Loaded on its own, it cleared the warning on our box too. It is the looser of the two, because the Ubuntu profile moves the commands bwrap starts into a separate stacked profile (unpriv_bwrap) that denies capabilities, while this one leaves bwrap unconfined.

The sysctl route in the message, sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0, also cleared the warning in our test. It is the bigger tradeoff, because it lifts the restriction for every program on the machine rather than just bwrap. It also resets at reboot, so it is fine for a quick test on a throwaway VM and the wrong choice for a workstation you keep.

Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules'

This is what npm install -g @anthropic-ai/claude-code printed on 26.04 with the archive npm and no prefix set:

npm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /usr/local/lib/node_modules
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules'

Your global prefix is /usr/local, which belongs to root. Do not rerun it with sudo. Set the user prefix from Step 2 and install again, or switch to the native installer, which never touches system directories.

Warning: Leftover npm global installation

We had a native install and an npm install for the same user, and both claude update and claude doctor flagged it:

Warning: Multiple installations found
- npm-global at /home/kibet/.npm-global/bin/claude
- native at /home/kibet/.local/bin/claude (currently running)

Warning: Leftover npm global installation at /home/kibet/.npm-global/bin/claude
Fix: Run: npm -g uninstall @anthropic-ai/claude-code

Whichever binary comes first in PATH wins, so you can end up updating one copy and running the other. Run the suggested npm -g uninstall @anthropic-ai/claude-code. Doctor did not flag the apt copy in /usr/bin on the same box, so check which -a claude yourself if you mixed apt with another method.

Login expired or the wrong credential is in use

We had no real login in the lab, so this one comes from the Claude Code error reference rather than our own capture. An expired subscription login shows up as Login expired · Please run /login or an OAuth token message, and a bad key as API Error: 401 Invalid authentication credentials. Run /login (or claude auth login) to renew, and /status to see which credential the session is actually using, because an exported ANTHROPIC_API_KEY you approved in Step 3 takes over from the subscription login.

Update and uninstall Claude Code

A native install updates itself, but you can force a check at any time:

claude update

It prints the running version, checks the channel and ends on the result. When a second install exists, the warnings from the errors section print between these lines:

Current version: 2.1.312
Checking for updates to latest version...
Claude Code is up to date (2.1.312)

apt installs update with sudo apt update && sudo apt upgrade claude-code, and npm installs with npm install -g @anthropic-ai/claude-code@latest. To remove a native install, delete the launcher, the version store and, if you will not come back, your config:

rm -f ~/.local/bin/claude
rm -rf ~/.local/share/claude
rm -rf ~/.claude ~/.claude.json

An apt install comes off with sudo apt remove claude-code plus the list file in /etc/apt/sources.list.d and the keyring. An npm install comes off with npm -g uninstall @anthropic-ai/claude-code. Deleting ~/.claude also removes settings, MCP definitions and session history, so skip that line if you plan to reinstall.

If you also run other coding agents on the same machine, the OpenCode install guide uses the same Ubuntu box layout, and the AppArmor guide for Ubuntu 26.04 explains the profile modes behind the sandbox fix. The token-reduction guide is the next stop once the monthly bill starts to matter.

Keep reading

Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) Ubuntu Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Ubuntu 26.04 vs 24.04 LTS: Should You Upgrade Now? Ubuntu Ubuntu 26.04 vs 24.04 LTS: Should You Upgrade Now? Upgrade a Single Package on Ubuntu / Debian with apt Debian Upgrade a Single Package on Ubuntu / Debian with apt APT Cheat Sheet: apt and apt-get Commands for Ubuntu/Debian Cheat Sheets APT Cheat Sheet: apt and apt-get Commands for Ubuntu/Debian How To Install FreeOffice on Ubuntu 24.04|22.04|20.04 Desktop How To Install FreeOffice on Ubuntu 24.04|22.04|20.04

Leave a Comment

Press ESC to close