CentOS

How to Install and Enable EPEL on RHEL 10 and CentOS Stream 10

Most EPEL guides still assume one repository per major release, where a single epel-release package gives every EL9 box the same package tree. EPEL 10 breaks that assumption. It keeps a separate repository for each RHEL 10 minor release. The same epel-release-latest-10 RPM pointed our RHEL 10.1 VM at the 10.2 tree with 23,984 packages and our CentOS Stream 10 VM at 10.3, until one routine upgrade moved the Stream box to 10.4 and 26,454 packages.

Original content from computingforgeeks.com - post 161464

To install EPEL on RHEL 10 you enable CodeReady Builder and install that permalink RPM, and on CentOS Stream 10 you enable crb and install epel-release from the extras repository. This guide runs both, then explains how dnf variables choose your mirror path, why EPEL packages fail without CRB, and which old favourites such as inxi never reached EPEL 10. Our RHEL VM was unregistered, so Red Hat’s CRB step is shown as documented rather than tested, and everything this guide says it ran was run on CentOS Stream 10 and RHEL 10.1 in October 2026.

Why EPEL 10 is no longer one repository per release

EPEL 10 follows RHEL’s minor releases because its packages are now built against a specific minor rather than against whatever RHEL 10 happens to be current. The EPEL branch documentation spells it out. Each RHEL 10 minor gets its own dnf repository and an epel10.x branch built against that minor’s BaseOS, AppStream and CRB, while the leading epel10 branch builds against CentOS Stream 10. The minor even shows up in the release tag, which is why the same btop release appears as el10_2 in the RHEL tree and el10_3 in the Stream tree on our two lab machines.

EPEL 9 worked differently. It had one tree shared by RHEL 9 and CentOS Stream 9, plus an optional epel-next layer for Stream packages that needed newer libraries. EPEL 10 drops epel-next entirely. The EPEL getting-started page lists epel-release-latest-10 with no Next package beside it, and CentOS Stream 10’s own extras repository offers only epel-release. Stream gets its own tree through a dnf variable instead, which the next section explains.

You can see the layout in dl.fedoraproject.org/pub/epel/, which holds one directory per minor from 10.0 to 10.4, plus directories named 10, 10s, 10z and 10.2z. Your machine never asks for a directory by name, though. It asks mirrors.fedoraproject.org for a metalink name, and this is where each name led when we checked on October 15, 2026:

Metalink nameTree it servedWho asks for it
epel-10sepel/10.4/CentOS Stream 10 with the current epel-release build
epel-10epel/10.3/CentOS Stream 10 with the older permalink build today; RHEL 10 with the current build once EPEL releases it to RHEL at RHEL 10.3
epel-z-10epel/10.2/RHEL 10 with the permalink build (our 10.1 VM); kept for compatibility after the switch
epel-10.4, epel-10.3, epel-10.2epel/10.4/, epel/10.3/, epel/10.2/Explicit minor names; neither repo file we saw builds them
epel-z-10.0, epel-z-10.1archive.fedoraproject.orgEnd-of-life minors

A RHEL machine does not read the tree named after its own minor. EPEL first planned exactly that, then dropped it because it caused problems on upgrades from one minor to the next. RHEL now asks for epel-z-10, and MirrorManager redirects that name to the current RHEL minor, as Carl George explains in the EPEL 10 retrospective on Fedora Discussion. That is why our 10.1 VM pulled from 10.2.

That redirect holds only while $releasever stays 10. The same thread mentions users who override $releasever to stay on an older minor. When they do, the minor lands in the metalink name, and for 10.0 and 10.1 that name now resolves to the archive server, which no longer gets updates. We did not test a pinned box, so treat this as the documented behaviour rather than a lab result.

CentOS Stream also runs ahead of RHEL. The Stream tree carried 2,470 more packages than the RHEL tree on our test day, so a package your Stream test box found is not guaranteed to exist yet for the RHEL servers you deploy to.

How dnf decides which EPEL 10 tree you get

dnf picks the tree by filling variables into one metalink line in /etc/yum.repos.d/epel.repo, so the result depends on two things you rarely look at: which build of epel-release wrote that file, and which variables your OS defines. The mechanism here runs in order:

  1. dnf loads its variables. $releasever is 10 on both systems. RHEL 10.1 also sets $releasever_minor to 1, while CentOS Stream leaves it empty and ships /etc/dnf/vars/stream with the value 10-stream.
  2. The repo file expands them shell-style. ${stream:+s} appends an s only when stream is set, and ${releasever_minor:+-z} inserts -z only when a minor is set.
  3. The metalink names exactly one tree. mirrors.fedoraproject.org returns mirrors for that tree alone, and dnf downloads that tree’s metadata, 7.3 MB for 10.4 on Stream and 6.7 MB for 10.2 on RHEL.

You can print the variables dnf will actually use through its own Python API, which beats reading /etc/dnf/vars. On our Stream box that directory held only the stream file, while releasever and releasever_minor live nowhere you can cat. This is the check we ran there:

python3 -c "import dnf; b=dnf.Base(); b.conf.substitutions.update_from_etc(\"/\"); print({k:v for k,v in b.conf.substitutions.items() if k.startswith(\"releasever\") or k in (\"stream\",\"basearch\")})"

Stream reports an empty releasever_minor and a stream value, which is exactly what sends it down the s branch of the repo file. The basearch value comes along because the metalink names your architecture too:

{'basearch': 'x86_64', 'releasever': '10', 'releasever_major': '10', 'releasever_minor': '', 'stream': '10-stream'}

RHEL 10.1 is the mirror image. On that box we ran the same check filtered to the releasever keys only:

python3 -c "import dnf; b=dnf.Base(); b.conf.substitutions.update_from_etc(\"/\"); print({k:v for k,v in b.conf.substitutions.items() if k.startswith(\"releasever\")})"

It shows a minor of 1. This filter can’t show stream, but RHEL doesn’t define that variable at all, which the EPEL discussion linked above states for RHEL and Oracle Linux alike:

{'releasever': '10', 'releasever_major': '10', 'releasever_minor': '1'}

Those variables only matter through the repo file, and the two epel-release builds you’ll meet write different metalink lines. The current build, which CentOS ships in extras-common and EPEL’s own repository serves as an update on Stream, keys on stream:

metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-$releasever${stream:+s}&arch=$basearch

The older build served by the epel-release-latest-10 permalink keys on the minor instead. Its commented-out baseurl line follows the same rule and expands to a 10z directory when a minor is set, which is where that directory on the mirror gets its name:

metalink=https://mirrors.fedoraproject.org/metalink?repo=epel${releasever_minor:+-z}-$releasever&arch=$basearch

Put the variables through those two lines and you get the three outcomes we measured. The package counts come from dnf repoinfo on each box on the same day:

Machineepel-release buildMetalink nameTreePackages
CentOS Stream 1010-9.el10s (extras-common)epel-10s10.426,454
CentOS Stream 1010-8.el10_2 (permalink)epel-1010.325,993
RHEL 10.110-8.el10_2 (permalink)epel-z-1010.223,984

The middle row is the odd one. The permalink’s repo file only branches on releasever_minor, which Stream never sets, so on Stream it falls through to plain epel-10. That is a working tree, one minor behind what the stream-aware file asks for, and dnf moves the box off it on its own, as the permalink section below shows. On RHEL the permalink does what it was written for and lands on epel-z-10.

The fourth combination, the current build on RHEL, is EPEL’s announced plan rather than something we measured. RHEL defines no stream variable, so on RHEL the stream-aware line expands to plain epel-10. The EPEL Steering Committee approved that switch on August 12, 2026 (steering issue 372), and EPEL published the new epel-release only in the 10.3 tree so CentOS users get it first. RHEL boxes receive it once RHEL 10.3 is released, after which epel-10 becomes the normal RHEL name for the current RHEL minor and epel-z-10 keeps moving forward for compatibility. On October 15, 2026 epel-z-10 still pointed at 10.2 and the 10.2 tree still carried only the 10-8 build, so the RHEL side had not switched yet. We also checked the explicit names the same day: epel-10.4, epel-10.3 and epel-10.2 each served their own tree, while epel-z-10.4 returned no mirrors.

Prerequisites

EPEL itself installs on almost any EL10 machine, but whether its packages install depends on the repositories underneath it. Before you start, have these in place:

  • A registered RHEL 10 system, or CentOS Stream 10. Registration is what gives RHEL its BaseOS, AppStream and CodeReady Builder repositories, and our RHEL 10 install walkthrough covers it. Our RHEL 10.1 test VM was unregistered, and the failures that caused are shown later.
  • A user with sudo rights. Every install and repository change below runs through sudo, while read-only checks such as dnf repoinfo don’t need it.
  • HTTPS access to the Fedora mirror network. dnf fetches the release RPM from dl.fedoraproject.org and then asks mirrors.fedoraproject.org for a mirror list, so a proxy or firewall has to allow both, plus the mirrors they hand back.
  • A RHEL rebuild instead of RHEL. The EPEL FAQ says its packages will likely work on distributions that target RHEL compatibility, but we only tested RHEL and CentOS Stream here. Check the stream variable on a rebuild: the EPEL discussion in July 2026 noted that AlmaLinux’s release package defines it (RHEL and Oracle Linux do not), which would send a box with the current epel-release to the Stream tree, and AlmaLinux said in August it would adjust for EPEL 11 but had not discussed the change for 10. The variables check above tells you which case you’re in. For Oracle Linux, see the Oracle Linux EPEL guide.

Both of our test machines ran dnf 4, not dnf 5, so the config-manager --set-enabled syntax used below is the right one for EL10. If you’re used to Fedora’s dnf5 options, our dnf guide for RHEL 10 and its rebuilds covers the rest of the command set.

Install EPEL on RHEL 10 and CentOS Stream 10

Both systems take the same two steps in the same order. First turn on the CRB repository, because many EPEL packages depend on it, then install an epel-release package, which drops the repo file, the EPEL 10 signing key and a small crb helper script. Only the commands differ between RHEL and Stream, and on Stream you don’t download anything from Fedora to get started.

RHEL 10: enable CodeReady Builder, then install the permalink RPM

On RHEL, CRB is called CodeReady Linux Builder and it is a subscription repository, so subscription-manager turns it on. The command below is the one the EPEL getting-started page gives for RHEL 10, and Red Hat’s article on CodeReady Linux Builder covers the repository itself. We did not run it on a registered machine, because our RHEL 10.1 VM had no subscription, so treat this one step as documented rather than tested:

sudo subscription-manager repos --enable codeready-builder-for-rhel-10-$(arch)-rpms

$(arch) expands to x86_64 or aarch64, so the same line covers both architectures. On an unregistered host it stops with this message and exit code 1, which tells you registration is the problem before EPEL is even involved:

This system has no repositories available through subscriptions.

If your RHEL repositories come from a cloud provider’s Red Hat Update Infrastructure (RHUI) rather than from subscription-manager, enable CodeReady Builder with dnf instead. Look the repository ID up in dnf repolist --all and pass it to dnf config-manager --set-enabled. The crb script that epel-release installs does that same lookup when you run it as sudo FORCE_DNF=1 crb enable (putting the variable before sudo loses it to sudo’s environment reset), because otherwise it always calls subscription-manager on RHEL. We read this from the script in the current epel-release and had no RHUI instance to test it on.

Our CRB guide for RHEL 10 and Rocky Linux 10 covers that repository on its own. The EPEL package does not need any Red Hat repository, because dnf downloads the RPM from the permalink and installs it from the command line. On our unregistered VM it took 1.6 seconds:

sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm

The transaction installs a single package from @commandline. Both epel-release builds we installed also print a reminder that many EPEL packages need CRB and that /usr/bin/crb enable turns it on, which matters again in the failures section:

 Package             Architecture  Version            Repository           Size
================================================================================
Installing:
 epel-release        noarch        10-8.el10_2        @commandline         19 k

Refresh the metadata so dnf contacts the mirror network for the first time, then ask it which tree it chose. The metalink name and the path in the base URL are the two lines that matter:

sudo dnf makecache
dnf repoinfo epel 2>&1 | grep -E "Repo-(id|name|pkgs|metalink|baseurl)"

Here the refresh took 2.7 seconds for 6.7 MB of metadata, after the same not-registered notice the install printed. A 10.1 machine reading the 10.2 tree is the minor-aware mapping from earlier, visible as epel-z-10 in the metalink and 10.2 in the mirror path:

Updating Subscription Management repositories.
Unable to read consumer identity

This system is not registered with an entitlement server. You can use "rhc" or "subscription-manager" to register.

Extra Packages for Enterprise Linux 10 - x86_64  30 MB/s | 6.7 MB     00:00
Last metadata expiration check: 0:00:01 ago on Thu Oct 15 07:34:31 2026.
Metadata cache created.
Repo-id            : epel
Repo-name          : Extra Packages for Enterprise Linux 10 - x86_64
Repo-pkgs          : 23984
Repo-metalink      : https://mirrors.fedoraproject.org/metalink?repo=epel-z-10&arch=x86_64
Repo-baseurl       : http://mirror.dogado.de/fedora-epel/10.2/Everything/x86_64/ (55 more)

That metalink name is due to change on RHEL. Under EPEL’s announced plan, once RHEL 10.3 is released a routine dnf upgrade brings the stream-aware epel-release over, the metalink becomes epel-10, and both names point at the current RHEL tree, so epel-10 on a RHEL box is expected from then on. A small, self-contained package proves the repository works end to end, and the first install from EPEL also imports its signing key:

sudo dnf install -y ncdu

ncdu installed in 1.7 seconds. Because it was the first package from EPEL on this box, dnf imported the EPEL 10 key on the way:

Importing GPG key 0xE37ED158:
 Userid     : "Fedora (epel10) <[email protected]>"
 Fingerprint: 7D8D 15CB FC4E 6268 8591 FB26 33D9 8517 E37E D158
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-10
Key imported successfully

Because of -y, dnf accepted the key without a prompt. Its ID, E37ED158, is the key ID rpm printed for the permalink RPM’s own signature, so the release package and the repository share one EPEL 10 key. The ncdu install worked because it needs nothing beyond what an unregistered RHEL box already has. Packages that need BaseOS or AppStream libraries didn’t, and the failures section shows exactly what that looks like.

CentOS Stream 10: enable crb, then install epel-release from extras

CentOS Stream has no subscription layer, so CRB is an ordinary repository that ships disabled on a fresh install. Stream 10 still runs dnf 4, which makes config-manager --set-enabled the syntax to use, and listing the repositories straight after shows whether it took:

sudo dnf config-manager --set-enabled crb && dnf repolist

The enabled list should now show crb next to BaseOS, AppStream and the extras repository. If crb is missing, EPEL still installs, and the gap only shows up later as a nothing provides error when a package needs it:

repo id                       repo name
appstream                     CentOS Stream 10 - AppStream
baseos                        CentOS Stream 10 - BaseOS
crb                           CentOS Stream 10 - CRB
extras-common                 CentOS Stream 10 - Extras packages

On Stream you do not need the permalink at all, because CentOS carries epel-release in that extras-common repository. Listing it alongside epel-next-release shows three builds of the first and none of the second:

dnf list --showduplicates epel-release epel-next-release 2>&1 | tail -5

Every match comes from extras-common, and nothing named epel-next exists for Stream 10. The el10s suffix is how you tell these CentOS builds apart from the el10_x builds EPEL ships itself:

CentOS Stream 10 - Extras packages               97 kB/s |  11 kB     00:00
Available Packages
epel-release.noarch                   10-2.el10s                   extras-common
epel-release.noarch                   10-5.el10s                   extras-common
epel-release.noarch                   10-9.el10s                   extras-common

EPEL’s docs recommend the permalink on every system, and it does work on Stream, but it currently installs an older build that reads a different tree until its first upgrade. We’d install the extras package on Stream, because it’s the newer build and it writes the stream-aware repo file from the start:

sudo dnf install -y epel-release

The install took 14.4 seconds. Because crb was already enabled, dnf also pulled two SELinux policy packages from it, one of them as a weak dependency:

Installing:
 epel-release                   noarch  10-9.el10s         extras-common   19 k
Installing dependencies:
 selinux-policy-targeted-extra  noarch  42.1.27-2.el10     crb            775 k
Installing weak dependencies:
 selinux-policy-extra           noarch  42.1.27-2.el10     crb             38 k

On our fresh image this was also the first transaction that installed anything from crb or extras-common, so dnf imported two keys: the CentOS official key for the crb packages and the CentOS Extras SIG key for epel-release. On an image that has already installed or updated packages, expect only the Extras SIG import. The second import prints a SHA1 policy warning that looks alarming and isn’t, which the failures section explains. Refresh the metadata next and check what dnf resolved. The repository details should name epel-10s and a 10.4 path:

sudo dnf makecache
dnf repoinfo epel 2>&1 | grep -E "Repo-(id|pkgs|metalink|baseurl)"

Stream’s refresh took 5.2 seconds, with 7.3 MB of EPEL metadata against 6.7 MB on RHEL because the Stream tree is the bigger one, and both expected values are there:

CentOS Stream 10 - BaseOS                        32 kB/s |  13 kB     00:00
CentOS Stream 10 - AppStream                    295 kB/s |  13 kB     00:00
CentOS Stream 10 - CRB                          230 kB/s |  12 kB     00:00
CentOS Stream 10 - Extras packages               27 kB/s |  14 kB     00:00
Extra Packages for Enterprise Linux 10 - x86_64  13 MB/s | 7.3 MB     00:00
Metadata cache created.
Repo-id            : epel
Repo-pkgs          : 26454
Repo-metalink      : https://mirrors.fedoraproject.org/metalink?repo=epel-10s&arch=x86_64
Repo-baseurl       : http://mirror.23m.com/epel/10.4/Everything/x86_64/ (57 more)

That’s the stream-aware result. On a Stream box, epel-10 and a 10.3 path instead would mean you’re on the permalink build, which the next section deals with. A handful of everyday tools makes a good smoke test, because between them they pull dependencies from BaseOS, AppStream and EPEL itself:

sudo dnf install -y htop btop ncdu fastfetch

Nine packages, 4.6 MB, installed in 5.9 seconds. The tools themselves came from epel while their libraries came from baseos and appstream, and rocm-smi came along as a weak dependency:

Installing:
 btop                    x86_64      1.4.7-1.el10_3        epel           629 k
 fastfetch               x86_64      2.68.1-1.el10_4       epel           669 k
 htop                    x86_64      3.3.0-5.el10_0        epel           196 k
 ncdu                    x86_64      1.22-1.el10_1         epel            62 k
Installing dependencies:
 hicolor-icon-theme      noarch      0.17-20.el10          appstream      219 k
 hwloc-libs              x86_64      2.11.1-4.el10         baseos         2.1 M
 ocl-icd                 x86_64      2.3.2-8.el10          baseos          67 k
 yyjson                  x86_64      0.12.0-1.el10_2       epel           121 k
Installing weak dependencies:
 rocm-smi                x86_64      7.2.1-2.el10_3        epel           614 k

dnf records where each installed package came from, which is the quickest way to confirm a tool really came from EPEL and not from a repository you forgot about. The capture below shows the final repository state on the Stream box and the origin of all four tools, and our btop and gotop walkthrough covers btop itself:

EPEL on CentOS Stream 10: dnf repolist, epel-10s metalink and htop, btop, ncdu, fastfetch installed from epel

All four report From repo : epel. Their release tags run from el10_0 to el10_4, so the leading tree carries builds made for several minors at once, and a tool nobody has rebuilt since the first minor is still current there.

It downgrades epel-release, moves the machine to an older tree, and then puts it back on the next upgrade. Provisioning code that follows the EPEL docs installs the permalink on every EL10 host regardless of flavour, so we ran it on top of the extras package to see what a Stream box does with it:

sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm 2>&1 | tail -4

dnf reports a downgrade, not an install. That one word is the first sign that the permalink is older than what CentOS ships:

Downgraded:
  epel-release-10-8.el10_2.noarch

Complete!

dnf has the version ordering right, because the permalink still serves 10-8.el10_2, one build behind the 10-9 that CentOS ships. The repo file changes with it. Since the permalink’s metalink keys only on releasever_minor, which Stream leaves empty, the box drops to plain epel-10:

rpm -q epel-release; sudo dnf makecache 2>&1 | grep -i epel; dnf repoinfo epel 2>&1 | grep -E "Repo-(pkgs|metalink|baseurl)"

Both the package count and the mirror path moved down a minor. Nothing else on the box changed, so the difference comes entirely from the repo file:

epel-release-10-8.el10_2.noarch
Repo-pkgs          : 25993
Repo-metalink      : https://mirrors.fedoraproject.org/metalink?repo=epel-10&arch=x86_64
Repo-baseurl       : http://ftp-stud.hs-esslingen.de/pub/epel/10.3/Everything/x86_64/ (77 more)

The machine now reads the 10.3 tree, 461 packages smaller than 10.4. The correction comes from EPEL itself. Its repository carries a newer epel-release, so a routine upgrade replaces the permalink build without any help from you:

sudo dnf upgrade -y epel-release 2>&1 | grep -E "epel-release|Upgraded|Complete"; rpm -q epel-release; sudo dnf makecache 2>&1 | grep -i epel; dnf repoinfo epel 2>&1 | grep -E "Repo-(pkgs|metalink|baseurl)"

dnf pulled 10-9.el10_3 from the epel repository rather than from extras-common, and that build writes the stream-aware metalink again:

 epel-release          noarch          10-9.el10_3          epel           19 k
epel-release-10-9.el10_3.noarch.rpm             948 kB/s |  19 kB     00:00
  Upgrading        : epel-release-10-9.el10_3.noarch                        1/2
  Running scriptlet: epel-release-10-9.el10_3.noarch                        1/2
  Cleanup          : epel-release-10-8.el10_2.noarch                        2/2
  Running scriptlet: epel-release-10-8.el10_2.noarch                        2/2
Upgraded:
  epel-release-10-9.el10_3.noarch
Complete!
epel-release-10-9.el10_3.noarch
Repo-pkgs          : 26454
Repo-metalink      : https://mirrors.fedoraproject.org/metalink?repo=epel-10s&arch=x86_64
Repo-baseurl       : http://mirror.23m.com/epel/10.4/Everything/x86_64/ (57 more)

The box is back on epel-10s and the 10.4 tree with 26,454 packages, and nobody edited the repo file. On Stream, then, the permalink and the extras package converge after one upgrade, and the only real difference is which tree you read in between. If your provisioning already installs the permalink everywhere, add an epel-release upgrade to the same run. If you’re writing it fresh, install from extras on Stream and skip the detour.

Why EPEL packages fail to install on RHEL 10 and CentOS Stream 10

EPEL packages are built against the full base system, meaning BaseOS, AppStream and CRB together, and EPEL doesn’t replace packages that RHEL provides, so dnf fails as soon as one of the three is missing. The error has the same shape every time, nothing provides X needed by Y from epel, and X tells you which repository you are short of. These are the failures and the one alarming warning we hit across both machines.

nothing provides texinfo needed by octave from epel

This is the classic missing-CRB failure. To reproduce it we disabled crb on the Stream box on purpose and asked for a dry run of octave, where --assumeno resolves the transaction and then declines it. You don’t need to run this; it’s here so the error below has its command:

sudo dnf config-manager --set-disabled crb && sudo dnf install --assumeno octave 2>&1 | tail -3

The transaction stopped at the dependency check:

 Problem: conflicting requests
  - nothing provides texinfo needed by octave-6:11.1.0-2.el10_3.x86_64 from epel
(try to add '--skip-broken' to skip uninstallable packages or '--nobest' to use not only best candidate packages)

texinfo lives in CRB, so turning CRB on is the cure, not --skip-broken, which would only drop octave from the transaction. The crb script that epel-release installs handles it, and on Stream it names the repository it turned on:

sudo crb enable

The second line names the repository ID it enabled, crb, which is the same one config-manager turned on earlier, so either route leaves the box in the same state:

Enabling CRB repo
CRB repo is enabled and named: crb

Repeat the dry run now that CRB is on, filtering the output down to the lines that matter. It still changes nothing on disk:

sudo dnf install --assumeno octave 2>&1 | grep -E "^ (octave|texinfo) |^Install |^Total download"

texinfo now comes from crb and octave from epel, and the totals, 365 packages for one application, show why we stopped at a dry run:

 octave                                    x86_64  6:11.1.0-2.el10_3                 epel        19 M
 texinfo                                   x86_64  7.1-6.el10                        crb        1.3 M
Install  365 Packages
Total download size: 443 M

443 MB for a package with a scientific stack behind it is not unusual, and it is the reason we’d dry-run any large EPEL install with --assumeno before committing a production box to it. You see the dependency tree, the repositories it draws from and the download size, and nothing changes on disk.

nothing provides libhwloc.so.15()(64bit) needed by htop

This one came from the unregistered RHEL 10.1 VM. EPEL installed fine and ncdu installed fine, and then htop and btop failed together:

Error:
 Problem 1: conflicting requests
  - nothing provides libhwloc.so.15()(64bit) needed by htop-3.3.0-5.el10_0.x86_64 from epel
 Problem 2: conflicting requests
  - nothing provides hicolor-icon-theme needed by btop-1.4.7-1.el10_2.x86_64 from epel

Neither dependency is an EPEL package. On the Stream box the same install took hwloc-libs from baseos and hicolor-icon-theme from appstream, and an unregistered RHEL machine has no BaseOS or AppStream at all, which dnf repolist --all confirms by printing No repositories available. Registering the system brings those repositories in. We did not register this VM, so that last step is Red Hat’s documented path rather than something we ran. The capture shows the failure next to the state of EPEL on that box:

EPEL on RHEL 10: htop and btop fail on missing BaseOS/AppStream deps while ncdu installs from epel-z-10

What still worked is telling. ncdu installed because it depends only on libraries every RHEL install already has. When a package installs on one RHEL host and fails on another, compare dnf repolist on both before you suspect EPEL itself.

subscription-manager: error: argument –enable: expected one argument

This is what crb enable prints on a RHEL machine with no subscription, and the exit code is the trap. The script found no CodeReady Builder repository to name, called subscription-manager’s --enable with nothing after it, and still returned 0:

sudo crb status; echo "exit=$?"; sudo crb enable; echo "exit=$?"

Status says disabled before and after, while both exit codes say success. Only the subscription-manager lines in the middle give the failure away:

CRB repo is disabled
exit=0
Enabling CRB repo
subscription-manager: error: argument --enable: expected one argument
usage: subscription-manager repos [OPTIONS]
CRB repo is disabled
exit=0

A provisioning script that runs crb enable and checks $? would carry on as if CRB were enabled. Read the status text instead, or look for the CodeReady Builder repository in dnf repolist. We could not test the registered case, where crb enable has a real repository to pass along.

Policy rejects subkey because SHA1 is not considered secure

You’ll see this on CentOS Stream 10 the first time dnf touches extras-common, which on a fresh image is the epel-release install itself. dnf imports the CentOS Extras SIG key and prints:

Importing GPG key 0x1D997668:
 Userid     : "CentOS Extras SIG (https://wiki.centos.org/SpecialInterestGroup) <[email protected]>"
 Fingerprint: 363F C097 2F64 B699 AED3 968E 1FF6 A217 1D99 7668
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Extras-SHA512
warning: Certificate 1FF6A2171D997668:
  Policy rejects subkey 8B5C8111FCA5D0FF: Policy rejected non-revocation signature (PrimaryKeyBinding) requiring second pre-image resistance, because SHA1 is not considered secure
Key imported successfully

It’s a warning, not an error. One subkey of that certificate carries a SHA1 binding signature, the signature policy refuses SHA1, so that subkey is ignored and the key is imported anyway, which is what the last line says. The epel-release transaction finished normally on our box, so there is nothing to fix.

What EPEL 10 doesn’t carry, and what to use instead

EPEL 10 is not a copy of EPEL 9. Each package needs a maintainer to request an epel10 branch, so tools that were a given on EL9 can be absent or ship under a new name. The inxi package, which the previous version of this guide recommended, is simply gone. We checked four names on the Stream box against the 10.4 tree:

htop --version; ncdu --version; fastfetch --version; for p in inxi neofetch the_silver_searcher p7zip; do printf "%-20s " $p; dnf repoquery --latest-limit 1 --arch x86_64,noarch --qf "%{name}-%{version}-%{release} %{repoid}" $p 2>/dev/null | head -1 | grep . || echo "NOT FOUND"; done

All three installed tools answer, and all four lookups print NOT FOUND. Read that as “no package by this name” and nothing more. According to dnf’s documentation, repoquery matches a spec against package names and file paths only. Install falls back to what packages provide when no name matches, and that gap matters for one of the four:

htop 3.3.0
ncdu 1.22
fastfetch 2.68.1 (x86_64)
inxi                 NOT FOUND
neofetch             NOT FOUND
the_silver_searcher  NOT FOUND
p7zip                NOT FOUND

The p7zip name is gone, but its replacement claims it. The repoquery loop above matches package names only, and dnf install also matches what a package provides. On our CentOS Stream 10 box with EPEL enabled, asking dnf who provides p7zip pointed straight at 7-Zip’s standalone build:

dnf repoquery --whatprovides p7zip

After the metadata refresh lines, the answer is a single package:

7zip-standalone-0:26.02-1.el10_3.x86_64

A dry run of sudo dnf install --assumeno p7zip confirmed it: the transaction resolved to 7zip-standalone 26.04 from epel, 744 k to download. That package ships 7za, the same command the old p7zip package gave you, but not 7z. Old scripts that call 7z want the full 7zip package, which installed in 1.6 seconds and printed 7-Zip 26.04 (x64) from 7z i. The two version strings are not a typo: the repoquery and the install hit different mirrors a few seconds apart.

We’d reach for these instead, based on what the same trees carry:

  • neofetch: fastfetch is in EPEL 10 and installed cleanly in the smoke test above, so it’s the drop-in for a system summary at login.
  • inxi: nothing in EPEL 10 provides it. fastfetch covers the quick OS and hardware summary, but not inxi’s sensor and driver detail, and we didn’t test a replacement for that part.
  • p7zip: sudo dnf install 7zip for the 7z command, or 7zip-standalone (what the p7zip name resolves to) for 7za. The same source package also builds 7zip-reduced for 7zr and 7zip-standalone-all for 7zz.
  • the_silver_searcher: nothing in EPEL 10 provides it or ships an ag binary. ripgrep is in EPEL 10 (repoquery showed 15.2.0 on our Stream box) and ships rg, and it’s the search tool we’d use instead, though we did not install it in this lab.

Large stacks did make the move. KDE Plasma is in EPEL 10 along with its package groups, so a Plasma desktop on an EL10 machine is still an EPEL install:

dnf repoquery --latest-limit 1 --qf "%{name}-%{version}-%{release} %{repoid}" plasma-desktop plasma-workspace 2>/dev/null; dnf group list --hidden 2>/dev/null | grep -i -E "kde|plasma"

Both core Plasma packages resolve from epel, and the group list includes the full workspace:

plasma-desktop-6.7.5-1.el10_4 epel
plasma-workspace-6.7.5-1.el10_4 epel
   KDE Plasma Workspaces
   KDE Plasma Mobile
   Critical Path (KDE)
   KDE Applications
   KDE
   KDE Multimedia support
   KDE Mobile
   KDE PIM
   KDE Software Development
   KDE Spin Initial Setup
   KDE Frameworks 6 Software Development

For automation, the lesson is to ask the repository the right question. A name lookup misses renamed packages, so check with dnf repoquery --whatprovides before a script decides a tool is missing. It’s a one-line check, and an image build that fails halfway on a missing package costs far more than writing it.

No match for argument: inxi

If you install inxi directly, as older EPEL guides told you to, dnf fails with exit code 1 because the package doesn’t exist in any enabled repository:

sudo dnf install -y inxi; echo "exit=$?"

dnf finds no match anywhere and stops before touching the system. Unlike repoquery, install also checks what packages provide, so this rules out a renamed package too:

No match for argument: inxi
Error: Unable to find a match: inxi
exit=1

Nothing is wrong with your EPEL setup when you see this. The same box installed four other EPEL tools moments earlier, so the message means the package isn’t built for EL10, and what you need is a different tool, not a different repository.

Common misconceptions about EPEL 10

Most of the confusion around EPEL 10 comes from carrying EPEL 9 habits forward. Each of these came up while we tested, and each one is settled by something the lab showed:

  • “EPEL 10 is one repository, like EPEL 9.” It’s one repository per RHEL minor. On the same day, our RHEL 10.1 VM read the 10.2 tree with 23,984 packages and our Stream VM read 10.4 with 26,454.
  • “CentOS Stream 10 needs epel-next.” There is no epel-next for EL10. The extras repository offers only epel-release, and Stream reaches its own tree through the stream dnf variable instead.
  • “The epel-release in CentOS extras is a fork to avoid.” It’s the newer build. The permalink downgraded it, and EPEL’s own repository then upgraded the box to a build with the same stream-aware metalink.
  • “RHEL 10.1 reads the 10.1 tree.” epel-z-10 redirects to the current RHEL minor, which was 10.2 on our test day, and the metalinks for the 10.0 and 10.1 trees now point at archive.fedoraproject.org.
  • “epel-10 in the metalink means a CentOS box.” Today it means a Stream box on the older permalink build. Under the plan EPEL approved in August 2026, epel-10 becomes the RHEL name once RHEL 10.3 ships, and Stream already moved to epel-10s in August.
  • “crb enable worked because it exited 0.” On unregistered RHEL it printed a subscription-manager usage error, left CRB disabled and still returned 0, so read crb status instead.
  • “Installing epel-release is enough.” It only adds a repo file, a key and the crb script. octave failed on texinfo until CRB was on, and htop and btop failed on BaseOS and AppStream libraries on the unregistered RHEL box.

The mental model that holds all of this together is that epel-release is a pointer, not a repository. The build you install and the variables your OS sets decide which minor tree it points at, and CRB plus the base repositories decide whether what you find there can actually install. Run dnf repoinfo epel once after setup and read the metalink name together with the OS. Expect epel-10s on Stream, epel-z-10 on RHEL today and epel-10 on RHEL once 10.3 is out, while epel-10 on a Stream box means the older permalink build. That is usually the first thing to check when the same EPEL package behaves differently on two hosts.

Keep reading

Patch PHP2Shell on RHEL, Rocky Linux and AlmaLinux 10 / 9 / 8 AlmaLinux Patch PHP2Shell on RHEL, Rocky Linux and AlmaLinux 10 / 9 / 8 Customize KDE Plasma Desktop with Themes|Modules|Extensions AlmaLinux Customize KDE Plasma Desktop with Themes|Modules|Extensions How To Extract .xz files on a Linux System CentOS How To Extract .xz files on a Linux System Best RHCSA Books for 2026: RHEL 10 (EX200) and RHCE Study Guides RHEl Best RHCSA Books for 2026: RHEL 10 (EX200) and RHCE Study Guides RHCSA Practice Questions: Hands-On EX200 Tasks with Solutions RHEl RHCSA Practice Questions: Hands-On EX200 Tasks with Solutions Stratis Storage Cheat Sheet – reference guide Cheat Sheets Stratis Storage Cheat Sheet – reference guide

1 thought on “How to Install and Enable EPEL on RHEL 10 and CentOS Stream 10”

Leave a Comment

Press ESC to close