RPM HOWTO: query, verify, build and sign RPM packages
RPM is the package format and low-level tool behind Fedora, RHEL, AlmaLinux, Rocky Linux and openSUSE. Use dnf (or zypper on openSUSE) to install and update software, because it resolves dependencies. Use rpm to ask questions: which package owns a file, what changed since install, whether a package file is signed. To make your own packages, you write a spec file and run rpmbuild.
- 6.1.0latest RPM release (Aug 20, 2026), as of November 2026
- 6.1.0RPM in Fedora 45 (6.0.2 in Fedora 43 and 44)
- 4.19RPM in RHEL/Alma/Rocky 10 (4.16 in 9)
- dnf5the
dnfcommand since Fedora 41; RHEL 10 still uses DNF 4
RPM 6.0 (September 2025) can read and write a new v6 package format, supports several OpenPGP signatures per package and newer key types, and dropped support for installing very old v3 packages. RPM 6.1 (August 2026) is a feature update on top of it. As of November 2026, Fedora 45 (released November 3, 2026) ships RPM 6.1.0 and Fedora 43 and 44 ship RPM 6.0.2. All three still produce v4 packages by default, so files built there install on older systems. Fedora 45 is also the first Fedora release in which rpm itself refuses unsigned packages (see below).
Install, update and remove
sudo dnf install httpd # install with dependencies
sudo dnf install ./hello-1.0-1.fc45.noarch.rpm # a local file, still resolving dependencies
sudo dnf upgrade # update everything
sudo dnf remove httpd
dnf provides /usr/bin/dig # which package would give me this file?
dnf history # past transactions
sudo dnf history undo last # roll back the last transaction (DNF 4 and dnf5)
You can still run sudo rpm -Uvh file.rpm (install or upgrade, verbose, with progress bar), but it stops at the first missing dependency and never downloads anything. rpm -i is right mainly for kernels, where several versions are meant to stay installed side by side.
Query installed packages
rpm -qa | sort # every installed package
rpm -qa --last | head # most recently installed first
rpm -qi openssh-server # name, version, vendor, build date, signature, description
rpm -ql openssh-server # files in the package
rpm -qc openssh-server # only its configuration files
rpm -qf /etc/ssh/sshd_config # which package owns this file?
rpm -q --changelog openssl | head
rpm -q --scripts httpd # install/remove scriptlets
rpm -q --whatrequires bash # installed packages that need bash
rpm -qpl ./hello-1.0-1.fc45.noarch.rpm # -p: ask a package file, not the database
rpm -qf answers "where did this come from?" in a second. If it prints "not owned by any package", someone installed the file by hand.
A query format prints exactly the fields you want, which is handy in scripts:
rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\n' | sort | head -3
Verify files against the database
RPM remembers the size, permissions, owner, digest and time of every file it installed. rpm -V compares them with what is on disk now and prints only the files that differ:
$ sudo rpm -V openssh-server
S.5....T. c /etc/ssh/sshd_config
| Letter | Meaning | Letter | Meaning |
|---|---|---|---|
S | file size differs | U | user owner differs |
M | mode (permissions or type) differs | G | group owner differs |
5 | digest (content) differs | T | modification time differs |
D | device number differs | P | capabilities differ |
L | symlink target differs | . / ? | test passed / could not run |
The letter after the codes marks the file type: c configuration, d documentation, l license, g ghost. A changed c file is usually your own edit. A changed binary in /usr/bin is not normal. Check the whole system with sudo rpm -Va (it takes a few minutes).
Not a security scanner. An attacker with root can change the RPM database too. For tamper detection, compare against a trusted copy or use a file-integrity tool such as AIDE; see the Security HOWTO.
Check signatures and keys
Distribution packages are signed with OpenPGP keys. dnf checks them automatically using the keys listed in each repository's gpgkey= setting. To check a downloaded file by hand:
rpmkeys -K hello-1.0-1.fc45.noarch.rpm # same as: rpm -K
rpmkeys -Kv hello-1.0-1.fc45.noarch.rpm # per-check detail
If the key is missing, the output says the signature is not OK or the key is unknown. Import a vendor key only from a source you trust, such as the file the distribution already ships in /etc/pki/rpm-gpg/:
ls /etc/pki/rpm-gpg/ /usr/share/pki/rpm-gpg/ # keys shipped by the distribution
sudo rpmkeys --import /etc/pki/rpm-gpg/RPM-GPG-KEY-<name>
rpmkeys --list # RPM 4.20 and newer (Fedora 41+)
rpm -q gpg-pubkey # older RPM (RHEL 9 and 10): keys appear as gpg-pubkey packages
On Fedora 45 the distribution keys moved to /usr/share/pki/rpm-gpg/ and the packaged .repo files to /usr/share/dnf5/repos.d/; /etc/yum.repos.d/ is still read for your own repositories. RPM 6 identifies keys by their full fingerprint everywhere and enforces signature checking by default upstream. Fedora 43 and 44 keep the old behavior (check signatures if present, never require them). Fedora 45 switched enforcement on: rpm refuses to install, and rpmkeys -K fails on, a package without a valid signature from an imported key, unless you add --nosignature. Packages from signed repositories are not affected; dnf has always required signatures for repositories with gpgcheck=1.
Build a package
Never build packages as root: a mistake in the spec file could overwrite system files. Install the tools and create the build tree in your home directory:
sudo dnf install rpm-build rpmdevtools rpmlint
rpmdev-setuptree # creates ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
Make a small source tarball. Here the "software" is a shell script plus a license file:
mkdir hello-1.0
printf '#!/bin/bash\necho "Hello, world"\n' > hello-1.0/hello
printf 'MIT License\n' > hello-1.0/LICENSE # use the real license text
tar -czf ~/rpmbuild/SOURCES/hello-1.0.tar.gz hello-1.0
Write ~/rpmbuild/SPECS/hello.spec (rpmdev-newspec hello gives you a template):
Name: hello
Version: 1.0
Release: 1%{?dist}
Summary: Print a friendly greeting
License: MIT
URL: https://example.com/hello
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
Requires: bash
%description
A tiny shell script that prints a greeting. It shows how RPM packaging works.
%prep
%setup -q
%build
# nothing to compile for a shell script
%install
install -D -m 0755 %{name} %{buildroot}%{_bindir}/%{name}
%files
%license LICENSE
%{_bindir}/%{name}
%changelog
* Wed Nov 04 2026 Your Name <you@example.com> - 1.0-1
- First package
- Header
Name,VersionandReleaseform the package file name.%{?dist}adds the distribution tag, such as.fc45.BuildArch: noarchis for packages with no compiled code.%prep- Unpacks the source.
%setup -qextractsSource0and changes intohello-1.0/. %build- Compiles. For a C program using autotools this is usually
%configureand%make_build; then use%make_installin%install. %install- Copies files into the fake root
%{buildroot}. Use macros such as%{_bindir}(/usr/bin);rpm --eval %{_bindir}shows any macro's value. %files- Lists every file the package owns. A file installed but not listed stops the build with an "unpackaged files" error.
Build the source and binary packages, then check them:
cd ~/rpmbuild/SPECS
rpmbuild -ba hello.spec
rpmlint hello.spec ~/rpmbuild/RPMS/noarch/hello-1.0-1.*.noarch.rpm
rpm -qpl ~/rpmbuild/RPMS/noarch/hello-1.0-1.*.noarch.rpm
The end of the rpmbuild output shows where the files went, with lines like Wrote: /home/you/rpmbuild/RPMS/noarch/hello-1.0-1.fc45.noarch.rpm and one for the .src.rpm in SRPMS/. Use -bb for the binary package only, -bs for the source package only, and rpmbuild --rebuild file.src.rpm to rebuild someone else's source package. For packages that need compilers and libraries, sudo dnf builddep hello.spec installs everything listed in BuildRequires:.
Fedora 45: local builds are unsigned. Because rpm now enforces signatures, a package you just built will not install with rpm until it is signed. For local work, run rpm-setup-autosign once (RPM 6.1 puts it in the normal $PATH; on RPM 6.0 it is /usr/lib/rpm/rpm-setup-autosign) and import the key it reports; after that rpmbuild signs every package automatically. For a one-off test, sudo rpm -i --nosignature file.rpm skips the check. Do not turn enforcement off system-wide.
Build in a clean chroot with mock
A package that builds on your workstation may only work because of something you happen to have installed. mock builds in a fresh chroot, and can target other releases:
sudo dnf install mock
sudo usermod -a -G mock "$USER" # log out and back in
mock -r fedora-45-x86_64 ~/rpmbuild/SRPMS/hello-1.0-1.fc45.src.rpm
On RHEL and its rebuilds, mock comes from EPEL. Membership in the mock group is close to root access, so give it only to trusted users.
Sign your packages
Create a signing key with GnuPG (gpg --full-generate-key), then tell RPM which key to use. With RPM 4 (RHEL 9 and 10), put the key's user ID in ~/.rpmmacros:
%_gpg_name Your Name <you@example.com>
With RPM 6 (Fedora 43 and newer), use the key's full fingerprint (gpg --list-keys --with-fingerprint shows it) in ~/.config/rpm/macros; %_gpg_name still works but is now the legacy name:
%_openpgp_sign_id <your-key-fingerprint>
Sign, publish the public key, and check from a client:
sudo dnf install rpm-sign
rpmsign --addsign ~/rpmbuild/RPMS/noarch/hello-1.0-1.fc45.noarch.rpm
gpg --export --armor you@example.com > RPM-GPG-KEY-example
# on a client:
sudo rpmkeys --import RPM-GPG-KEY-example
rpmkeys -K hello-1.0-1.fc45.noarch.rpm
For a repository, run createrepo_c on the directory of signed files and give clients a .repo file with gpgcheck=1 and gpgkey= pointing at your public key. RPM 6 can also use Sequoia PGP instead of GnuPG (%_openpgp_sign sq), and rpmsign --resign replaces all old signatures.
Mistakes to avoid
- Do not use
rpm -e --nodepsorrpm -i --forceto get past a dependency problem. You end up with a system thatdnfcannot update cleanly. - Do not edit files in
/usrthat belong to packages; the next update overwrites them. Put changes in/etcor/usr/local. - Bump
Releaseevery time you rebuild a changed spec, or clients will not see the update. - Never turn off
gpgcheckto make a repository "work". Import the right key instead.
The classic 1999 RPM HOWTO is kept by the LDP at tldp.org. For release practices such as versioning and signed tags, see the Software Release Practice HOWTO.
Sources
- RPM Package Manager news and releases, rpm.org
- RPM 6.1.0 release notes, rpm.org, RPM 6.0.0 release notes and RPM 4.20.0 release notes
- rpm-config(5), rpm.org
- rpm(8), rpm.org
- rpmkeys(8), rpm.org and rpmsign(1), rpm.org
- RPM Packaging Guide
- Changes/RPM-6.1, Changes/Enforcing signature checking by default and Changes/RelocateRpmRepoConfigsToUsr, Fedora Project
- Changes/RPM-6.0, Fedora Project and Changes/SwitchToDnf5, Fedora Project
- DNF command reference and dnf5 history, dnf5 builddep
- Mock documentation
- AlmaLinux 10 BaseOS packages (rpm and dnf versions)