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 intrusted.gpg.dis 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-upgradeonly 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 plainapt installthe first time.
Two things worth knowing about apt-managed R:
- apt upgrades in place. Installing a newer
r-basereplaces the old one; apt never keeps two R versions side by side. There is no "old version" left to clean up afterwards. (If you genuinely need multiple R versions simultaneously, apt is the wrong tool — use rig.) - Minor-version upgrades reset your package library. R 4.5 → 4.6 changes the
library path, so
/usr/lib/R/site-libraryand/usr/local/lib/R/site-librarycome up empty and every add-on package must be reinstalled. Plan for this; see below.
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:
System build dependencies. Many packages need system libraries. A good baseline for a genomics/plotting workflow:
sudo apt install --no-install-recommends \ build-essential gfortran \ libcurl4-openssl-dev libssl-dev libxml2-dev \ libfontconfig1-dev libharfbuzz-dev libfribidi-dev \ libfreetype6-dev libpng-dev libtiff5-dev libjpeg-dev
Binary packages via r2u. Compiling
tidyversefrom source takes tens of minutes. r2u serves prebuilt binaries for Ubuntu LTS and turns that into seconds. Worth setting up if you install packages often. (CRAN's olderc2d4uPPA is on hiatus and no longer updated — don't use it.)
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 withno matches found, so any later checks on the same line are silently skipped. Run the checks separately, or prefix withnoglob, to avoid mistaking a skipped check for a clean result.