How to Sign RPM Packages with GPG Keys

Generate a GPG key pair, import the public key into the RPM database, and use rpmsign to sign the package file.

You built a package and now you need to prove it is yours

You just compiled a custom kernel module or packaged a proprietary tool for your team. You run rpmbuild and get a clean .rpm file. You push it to your internal repository, but dnf install refuses to touch it. The terminal spits out a GPG verification error. You need to sign the package so Fedora trusts it. This is how you generate a key, register it, and apply a signature that survives package upgrades.

Why RPM signatures matter

RPM packages are just compressed archives with metadata. Anyone can repack them. A GPG signature attaches a cryptographic proof to that metadata. When you sign a package, you are telling the system that the contents match exactly what you built, and that you authorize the installation. Think of it like a wax seal on a diplomatic envelope. The seal proves the sender, and any tampering breaks the wax. Fedora package manager checks that seal before unpacking anything. If the seal is missing or broken, dnf stops the transaction to protect your system.

The signature does two things. It verifies integrity. It verifies identity. Integrity means the file has not been altered since you signed it. Identity means the key belongs to you. The RPM database stores a list of trusted public keys. When dnf downloads a package, it extracts the signature, looks up the corresponding public key in the database, and runs a mathematical verification. If the math checks out, the package installs. If it fails, the transaction aborts.

Run journalctl -xeu dnf.service after a failed install. Read the actual error before guessing. The log will tell you exactly which key is missing or which checksum failed.

Generate a key that RPM will accept

You need a GPG key pair before you can sign anything. The private key stays on your machine. The public key goes into the RPM trust store. Fedora and the broader RPM ecosystem expect RSA keys with a minimum length of 4096 bits. Shorter keys trigger warnings. Expired keys break automated updates. Generate a key that matches these expectations.

Here is how to create a key that RPM will accept without complaints.

gpg --full-generate-key
# Select RSA and RSA for the algorithm pair
# Choose 4096 for the key size
# Set the expiration to 0 for no expiration
# Enter your full name and the email address you will use for signing
# Provide a passphrase to protect the private key

The interactive prompt will ask for your preferences. Stick to RSA and 4096 bits. Set the expiration to zero if this key will live in a long-term repository. GPG will generate the key pair and store it in your local keyring. You will see a fingerprint and a key ID. Write down the key ID. You will need it later.

Convention aside: GPG stores keys in ~/.gnupg/. RPM stores keys in /var/lib/rpm/. They are completely separate databases. Importing a key into GPG does nothing for RPM. Importing a key into RPM does nothing for GPG. Keep them straight.

Export the public key immediately. You will need it for distribution and for the RPM import step.

gpg --armor --export YOUR_EMAIL > YOUR_EMAIL.asc
# --armor encodes the binary key as ASCII text
# The .asc extension signals a text file to package managers
# Redirect output to a file for safekeeping and distribution

Store the .asc file in a secure location. You will distribute it to anyone who needs to install your packages. You will also import it into the local RPM database.

Back up the private key and passphrase. A lost key means you cannot sign future updates. Revoke the old key and generate a new one if you suspect compromise.

Register the key with the RPM database

The RPM package manager does not read GPG keyrings. It maintains its own trust store. You must explicitly import your public key into the RPM database. This step tells the system that packages signed with this key are authorized for installation.

Here is how to add your public key to the RPM trust store.

sudo rpm --import YOUR_EMAIL.asc
# --import reads the ASCII-armored public key
# sudo is required because the RPM database is owned by root
# The key is written to /var/lib/rpm/pubkeys/ and indexed

The command produces no output on success. Silence means the key is registered. If you see an error about duplicate keys, the key is already in the database. You can verify the import by listing the trusted keys.

rpm -qa gpg-pubkey*
# Lists all GPG keys currently trusted by the RPM database
# The output shows the key ID and import date
# Match the key ID to the one you generated earlier

The output will look like gpg-pubkey-<KEYID>-<DATE>. The key ID matches the last eight characters of your GPG fingerprint. Keep this ID handy. You will reference it when configuring the signing tool.

Convention aside: Config files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/. The RPM key database lives in /var/lib/rpm/, which is managed by the package manager itself. Do not manually delete files from that directory. Use rpm --import and rpm -e gpg-pubkey-<ID> to manage trust.

Verify the key import before proceeding. A missing key in the RPM database will cause rpmsign to fail silently or abort with a cryptic error.

Tell rpmsign which key to use

The rpmsign tool does not automatically know which GPG key to use. It looks for a macro in your RPM configuration. Without this macro, the command will refuse to sign anything. You need to create or edit ~/.rpmmacros and point it to your key ID.

Here is how to configure the signing macro for your user account.

echo '%_gpg_name YOUR_EMAIL' >> ~/.rpmmacros
# %_gpg_name tells rpmsign which key to use for signing
# YOUR_EMAIL must match the email you used during key generation
# Appending to ~/.rpmmacros preserves any existing RPM settings

If you prefer to use the key ID instead of the email address, you can set %_gpg_name 0x<KEYID>. Both formats work. The email format is easier to read. The key ID format is more precise when you manage multiple keys. Pick one and stick with it.

The ~/.rpmmacros file applies to your user account only. It does not affect system-wide builds. This is intentional. It prevents accidental cross-contamination between different developers on the same machine. If you run rpmsign as root, you must place the macro in /root/.rpmmacros instead.

Test the macro configuration before signing. Run rpm --eval '%_gpg_name'. The output should print your email address or key ID. If it prints nothing, the macro is missing or malformed.

Set the macro once. Verify it immediately. Skip this step and rpmsign will waste your time.

Sign the package

You have the key. You have the trust store. You have the macro. Now you apply the signature. The rpmsign command reads the package, calculates a cryptographic hash, encrypts the hash with your private key, and embeds the signature into the RPM header.

Here is how to sign a single RPM package.

rpmsign --addsign package-name.rpm
# --addsign applies the GPG signature to the specified file
# rpmsign looks for the key defined in %_gpg_name
# You will be prompted for your GPG passphrase
# The command modifies the file in place and creates a .rpm~ backup

Enter your passphrase when prompted. The tool will process the file and exit. If it succeeds, the original file is overwritten with the signed version. A backup copy with a ~ suffix is created automatically. You can delete the backup after verification.

If you are signing multiple packages, you can pass them all at once. The command processes them sequentially. It stops on the first error. Fix the error and run the command again.

Convention aside: dnf upgrade --refresh is the normal weekly maintenance command. dnf system-upgrade is for crossing major Fedora releases. They are different commands. Don't conflate them. Similarly, rpmsign --addsign signs a package. rpmsign --resign replaces an existing signature. Use --addsign for new packages. Use --resign only when you intentionally need to replace a signature with a different key.

Sign the package once. Verify it immediately. Do not modify the file after signing. Any change breaks the signature.

Verify the signature

Never assume a signature is valid because the command exited cleanly. Always verify it. The rpm command can check signatures without installing the package. This step catches configuration errors before they reach your repository.

Here is how to verify the signature on your signed package.

rpm -K package-name.rpm
# -K is short for --checksig
# The command reads the embedded signature and validates it
# It checks both the GPG signature and the package checksum
# Output shows OK for valid signatures or FAILED for broken ones

A successful verification prints a line like package-name.rpm: rsa sha256 (md5) pgp md5 OK. The OK at the end means the signature matches the trusted key in the RPM database and the file has not been altered. If you see FAILED, the signature is broken, the key is missing, or the file was modified after signing.

You can also verify the signature during installation. Run sudo dnf install package-name.rpm. The package manager will perform the same check. If the signature is valid, the installation proceeds. If it fails, dnf aborts and prints the exact error.

Verify every package before distribution. A broken signature blocks updates and frustrates users. Fix the root cause before pushing to the repository.

Common pitfalls and broken signatures

Signing RPM packages involves multiple moving parts. A misconfiguration anywhere in the chain breaks the process. Here are the most common failure modes and how to fix them.

The first issue is a missing or mismatched key ID. If rpmsign prints Error: no signature key, your ~/.rpmmacros file is missing the %_gpg_name macro, or the email address does not match the key in your GPG keyring. Run gpg --list-keys to confirm the email. Update the macro and try again.

The second issue is an expired key. GPG keys with expiration dates will cause rpmsign to fail with public key is expired. RPM refuses to sign packages with expired keys to prevent repository chaos. You must extend the key expiration or generate a new key. Run gpg --edit-key YOUR_EMAIL and use the expire command to set a new date.

The third issue is modifying the package after signing. If you run rpmbuild again, or if a backup tool modifies the file, the signature breaks. The cryptographic hash no longer matches the file contents. rpm -K will report FAILED. Re-sign the package. Do not patch signed RPMs manually.

The fourth issue is using the wrong key type. DSA keys are deprecated. EdDSA keys are not fully supported by older RPM tools. Stick to RSA 4096. If you generated a different key type, delete it and generate a new one. RPM tools expect RSA.

Run rpm -K first. Read the actual error before guessing. The output tells you exactly which part of the chain failed.

When to sign locally versus using a build service

Use local signing when you are packaging a single tool for your own workstation or a small internal team. Use local signing when you need immediate control over the key material and do not want to configure a remote build system. Use a build service like OBS or Koji when you are maintaining a public repository with dozens of packages. Use a build service when you need automated CI pipelines, reproducible builds, and centralized key management. Stay unsigned when you are testing a package on a disposable VM and do not care about integrity verification. Trust the package manager. Manual file edits drift, signatures stay.

Where to go next

Verify the signature before distribution. A broken seal breaks trust.