Install Erlang on Ubuntu 24.04 from the RabbitMQ team’s Launchpad PPA unless something forces you elsewhere. It ships OTP 27 as plain apt packages that upgrade with the rest of the system, while Ubuntu’s own archive stops at OTP 25, too old for any RabbitMQ 4.x release. Whichever source you use, install erlang-nox rather than erlang, because on a clean noble box the erlang metapackage pulls in 244 packages, OpenJDK 21 and GTK 3 among them.
The older version of this guide used a Cloudsmith setup script that now returns HTTP 404, so that path is dead. Below you get the three methods that work today (the archive package, the RabbitMQ PPAs and a kerl source build), an exact version check and a clean way to switch. Every command below was run on Ubuntu 24.04.5 LTS in October 2026, with each install method started from a clean snapshot.
Choose an Erlang source for Ubuntu 24.04
Three sources install cleanly on noble, and they differ in which OTP you get and who keeps it updated. We installed each one on the same box from a clean snapshot, so the numbers below come from the lab rather than from package pages.
| Source | OTP you get | Install cost | Updates | Pick it when |
|---|---|---|---|---|
Ubuntu archive, erlang-nox | 25 (25.3.2.8) | 27 packages, 37.1 MB, 34 s | Ubuntu security updates | Packaged software accepts OTP 25 |
RabbitMQ PPA, erlang-nox | 27 (27.3.4.18), 28 from the -28 PPA | 27 packages, 47.1 MB, 27 s | apt, from Launchpad | RabbitMQ or any app that wants a current OTP |
| kerl source build | Any release, 28.5.0.8 here | 93 build packages (368 MB) plus a build of about 5 min | You rebuild | You need an exact patch release or several OTPs side by side |
The PPA is the default we’d run on a server, because it tracks current OTP without putting a compiler on the box. Take the archive build only when the software you run was built against OTP 25 and you’d rather lean on Ubuntu’s security team. Reach for kerl when a project pins a patch release, since kerl listed 28.5.0.8 while the PPA still served 28.5.0.7, or when two majors have to live on one machine.
The lab box was an Ubuntu 24.04 system container under Incus, so the userland and apt sources are stock noble and only the kernel came from the host. You need a sudo user and outbound HTTPS for every method. kerl also wants a few spare vCPUs, because the compile time scales with them.
Install Erlang from the Ubuntu archive
Check what noble itself offers before you add anything, because the candidate versions decide whether the archive is enough for your software. Refresh the index and ask apt for the main Erlang packages plus the two tools people usually install beside them.
sudo apt update
apt-cache policy erlang erlang-base erlang-nox rebar3 elixir | grep -E "^[a-z]|Cand"
Every Erlang package sits at the same OTP 25 build. The 1: prefix is a Debian epoch, not part of the OTP version, so read the rest as the release number.
erlang:
Candidate: 1:25.3.2.8+dfsg-1ubuntu4.8
erlang-base:
Candidate: 1:25.3.2.8+dfsg-1ubuntu4.8
erlang-nox:
Candidate: 1:25.3.2.8+dfsg-1ubuntu4.8
rebar3:
Candidate: 3.19.0-1
elixir:
Candidate: 1.14.0.dfsg-2
Next, see what each metapackage would drag in. A simulated install with -s changes nothing on the system, so it is safe to run on a production box, and the second command filters the erlang simulation for its desktop and Java pieces.
for p in erlang erlang-nox erlang-base; do echo "$p: $(apt-get install -s -y $p 2>/dev/null | grep -c ^Inst) packages"; done
apt-get install -s -y erlang | grep -oE "^Inst (libwx[^ ]*|x11-common|libgtk-3-0t64|openjdk[^ ]*|erlang-wx|erlang-jinterface)"
The gap is large. erlang-base is only the runtime at 4 packages, erlang-nox adds the standard applications without a GUI at 27, and erlang wants 244, including X11, GTK 3, four wxWidgets libraries with erlang-wx on top, and a full OpenJDK 21 runtime.

Those extra packages exist for the observer GUI, the wx bindings and jinterface, and a headless server uses none of them. So never run apt install erlang on a server, since you’d be patching a Java runtime and a GUI toolkit for no gain. If you need Java on the box anyway, install it on purpose rather than as a side effect. Install the headless set instead.
sudo apt install -y erlang-nox
apt’s summary lines match the simulation, and the whole install took 34 seconds on the lab box. That is roughly a ninth of what the full metapackage wanted, so it is also a ninth of what apt has to patch on every security update.
0 upgraded, 27 newly installed, 0 to remove and 12 not upgraded.
After this operation, 37.1 MB of additional disk space will be used.
What you give up is small, but know it before a tool fails on you. With only erlang-nox installed, the runtime can’t find these:
observer, socode:which(observer)returnsnon_existingwx, which returnsnon_existingtoodialyzerandtyper, shipped inerlang-dialyzer
We did not install erlang-dialyzer separately, so add it yourself if your build runs type checks. Skip the apt rebar3 package, though, because it does not stay headless. Check its dependencies and what a simulated install would add on top of erlang-nox, with and without recommended packages.
apt-cache show rebar3 | grep -E "^(Depends|Recommends)"
apt-get install -s rebar3 | grep -E "newly installed"
apt-get install -s --no-install-recommends rebar3 | grep -E "newly installed"
Recommends: erlang is the problem, since apt installs recommended packages by default and that brings the full GUI and Java metapackage back. The simulation wanted 228 new packages, and still 180 with --no-install-recommends, because the hard dependencies include erlang-dialyzer, erlang-dev and most of the toolchain. You get the same counts with the PPA’s OTP 27 installed. There, apt satisfied those dependencies from the PPA instead of pulling OTP 25.
Depends: erlang-asn1:any, erlang-base:any, erlang-common-test:any, erlang-crypto:any, erlang-dev:any, erlang-dialyzer:any, erlang-edoc:any, erlang-eunit:any, erlang-parsetools:any, erlang-reltool:any, erlang-syntax-tools:any, erlang-tools:any
Recommends: erlang, git
0 upgraded, 228 newly installed, 0 to remove and 12 not upgraded.
0 upgraded, 180 newly installed, 0 to remove and 12 not upgraded.
On a server, take the upstream rebar3 escript instead, which the kerl section installs. It needs a recent OTP, so pair it with the PPA or a kerl build rather than the archive’s OTP 25.
Check the installed Erlang version
Erlang has no --version flag (erl -version prints only the ERTS emulator version), and the erl banner names only the major release, so most version questions are really about the OTP patch level. Ask the runtime for the OTP major and the ERTS (emulator) version, then read the OTP_VERSION file the install writes, because otp_release never returns more than the major number.
erl -noshell -eval 'io:format("OTP ~s, ERTS ~s~n", [erlang:system_info(otp_release), erlang:system_info(version)]), halt().'
cat /usr/lib/erlang/releases/*/OTP_VERSION
On the archive build the first line reports OTP 25 with its emulator, and the second gives the full release.
OTP 25, ERTS 13.2.2.5
25.3.2.8
Quote the second number when a project or a compatibility table asks for an exact patch release, since that’s the one RabbitMQ and similar projects use in their requirements. Both checks work the same on the PPA packages, which also install under /usr/lib/erlang. A kerl build keeps the file under its own prefix, so the path changes there.
Compile a test module and an escript
A version string proves the runtime starts, but compiling a module also proves that erlc, the code path and the standard library are in place. Create a working directory in your home and open a new source file there.
mkdir -p ~/hello && cd ~/hello
vim hello.erl
Paste this module. It prints the OTP release it runs on, so it stays useful after you switch versions.
-module(hello).
-export([start/0]).
start() ->
io:format("Hello from Erlang/OTP ~s~n", [erlang:system_info(otp_release)]).
Compile it, list the directory and run the module without the interactive shell. The two -s flags call hello:start() and then init:stop(), so the VM exits by itself instead of leaving you at a prompt.
erlc hello.erl
ls
erl -noshell -s hello start -s init stop
A hello.beam file beside the source means the compile worked, and the greeting names the release that ran it.
hello.beam
hello.erl
Hello from Erlang/OTP 25
For small admin scripts you don’t need a module at all, because escript runs Erlang straight from a file with a shebang. Open a second file in the same directory.
vim sched.escript
This two-line script reports how many schedulers the VM started, which normally equals the CPU count it can see.
#!/usr/bin/env escript
main(_) -> io:format("~p schedulers online~n", [erlang:system_info(schedulers_online)]).
Make it executable and run it. The shebang finds escript through env, so the script runs on whichever Erlang comes first on the PATH, including a kerl build you activate later.
chmod +x sched.escript
./sched.escript
The lab box had 4 vCPUs, and the VM picked up all of them.
4 schedulers online
If the number is lower than your core count, check for a CPU limit on the container or VM first. Erlang sizes its schedulers to what it is allowed to see, so a capped container quietly gets fewer of them.
Install the latest Erlang from the RabbitMQ PPA
The RabbitMQ team publishes current OTP builds for Ubuntu on Launchpad, and these replace the Cloudsmith repository the old instructions used. They carry the same erlang-* package names as the archive, only newer, so everything above about erlang-nox still applies.
Cloudsmith setup.deb.sh returns 404
The old one-liner, curl -1sLf 'https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/setup.deb.sh' | sudo -E bash, fails today. In our test the script URL returned HTTP 404, the gpg.E495BB49CC4BBE5B.key signing key beside it returned 404 too, and curl -f saved nothing. Piped into bash that fails quietly, so the apt install erlang that followed in the old guide then installs the archive OTP 25 with all 244 packages. If you ran it on an older box, look in /etc/apt/sources.list.d/ for a leftover Cloudsmith entry and delete it before adding the PPA.
The PPA needs add-apt-repository, which comes from software-properties-common. Minimal and container images often leave that package out, so install it first, then add the main RabbitMQ Erlang PPA. On Ubuntu the add command refreshes the package index for you.
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:rabbitmq/rabbitmq-erlang
The last lines of its output point at the version-specific PPAs. Keep them in mind, because they matter later when you want OTP 28 or need to stay on 26.
See https://launchpad.net/~rabbitmq/+archive/ubuntu/rabbitmq-erlang-27 for Erlang 27.x packages and https://launchpad.net/~rabbitmq/+archive/ubuntu/rabbitmq-erlang-26 for Erlang 26.x packages.
More info: https://launchpad.net/~rabbitmq/+archive/ubuntu/rabbitmq-erlang
Adding repository.
Confirm that apt now prefers the PPA build before you install, so you don’t end up with OTP 25 by accident.
apt-cache policy erlang-base erlang-nox | grep -E "^[a-z]|Cand|ppa"
Look for the rmq1ppa1 suffix and a ppa.launchpadcontent.net source. If the candidate still shows the ubuntu4.8 build, the add step failed and the install would quietly give you the archive version.
erlang-base:
Candidate: 1:27.3.4.18-1rmq1ppa1~ubuntu24.04.1
1:27.3.4.18-1rmq1ppa1~ubuntu24.04.1 500
500 https://ppa.launchpadcontent.net/rabbitmq/rabbitmq-erlang/ubuntu noble/main amd64 Packages
erlang-nox:
Candidate: 1:27.3.4.18-1rmq1ppa1~ubuntu24.04.1
1:27.3.4.18-1rmq1ppa1~ubuntu24.04.1 500
500 https://ppa.launchpadcontent.net/rabbitmq/rabbitmq-erlang/ubuntu noble/main amd64 Packages
Install the headless set from the PPA and run both version checks again.
sudo apt install -y erlang-nox
erl -noshell -eval 'io:format("OTP ~s, ERTS ~s~n", [erlang:system_info(otp_release), erlang:system_info(version)]), halt().'
cat /usr/lib/erlang/releases/*/OTP_VERSION
apt installed the same 27 packages as the archive build, 10 MB larger, and it finished in 27 seconds. The hello module recompiled with erlc then prints Hello from Erlang/OTP 27.
0 upgraded, 27 newly installed, 0 to remove and 12 not upgraded.
After this operation, 47.1 MB of additional disk space will be used.
OTP 27, ERTS 15.2.7.13
27.3.4.18
OTP 27 clears RabbitMQ’s floor. According to the RabbitMQ Erlang compatibility table, every 4.x release needs at least OTP 26.2, and recent 4.2.x and 4.3.x releases need 27 (4.2.9+ and 4.3.3+), so the archive’s OTP 25 can’t run any of them. With Erlang in place you can install RabbitMQ on Ubuntu 24.04 next. Check your release’s row in the matrix before you jump to OTP 28: for releases before 4.3.6 RabbitMQ states that “Erlang 28 is only supported for brand new clusters”, while 4.3.6 and 4.4.0 support it fully. Note that we did not install RabbitMQ itself for this test.
Move to OTP 28 with the rabbitmq-erlang-28 PPA
The RabbitMQ team also runs one PPA per OTP major, and the 26, 27 and 28 pages all exist on Launchpad. The -27 PPA lagged at 27.3.4.1 for noble when we checked, so the main rabbitmq-erlang PPA is the one carrying current 27 builds. Adding the OTP 28 PPA on top of the main one gives apt a newer candidate, so a plain install upgrades in place.
sudo add-apt-repository -y ppa:rabbitmq/rabbitmq-erlang-28
apt-cache policy erlang-base | head -8
Now the installed 27 build is marked ***, and the 28 build from the new PPA is the candidate. Both sources carry the same default priority of 500, so apt takes the higher version and no pinning file is needed.
erlang-base:
Installed: 1:27.3.4.18-1rmq1ppa1~ubuntu24.04.1
Candidate: 1:28.5.0.7-1rmq1ppa1~ubuntu24.04.1
Version table:
1:28.5.0.7-1rmq1ppa1~ubuntu24.04.1 500
500 https://ppa.launchpadcontent.net/rabbitmq/rabbitmq-erlang-28/ubuntu noble/main amd64 Packages
*** 1:27.3.4.18-1rmq1ppa1~ubuntu24.04.1 500
500 https://ppa.launchpadcontent.net/rabbitmq/rabbitmq-erlang/ubuntu noble/main amd64 Packages
Run the install again and read the new version file.
sudo apt install -y erlang-nox
cat /usr/lib/erlang/releases/*/OTP_VERSION
apt reported 24 packages upgraded and none added, and the version file moved to 28. An in-place upgrade replaces the library directories under any running Erlang node, and a node that later loads a module from the new tree can fail, so restart Erlang services promptly after the upgrade.
24 upgraded, 0 newly installed, 0 to remove and 12 not upgraded.
28.5.0.7
Our hello.beam compiled under OTP 27 still ran on 28 and printed Hello from Erlang/OTP 28, and a fresh erlc compile worked too. Recompile your own code after a major jump anyway, because a BEAM file built on a newer OTP is not guaranteed to load on an older one, and that bites when you roll back.
Build a specific OTP release with kerl
kerl downloads an OTP source release, builds it and installs it into a directory you choose. Each build is separate from apt and from every other build, so it’s the method for “this project needs exactly one patch release” or for two majors on one machine. The price is a compiler toolchain on the box and a few minutes per build, so install the build dependencies first.
sudo apt install -y build-essential autoconf m4 libncurses-dev libssl-dev curl git
Expect this to be the largest step in the guide: it took 2 minutes 32 seconds on the lab box. The toolchain stays installed afterwards, which is what every later kerl build reuses.
0 upgraded, 93 newly installed, 0 to remove and 12 not upgraded.
After this operation, 368 MB of additional disk space will be used.
Don’t trim libssl-dev from that list. Without it the crypto and ssl applications don’t build, and almost every real Erlang service needs TLS. kerl itself is a single shell script, so download it into /usr/local/bin, make it executable and refresh its list of OTP releases.
sudo curl -sSfLo /usr/local/bin/kerl https://raw.githubusercontent.com/kerl/kerl/4.4.0/kerl #https://github.com/kerl/kerl/releases
sudo chmod +x /usr/local/bin/kerl
kerl version
kerl update releases
kerl list releases all | tr " " "\n" | grep -E "^2[78]\." | sort -V | tail -4
The URL is pinned to the kerl 4.4.0 tag, the version we tested. The newest release on the list was one patch ahead of the PPA, which is the usual gap between a source tag and a packaged build.
4.4.0
28.5.0.5
28.5.0.6
28.5.0.7
28.5.0.8
Set the release you want once, since the commands that follow use it several times, and pick it from that list or from the OTP releases page linked in the comment. MAKEFLAGS=-j4 matches the 4 vCPUs of the lab box, so set it to your own core count. Leave KERL_BUILD_DOCS unset: kerl builds the documentation whenever that variable holds any value, even no.
export OTP_VER="28.5.0.8" #https://github.com/erlang/otp/releases
export MAKEFLAGS=-j4
time kerl build "${OTP_VER}" "${OTP_VER}"
On 4 vCPUs the build took about five minutes of wall time, 5 minutes 11 seconds here, with almost nine minutes of CPU time. Budget more on a box with fewer cores, and expect to pay that time again for every OTP release you build.
real 5m10.679s
user 8m37.681s
sys 1m6.141s
A regular user cannot write to /opt, so give yourself the install directory first, then install the build into it and check its size. We ran this flow as an unprivileged user too, and the build, install, activate and deactivate steps all behaved the same as under root.
sudo install -d -o "$USER" /opt/erlang
kerl install "${OTP_VER}" "/opt/erlang/${OTP_VER}"
du -sh "/opt/erlang/${OTP_VER}"
The installed tree is about 166 MB, well above the PPA packages, because it holds every application that built.
166M /opt/erlang/28.5.0.8
kerl wrote nothing outside that directory and ~/.kerl, so an apt-installed Erlang on the same box keeps answering as erl until you activate this build.
kerl build: “No OpenGL headers found, wx will NOT be usable”
The build output ends on a block of wxWidgets warnings, which looks like a failure but is not one. Read the summary in the build log to see what was actually left out.
grep -B2 -A12 "APPLICATIONS DISABLED\|APPLICATIONS INFORMATION" ~/.kerl/builds/${OTP_VER}/otp_build_${OTP_VER}.log | head -30
Two applications were disabled, and a third built but cannot be used (output trimmed by head).
*********************************************************************
********************** APPLICATIONS DISABLED **********************
*********************************************************************
jinterface : No Java compiler found
odbc : ODBC library - link check failed
*********************************************************************
*********************************************************************
********************** APPLICATIONS INFORMATION *******************
*********************************************************************
wx : No OpenGL headers found, wx will NOT be usable
No GLU headers found, wx will NOT be usable
wxWidgets was not compiled with --enable-webview or wxWebView developer package is not installed, wxWebView will NOT be available
jinterface and wx are the same pieces erlang-nox leaves out. ODBC is the exception, because erlang-nox does ship erlang-odbc, so a kerl build only gets it with the ODBC development headers installed before the build. For a server without ODBC database access that is the right result, so ignore the warnings, and rebuild with the matching development packages only if you need ODBC or the observer GUI. If crypto or ssl shows up in that disabled list, though, libssl-dev was missing, so install it and rebuild before going further.
A kerl build stays off your PATH until you activate it in the current shell. Activate it, then confirm which erl answers and read its version file, which sits under the install prefix.
. "/opt/erlang/${OTP_VER}/activate"
which erl
cat "/opt/erlang/${OTP_VER}/releases/28/OTP_VERSION"
Both lines check out: the path points into /opt/erlang, and the version file shows the exact patch release you asked for. If which erl still prints /usr/bin/erl, the activate script was run in a subshell instead of sourced with the leading dot.
/opt/erlang/28.5.0.8/bin/erl
28.5.0.8
Below, the activated build on the lab box runs the version one-liner, the hello module from earlier and rebar3.

The build links against Ubuntu’s system OpenSSL rather than a bundled copy, so crypto gets its security fixes from apt like everything else. Check which library it loaded.
erl -noshell -eval 'io:format("~s~n", [element(3, hd(crypto:info_lib()))]), halt().'
That is noble’s OpenSSL 3.0 library, the same one the system packages use, so an Ubuntu OpenSSL update covers this build once you restart the Erlang node.
OpenSSL 3.0.13 30 Jan 2024
For a build tool, take the upstream rebar3 escript instead of the apt one, which drags the full erlang metapackage back in as shown earlier. It runs on whichever erl is active, so the same binary serves every kerl build you activate. Install it, then generate a skeleton application and compile it to prove the toolchain works end to end.
sudo curl -sSfLo /usr/local/bin/rebar3 https://s3.amazonaws.com/rebar3/rebar3
sudo chmod +x /usr/local/bin/rebar3
rebar3 version
cd ~ && rebar3 new app myapp
cd myapp && rebar3 compile
rebar3 reports the OTP and ERTS it found, which is a quick way to confirm the right build is active. It then compiles the app without errors, so the build tool, the compiler and the code path all agree on the active OTP, and that is the check that matters before you point it at a real project. The output below is the version line plus the last lines of the generate and compile steps.
rebar 3.27.3 on Erlang/OTP 28 Erts 16.4.0.7
===> Writing myapp/.gitignore
===> Writing myapp/LICENSE.md
===> Writing myapp/README.md
===> Analyzing applications...
===> Compiling myapp
Run kerl_deactivate to drop the build from your PATH again, after which which erl prints nothing on a box without apt Erlang, or falls back to /usr/bin/erl if the packages are installed. Activation only lasts for the shell it ran in, so a systemd service needs the full bin path under /opt/erlang in its unit. If you manage Go toolchains by hand on the same server, it’s the same model, one directory per version and a choice per shell.
rebar3: “escript: exception error: undefined function rebar3:main/1”
The same rebar3 3.27.3 escript ran fine on the PPA’s OTP 27 and on the kerl OTP 28 build, but on the archive’s OTP 25 it fails before printing anything useful. This was the start of the error on that box.
escript: exception error: undefined function rebar3:main/1
in function escript:run/2 (escript.erl, line 750)
in call from escript:start/1 (escript.erl, line 277)
in call from init:start_em/1
in call from init:do_boot/3
The escript is compiled for newer OTP releases. OTP 25 cannot load its main module, so you need a newer runtime. The fix we would take is moving the box to the PPA’s OTP 27, where the same binary printed rebar 3.27.3 on Erlang/OTP 27 Erts 15.2.7.13. An older rebar3 release built for OTP 25 may also work, but we did not test one.
Uninstall Erlang or switch OTP versions
Going up a version is an apt install, as the OTP 28 PPA showed. Going down or back to stock is where people get stuck, because removing a PPA doesn’t remove what it installed. To see it, remove the OTP 28 PPA and check what apt plans.
sudo add-apt-repository -y --remove ppa:rabbitmq/rabbitmq-erlang-28
sudo apt update
apt-cache policy erlang-base | head -3
OTP 28 stays installed and stays the candidate, and a simulated apt install erlang-nox reports 0 changes.
erlang-base:
Installed: 1:28.5.0.7-1rmq1ppa1~ubuntu24.04.1
Candidate: 1:28.5.0.7-1rmq1ppa1~ubuntu24.04.1
apt never downgrades on its own, so the clean way down is to purge every Erlang package and install again from the sources that remain. That purge is also the most dangerous command in this guide. The erlang* pattern takes every package that depends on Erlang with it, and you may not know what those are. This order worked on the lab box, which ran nothing but Erlang:
- Remove the PPA you’re leaving, as above.
- Simulate the purge and read every
Purgline. - Purge all
erlang*packages and autoremove what they pulled in. - Confirm no Erlang package is left and
erlis gone. - Install
erlang-noxagain and readOTP_VERSION.
Simulate first. The command below changes nothing and lists every package the real purge would remove.
apt-get -s purge "erlang*" | grep -E "^Purg"
Stop here if any line names a package that is not erlang-*. On a test box with Ubuntu’s rabbitmq-server installed, the simulation listed 25 packages, and this was one of them (excerpt):
Purg rabbitmq-server [3.12.1-1ubuntu1.7]
Purging Ubuntu’s own rabbitmq-server package (3.12.1-1ubuntu1.7) runs its package scripts, which delete /var/lib/rabbitmq, /var/log/rabbitmq and /etc/rabbitmq. The broker’s data and config would go with it. We did not check the Team RabbitMQ .deb, but the simulate-first guard catches either package. We only simulated that purge. On a RabbitMQ server, you should not use this method at all. Either remove RabbitMQ deliberately after a backup of its data and definitions, or downgrade only the Erlang packages to a pinned version with apt install, which we did not test.
When the simulation lists only Erlang packages, run the purge. The package counts on either side of it prove nothing Erlang survived, so no stray package from the old PPA mixes with the new install. With the main RabbitMQ PPA still configured, the reinstall then picks its OTP 27 build.
dpkg -l | grep -c "^ii erlang"
sudo apt purge -y "erlang*"
sudo apt autoremove -y
dpkg -l | grep -c "^ii erlang"
which erl || echo erl-gone
sudo apt install -y erlang-nox
cat /usr/lib/erlang/releases/*/OTP_VERSION
24 Erlang packages went to 0, erl left the PATH, and the reinstall landed on the PPA’s OTP 27 build. That is the newest version any remaining source offers, which is exactly how apt picks a candidate, so to land on a specific major you control which PPAs are configured rather than which version you ask for.
24
0
erl-gone
27.3.4.18
Going all the way back to the Ubuntu archive build is the same sequence with the last PPA removed too. List the sources directory after removing it, because only ubuntu.sources should remain, and any leftover file there would hand apt a newer candidate again.
sudo add-apt-repository -y --remove ppa:rabbitmq/rabbitmq-erlang
ls /etc/apt/sources.list.d/
sudo apt purge -y "erlang*"
sudo apt autoremove -y
sudo apt update
sudo apt install -y erlang-nox
cat /usr/lib/erlang/releases/*/OTP_VERSION
apt-cache policy erlang-base | head -3
Only the stock source file is left, the version file is back on OTP 25, and installed and candidate both show the archive build with no rmq suffix. From here the box behaves like one that never saw the PPAs, and Erlang updates arrive with the regular Ubuntu security updates.
ubuntu.sources
25.3.2.8
erlang-base:
Installed: 1:25.3.2.8+dfsg-1ubuntu4.8
Candidate: 1:25.3.2.8+dfsg-1ubuntu4.8
To uninstall Erlang completely, stop after the purge and autoremove step. A kerl build never touched apt, so remove it with kerl itself, which deletes the installed tree and the build directory and leaves the packages alone.
kerl delete installation "/opt/erlang/${OTP_VER}"
kerl delete build "${OTP_VER}"
kerl list builds
kerl list installations
Both lists come back empty and /opt/erlang is left empty. ~/.kerl still held 101 MB of downloaded source archives and release lists afterwards, so rm -rf ~/.kerl finishes the cleanup (we did not run that last command). If the box runs a RabbitMQ cluster, check the compatibility table before any OTP change, because every node has to run an OTP its RabbitMQ release accepts. For releases before 4.3.6, RabbitMQ also documents a known issue when clusters running Khepri on Erlang 27 are upgraded to Erlang 28. It affects rolling upgrades, so on those releases never switch a cluster from 27 to 28 node by node, and check your release’s row first. When the server itself moves on with an upgrade to Ubuntu 26.04, remove the noble PPAs first. The RabbitMQ PPAs also list builds for 26.04 (resolute), which we did not test, so add them back after the upgrade and confirm the candidate with apt-cache policy before installing.