Dahao's Personal Website

Installing the latest R on Ubuntu

Verified working procedure, distilled from the shell history on this machine (Ubuntu 24.04.4 LTS "noble", amd64) and cross-checked against the official CRAN instructions at https://cloud.r-project.org/bin/linux/ubuntu/.

Result on this machine: R 4.6.1 "Happy Hop" (2026-06-24), the current upstream release.


Why not just apt install r-base?

Ubuntu's own universe repository ships whatever R version was current when the Ubuntu release was frozen, and it is never updated for the life of that release. On noble that means R 4.3.x — years behind. To get the latest R you must add CRAN's own apt repository, which is what everything below is about.


1. Prerequisites

sudo apt update -qq
sudo apt install --no-install-recommends software-properties-common dirmngr

dirmngr is needed for key retrieval; software-properties-common provides add-apt-repository. --no-install-recommends keeps this from pulling in a large desktop dependency tree.

2. Add the CRAN signing key

CRAN's Ubuntu builds are signed by Michael Rutter. Fetch the key and convert it to binary keyring format with gpg --dearmor:

wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc \
  | sudo gpg --dearmor -o /etc/apt/keyrings/cran_ubuntu_key.gpg

Verify you got the real key before trusting it:

gpg --show-keys --with-fingerprint /etc/apt/keyrings/cran_ubuntu_key.gpg

Expected fingerprint (confirmed against CRAN's published value):

E298 A3A8 25C0 D65D FD57  CBB6 5171 6619 E084 DAB9
uid   Michael Rutter <marutter@gmail.com>

If the fingerprint does not match, stop — do not add the repository.

Note on key location. This machine currently has the key at /etc/apt/trusted.gpg.d/cran_ubuntu_key.gpg. That works, but anything in trusted.gpg.d is trusted for every repository on the system. The /etc/apt/keyrings/ + signed-by= approach used here scopes the key to CRAN only and is the current Debian/Ubuntu recommendation. See Migrating an existing install to switch.

3. Add the CRAN repository

Pin the key to this repo with signed-by=:

echo "deb [signed-by=/etc/apt/keyrings/cran_ubuntu_key.gpg] https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/" \
  | sudo tee /etc/apt/sources.list.d/cran.list

$(lsb_release -cs) expands to your Ubuntu codename (noble on 24.04). The -cran40 suffix means "R 4.x series".

Using a regional mirror

Any CRAN mirror works — substitute its base URL, keeping the /bin/linux/ubuntu path. This machine uses the Tsinghua mirror, which is much faster from mainland China:

echo "deb [signed-by=/etc/apt/keyrings/cran_ubuntu_key.gpg] https://mirrors.tuna.tsinghua.edu.cn/CRAN/bin/linux/ubuntu $(lsb_release -cs)-cran40/" \
  | sudo tee /etc/apt/sources.list.d/cran.list

The signing key is the same for all mirrors — mirrors copy CRAN's signed packages verbatim. Pick a mirror near you from https://cran.r-project.org/mirrors.html. For Australia, try https://cran.csiro.au or https://mirror.aarnet.edu.au/pub/CRAN.

4. Install R

sudo apt update
sudo apt install --no-install-recommends r-base r-base-dev
Package What it gives you
r-base Metapackage: pulls in r-base-core plus the recommended packages
r-base-core The R and Rscript binaries and the base library
r-base-dev Headers and toolchain needed to compile packages from source — required for most CRAN installs
r-recommended MASS, Matrix, survival, mgcv, lattice, etc.

r-base-dev is not optional in practice: without it, install.packages() fails on any package with C/C++/Fortran source.

5. Verify

R --version
which -a R Rscript

Expect /usr/bin/R and a version line like R version 4.6.1 (2026-06-24) -- "Happy Hop".


Upgrading an existing install

Once the CRAN repo is configured, upgrades are ordinary apt upgrades:

sudo apt update
sudo apt install --only-upgrade r-base r-base-core r-base-dev

--only-upgrade only touches packages that are already installed. It is the right flag for upgrading, and the wrong one for a fresh machine — there, it silently does nothing. Use plain apt install the first time.

Two things worth knowing about apt-managed R:

Migrating an existing install

To move from the legacy trusted.gpg.d key to a scoped signed-by keyring:

# Install the key in the scoped location (verify the fingerprint per step 2)
wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc \
  | sudo gpg --dearmor -o /etc/apt/keyrings/cran_ubuntu_key.gpg

# Rewrite the repo line to reference it
echo "deb [signed-by=/etc/apt/keyrings/cran_ubuntu_key.gpg] https://mirrors.tuna.tsinghua.edu.cn/CRAN/bin/linux/ubuntu $(lsb_release -cs)-cran40/" \
  | sudo tee /etc/apt/sources.list.d/cran.list

# Drop the globally-trusted copy
sudo rm -f /etc/apt/trusted.gpg.d/cran_ubuntu_key.gpg

sudo apt update

If apt update reports no signature errors, the migration worked.

Removing a broken repository

If you added a repo with a wrong URL, remove both the key and the list file, then refresh:

sudo rm -f /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc
sudo rm -f /etc/apt/sources.list.d/cran.list
sudo apt update

add-apt-repository names its file after the URL, so a repo added that way may land somewhere like /etc/apt/sources.list.d/archive_uri-https_r-project_org-noble.list. List the directory to find it:

ls /etc/apt/sources.list.d/

Installing R packages

After R is installed (and after every minor-version upgrade), reinstall your project's packages. Set a CRAN mirror so install.packages() doesn't prompt:

options(repos = c(CRAN = "https://cloud.r-project.org"))
install.packages(c("qtl2", "tidyverse", "data.table"))

Or from the shell:

Rscript -e 'options(repos=c(CRAN="https://cloud.r-project.org")); install.packages(c("qtl2","tidyverse"))'

To make the mirror choice permanent, add the options(repos = ...) line to ~/.Rprofile.

Two things that make source installs much less painful:

Writing packages to a user library

If you'd rather not need sudo for install.packages(), use a personal library. R already reserves one by default — R_LIBS_USER points at ~/R/x86_64-pc-linux-gnu-library/4.6 on this machine — it just isn't used until the directory exists:

mkdir -p "$(Rscript -e 'cat(Sys.getenv("R_LIBS_USER"))')"

Create it and install.packages() will write there instead of asking for root. Confirm it's picked up with Rscript -e '.libPaths()' — the new path should appear first.

The default path embeds the R version (%v, major.minor — note the lowercase; %V would include the patchlevel and give you a fresh library on every point release). So the path shifts on a minor upgrade and the library still needs repopulating, which is the behaviour you want: packages built for 4.5 are not guaranteed to load under 4.6.


Full sequence, copy-pasteable

Fresh Ubuntu 24.04 machine, official CRAN mirror:

# 1. Prerequisites
sudo apt update -qq
sudo apt install --no-install-recommends software-properties-common dirmngr

# 2. Signing key (verify fingerprint E298A3A825C0D65DFD57CBB651716619E084DAB9)
wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc \
  | sudo gpg --dearmor -o /etc/apt/keyrings/cran_ubuntu_key.gpg
gpg --show-keys --with-fingerprint /etc/apt/keyrings/cran_ubuntu_key.gpg

# 3. Repository
echo "deb [signed-by=/etc/apt/keyrings/cran_ubuntu_key.gpg] https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/" \
  | sudo tee /etc/apt/sources.list.d/cran.list

# 4. Install
sudo apt update
sudo apt install --no-install-recommends r-base r-base-dev

# 5. Verify
R --version

Pitfalls found in this machine's history

These commands were actually run here and did not work. They're recorded so the same mistakes don't get repeated.

Command Why it failed
wget -qO- https://r-project.org \| sudo tee /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc Downloads the R homepage HTML, not the key. The key lives at https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc. This wrote a junk file into trusted.gpg.d.
sudo add-apt-repository "deb https://r-project.org $(lsb_release -cs)-cran40/" r-project.org is the project website, not a package archive. The repo root is https://cloud.r-project.org/bin/linux/ubuntu.
deb https://cloud.r-project.org/bin/linux/ubuntu noble-cran40/ This is a sources.list line, not a shell command. It has to go inside a file under /etc/apt/sources.list.d/.
sudo apt install deb Consequence of the above — there is no package named deb.
sudo apt install r-base (with the broken repo active) Falls back to Ubuntu's stale universe copy of R rather than the current CRAN build. Always confirm with apt-cache policy r-base that the candidate comes from a CRAN URL.
wget -qO- ... \| sudo tee /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc (overwrite form) tee without -a clobbers the file. More importantly, a .asc file must stay ASCII-armored; if you pipe through gpg --dearmor, name the output .gpg.

Checking which repo a version actually came from

apt-cache policy r-base

The Installed: and Candidate: lines should match, and the 500-priority source underneath should be a CRAN URL — not archive.ubuntu.com:

r-base:
  Installed: 4.6.1-1.2404.1
  Candidate: 4.6.1-1.2404.1
  Version table:
 *** 4.6.1-1.2404.1 500
        500 https://mirrors.tuna.tsinghua.edu.cn/CRAN/bin/linux/ubuntu noble-cran40/ Packages

Older versions listed below the installed one are merely available in the repo; they are not installed.


Verifying there's exactly one R

which -a R Rscript              # should resolve only to /usr/bin
dpkg -l 'r-base*' | grep '^ii'  # one version per package
apt-cache policy r-base         # Installed == Candidate
Rscript -e '.libPaths()'        # where packages will be read from

Also worth a glance for hand-built or third-party installs, which apt won't know about:

ls -d /opt/R/* /usr/local/lib/R /usr/lib/R 2>/dev/null
command -v conda rig
snap list 2>/dev/null | grep -i '\br\b'

Watch out when running these in zsh: a glob like /opt/R/* that matches nothing aborts the whole command line with no matches found, so any later checks on the same line are silently skipped. Run the checks separately, or prefix with noglob, to avoid mistaking a skipped check for a clean result.