Skip to main content

    How to Sign RPM and DEB Packages (and Why a Code Signing Certificate Won't)

    RPM and DEB packages are signed with an OpenPGP key, not an X.509 certificate. rpmsign, InRelease repository signing, signed-by keyrings, key custody.

    DR
    Daniel Rehak
    ·
    13 min read
    ·
    Published September 26, 2026
    ·
    Last updated September 26, 2026

    The short answer

    RPM and DEB packages are signed with an OpenPGP key, not with an X.509 code signing certificate. As of September 2026 there is still no way to sign an RPM package directly with a certificate from a public CA, and dpkg ships with embedded .deb signature checking switched off. For RPM you name a key in %_gpg_name and run rpm --addsign. For Debian and Ubuntu the signature that actually protects users sits on the repository's InRelease file, which carries the hashes of the Packages index, which carries the hash of every .deb. Your users trust that key because you handed it to them, not because anybody vouched for it — so the whole scheme rests on where the private key lives.

    Two signing systems: artifacts checked against a public root program versus Linux packages checked against a key the publisher distributesTwo panels side by side. The left panel is headed Checked against a public root program and lists Windows binaries such as exe, dll, msi and msix signed with Authenticode, and Java archives signed with jarsigner. Its trust anchor box reads a publicly trusted code signing certificate, with the key held in certified hardware. The right panel is headed Checked against a key you distribute and lists the RPM package header signature and the APT repository InRelease signature, both made with OpenPGP. Its trust anchor box, highlighted in gold, reads your own OpenPGP key, which the user imports, and notes that no certificate authority and no root program is involved. A band across the bottom states the consequence: on the left a rule decides where the private key lives, and on the right that decision is entirely yours.One phrase, two trust anchorsChecked against a public root program.exe .dll .msi .msixAuthenticode.jarjarsignerTrust anchora publicly trusted codesigning certificate, keyin certified hardwareChecked against a key you distribute.rpm package headerOpenPGPAPT repository InReleaseOpenPGPTrust anchoryour own OpenPGP key,imported by the user. No CA,no root program.On the left, a rule decides where the private key lives.On the right, that decision is entirely yours — which is the whole risk.
    The two systems never meet. A certificate cannot produce a package signature, and an OpenPGP key cannot produce an Authenticode one.

    What actually signs a Linux package?

    An OpenPGP key. RPM stores an OpenPGP signature inside the package header; Debian and Ubuntu put the signature on the repository's index files instead of on the packages. Neither format consumes an X.509 certificate the way Windows consumes one through Authenticode. There is no root program behind a Linux package signature, so the trust anchor is the public key you publish and your users install.

    That difference is the source of nearly every wrong assumption in this area. On Windows, the certificate is doing two jobs at once: it proves a key belongs to a named legal entity, and it chains to a root the operating system already trusts. A package signing key does the first job only, and only for people who already got the key from you. Nothing about it is weaker cryptographically — RSA and ECC behave the same in both worlds — but the question "who decided to trust this key?" has a completely different answer.

    Can I use my code signing certificate on a .rpm or .deb?

    No — not for the package signature. RPM accepts OpenPGP keys and nothing else, and Red Hat states the position plainly: there is currently no way to sign an RPM using an X.509 certificate. You can sign the .rpm file as an opaque blob with openssl and ship the detached signature and certificate next to it, but no package manager will look at that, so you have built a side channel rather than a package signature. Debian gives the same answer from the other direction: debsigs and the repository tooling both speak OpenPGP.

    This is the single most common mismatch we see on the support side. Someone buys an OV or EV code signing certificate because the product page mentions Linux, receives a token or a cloud key, and then discovers the certificate signs their Windows installer and their .jar perfectly while their .rpm will not take it at all. Both halves of that are true at once, which is why the table below is worth reading before you buy, not after:

    What you are signingMechanismDoes a public CA certificate apply?
    .exe, .dll, .msi, .msixAuthenticode (signtool, osslsigncode, Jsign)Yes — and it is required for a trusted result
    .jarjarsignerYes
    .rpm package signatureOpenPGP, in the package headerNo
    .deb embedded signatureOpenPGP via debsigs, off by defaultNo
    apt or dnf repository metadataOpenPGP, on InRelease or repomd.xmlNo
    RPM IMA file signaturesX.509 key via rpmsign --signfilesX.509, yes — but the kernel trusts the key because you enrolled it, not because a CA issued it
    Container imagescosign / SigstoreNo — an OIDC identity or your own key

    The highlighted row is the one that trips up careful readers, because it does involve a certificate. RPM can attach per-file signatures for the kernel's Integrity Measurement Architecture, and rpmsign --signfiles takes an RSA key with --fskpath or %_file_signing_key, including a PKCS#11 URI where OpenSSL has the provider loaded. The key ID is normally the Subject Key Identifier of the matching X.509 certificate. But nothing about that path goes through a public root program: the key is trusted because it sits in the kernel keyring on the machines you control.

    How to sign an RPM package

    Three steps: create an OpenPGP key, name it in ~/.rpmmacros, then run rpm --addsign against the built package. The signing tool lives in the rpm-sign package on Red Hat-family distributions, which is not installed by default, so that is usually the first error you hit. Verification is the same command a user would run, which makes it worth doing yourself before you publish anything.

    # 0. the signing tool is a separate package
    sudo dnf install rpm-sign
    
    # 1. create the key. rpm wants an OpenPGP key, not a CSR.
    gpg --full-generate-key
    
    # 2. tell rpm which key to use
    cat >> ~/.rpmmacros <<'EOF'
    %_signature gpg
    %_gpg_name  Example Ltd (package signing) <[email protected]>
    %_gpgbin    /usr/bin/gpg2
    EOF
    
    # 3. sign — add a signature, keeping any that are already there
    rpm --addsign dist/example-2.4.1-1.el9.x86_64.rpm
    
    # or replace the existing signature instead of adding to it
    rpm --resign dist/example-2.4.1-1.el9.x86_64.rpm
    
    # 4. verify the way a user's machine will
    rpm --checksig dist/example-2.4.1-1.el9.x86_64.rpm
    The RPM signing path, from an OpenPGP key to a user verifying the packageA left to right flow in four steps. Step one, an OpenPGP key pair created with gpg. Step two, the key named in the underscore gpg name macro in the rpmmacros file. Step three, the rpm add sign command, which writes the signature into the package header rather than over the payload; this box is highlighted in gold. Step four, the user side, where rpm import loads the public key into the rpm database and rpm dash capital K reports whether signatures as well as digests are present. A note underneath explains that an unsigned package still reports its digests as OK, so the word to look for in the output is signatures.The signature goes into the header, not the payload1. Keygpg --full-generate-keyOpenPGP2. Point rpm at it~/.rpmmacros%_gpg_name3. Signrpm --addsignheader signaturepayload covered by hash4. User verifiesrpm --importrpm -K pkg.rpmReading the verification outputAn unsigned package still reports its digests as OK. Digests prove thefile arrived intact; only a signature says who built it. The word tolook for in the output is "signatures".
    Most failed signing setups are found here: the build succeeded, the package installs, and nobody ever read past the word OK.

    Read the verification output carefully. RPM reports digests and signatures as separate results, and an unsigned package still reports its digests as fine — the file simply arrived intact. If the word signatures is missing from the line, the package is unsigned no matter how healthy the rest of the output looks. This is where most broken pipelines are found weeks late: the macro name had a typo, rpm --addsign silently did nothing useful, and every release since has shipped bare.

    On the consumer side, the public key has to be in RPM's own keyring, which is separate from the user's GnuPG keyring:

    # import the publisher's key into the rpm database
    sudo rpm --import https://packages.example.com/RPM-GPG-KEY-example
    
    # list what rpm currently trusts
    rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE} %{SUMMARY}\n'
    
    # and in a .repo file, point dnf at the same key
    #   gpgcheck=1
    #   gpgkey=https://packages.example.com/RPM-GPG-KEY-example

    Serve that key file over HTTPS and publish its full fingerprint somewhere a human can compare it against — a documentation page, not a blog post that scrolls away. The key is the whole trust anchor, so the channel you distribute it over is the channel an attacker would target.

    How to sign a .deb, and who checks it

    You can embed a signature in a .deb with debsigs, and on a default Debian or Ubuntu system nobody will check it. dpkg ships with no-debsig in /etc/dpkg/dpkg.cfg, and a client that wants verification needs debsig-verify installed plus an XML policy document for each key it will accept. Debian-based distributions made that choice deliberately: they verify repository metadata and source packages instead.

    # embedded signature, role-based (origin, maint, archive)
    debsigs --sign=origin -k 0xE732A79A dist/example_2.4.1_amd64.deb
    
    # source packages and .changes files take a different, text-based signature
    debsign -k 0xE732A79A dist/example_2.4.1-1.dsc
    
    # what a client needs before any of the above is checked:
    #   apt install debsig-verify
    #   a policy XML under /etc/debsig/policies/<fingerprint>/
    #   the key under /usr/share/debsig/keyrings/<fingerprint>/

    Embedded signatures are worth the effort in one situation: a fleet you control, where you can ship the policy and keyring along with the machines, and you want a package that is still verifiable after it has been copied off the repository onto a USB stick. For public distribution, spend the same effort on the repository instead. Nothing you do inside the .deb reaches a stock apt installation.

    Sign the repository: InRelease and signed-by

    apt verifies a chain, not a package. Your key signs the Release file; that file holds the SHA-256 hashes of every Packages index; each entry in Packages holds the hash of one .deb. Break any link and apt refuses the update. Since Debian 10 the signed artefact is InRelease, an inline-signed copy of Release; Release.gpg is the older detached form, still published for compatibility.

    What apt actually verifies: one signature on InRelease, then a chain of hashes down to each .deb fileA vertical chain of four boxes. At the top, your OpenPGP key makes one signature. That signature is on the InRelease file, a clearsigned copy of Release; this box is highlighted in gold and notes that Release.gpg is the older detached form of the same thing. The InRelease file holds the SHA-256 hashes of the Packages indexes. Each Packages entry holds the SHA-256 hash of one .deb file. At the bottom, apt installs the .deb only if every link matches. To the side, a separate box shows the embedded signature that debsigs can place inside a .deb, marked as not verified by default because dpkg ships with the no-debsig setting and a client also needs debsig-verify and an XML policy file.One signature protects every package in the repositoryYour OpenPGP key signs onceInRelease — a clearsigned copy of ReleaseRelease.gpg is the older detached form of the same signatureholds SHA-256 of each Packages indexPackages holds SHA-256 of each .debapt installs only if every link matchesThe other signaturedebsigs can embed oneinside the .deb itselfNot verified by defaultdpkg ships no-debsig, anda client also needsdebsig-verify plus anXML policy per key
    This is why a Debian publisher who signs every .deb and forgets the repository has protected nothing a user's machine will check.

    In practice a repository tool such as reprepro or aptly produces both files for you from a SignWith setting. It is still worth knowing the two commands underneath, because this is what you end up running by hand the first time a CI job produces an unsigned index:

    # InRelease — the signature and the content in one file
    gpg --clearsign --local-user 0xE732A79A -o dist/InRelease dist/Release
    
    # Release.gpg — the same signature, detached, for older clients
    gpg -abs --local-user 0xE732A79A -o dist/Release.gpg dist/Release

    The client half is where the guidance has changed. Publish your key as a file and have users scope it to your repository with signed-by, instead of adding it to a global keyring:

    # one-line format
    echo "deb [signed-by=/etc/apt/keyrings/example-archive-keyring.gpg] \
    https://packages.example.com/debian stable main" \
      | sudo tee /etc/apt/sources.list.d/example.list
    
    # deb822 format — /etc/apt/sources.list.d/example.sources
    Types: deb
    URIs: https://packages.example.com/debian
    Suites: stable
    Components: main
    Signed-By: /etc/apt/keyrings/example-archive-keyring.gpg

    The security argument for this has nothing to do with the deprecation warning people are trying to silence. A key added with apt-key add lands in a keyring that apt trusts for every repository on the machine, so a small third-party repository ends up able to vouch for a package claiming to be from the distribution itself. signed-by binds one key to one source, which is what you wanted in the first place. If you publish installation instructions, this is the line to fix in them.

    Ship the key as a proper keyring package too, if you can — example-archive-keyring containing the file under /usr/share/keyrings/. Then key rotation is a package update rather than a support article and a thousand people running curl | gpg --dearmor from memory.

    What changed: RPM 6.0 and the end of apt-key

    Two shifts matter if your signing setup predates them. RPM 6.0.0 was released on 22 September 2025 and enforces signature checking by default, accepts multiple OpenPGP signatures on one package, supports OpenPGP v6 and post-quantum keys, and makes the new v6 package format rpmbuild's default while still building v4. On the Debian side, apt-key is deprecated and its manual page describes it as scheduled for removal after Debian 12 and Ubuntu 24.04, with /etc/apt/keyrings the recommended home for keys APT does not manage since APT 2.4.

    ChangeAs ofWhat it means for a publisher
    RPM enforces signature checking by defaultRPM 6.0, 22 Sep 2025An unsigned internal repository stops being merely untidy
    Multiple OpenPGP signatures per packageRPM 6.0Rotate keys, or add a post-quantum signature, without invalidating the first one
    v6 format drops MD5 and SHA-1RPM 6.0Old tooling that parses packages needs checking; v4 output is still available
    Installing RPM v3 packages removedRPM 6.0Very old archives can still be queried and unpacked with rpm2cpio
    apt-key deprecated, removal scheduledafter Debian 12 / Ubuntu 24.04Rewrite your published install instructions to use signed-by

    The multiple-signature change is the one worth planning around. Until RPM 6.0, adding a signature with a new key meant deciding what happened to the old one, which made rotation a coordination problem with every consumer. Now a package can carry both, so the sequence becomes: publish the new key, sign with both for a release cycle, then drop the old one. That is the same "add, do not replace" discipline that certificate rotation needs on the Windows side, and for the same reason.

    Where the signing key should live

    Nothing in RPM or APT requires the private key to be in hardware. There is no CA/Browser Forum equivalent for package signing, no audit, and no issuer to refuse you — so a key sitting in ~/.gnupg on a shared build agent is entirely permitted and entirely exposed. Anyone who can read that directory can sign packages as you, and your users' machines will install them without a murmur, because the signature will be perfectly valid.

    Four places a package signing key can live, from a file on the build agent to a hosted signing serviceFour rungs drawn as a rising staircase. Rung one, a key file in the gnupg directory on the build agent, labelled anyone with the agent has your identity. Rung two, an offline primary key with a signing subkey on the agent, labelled compromise costs a subkey rotation rather than the identity. Rung three, an OpenPGP smartcard or a hardware security module reached through the gpg agent smartcard daemon or a PKCS eleven URI, labelled the key cannot be copied off. Rung four, a hosted signing service that signs on request and logs who asked. A gold band across the bottom carries the point: nothing in RPM or APT forces you up this staircase, which is the opposite of a publicly trusted code signing certificate, where hardware key storage has been mandatory since the first of June 2023.Nobody will stop you standing on the bottom step1. Key file onthe build agentagent access =your identity2. Offline primary,signing subkeyrotate a subkey,keep the identity3. Smartcardor HSMscdaemon or aPKCS#11 URI4. Hostedsigning servicesigns on request,logs who askedNo rule pushes you up these steps.For code signing certificates, hardware has been mandatory since 1 June 2023.
    The absence of a rule is the thing to plan around. Pick a step deliberately and write down which one you chose.

    Pick a step on that staircase deliberately. The cheapest real improvement is structural rather than physical: keep the primary key offline and give the build agent a signing subkey, so a compromised agent costs you a subkey rotation instead of your published identity. Above that, an OpenPGP smartcard or an HSM reached through gpg-agent's smartcard daemon means the key cannot be copied off at all, and rpmsign will take a PKCS#11 URI for the IMA file signing path. A hosted signing service adds the thing hardware alone does not give you: a log of who asked for each signature.

    Set an expiry date on the signing key and put the rollover in the calendar. OpenPGP has no equivalent of the RFC 3161 timestamp that keeps an Authenticode signature valid after its certificate lapses, so tooling that checks key validity will start complaining the day after expiry. Extend the existing key or publish the replacement well before that date, and use RPM 6.0's multiple-signature support to overlap the two rather than cutting over in one release.

    Where a code signing certificate does apply on Linux

    On the artifacts that leave the Linux world. A Linux build agent can produce a perfectly valid Authenticode signature for a Windows .exe, .dll, .msi or .msix using osslsigncode or Jsign, and can sign a .jar with jarsigner. Those signatures are checked against a public root program, which is exactly why they need a publicly trusted certificate — and, since 1 June 2023, a key generated inside certified hardware.

    So a release pipeline that ships both a .deb and a Windows installer runs two independent signing systems side by side, with two key custody stories. The mechanics of the Authenticode half on a Linux runner are covered in how to sign a Windows EXE on Linux or macOS, down to the PKCS#11 configuration for a token or a cloud key. If the certificate side is new to you, the OV and EV code signing certificates we resell from Certum list which delivery model — shipped token, or a cloud key that works unattended — fits a pipeline that also builds Linux packages. Certificates issued on or after 1 March 2026 are capped at 460 days under CA/Browser Forum ballot CSC-31, so the renewal cadence is now close to annual.

    For container images the answer is different again: neither an OpenPGP key nor a public CA certificate is the default path, and Sigstore compared with code signing certificates covers where each model actually fits. A certificate can be made to work on an OCI image, though whether a code signing certificate is worth buying for Docker images turns out to be a narrower question than it first looks. Three ecosystems, three trust anchors, one phrase — which is why "do we sign our releases?" is never a single yes or no.

    Frequently Asked Questions

    Answers to common questions about certificates and our services.

    Can I sign an RPM package with a code signing certificate?

    Not the package signature itself. RPM consumes OpenPGP keys, and as of September 2026 there is no facility for signing a package with an X.509 certificate from a public CA. Red Hat's own position is that if you insist on a certificate you can sign the whole .rpm as an opaque file with openssl and distribute the detached signature and certificate alongside it — but no package manager will check that for you, so you have built a side channel rather than a package signature.

    Do I need to sign individual .deb files, or is signing the repository enough?

    For almost every publisher, signing the repository is the part that matters. dpkg ships with embedded .deb signature verification switched off, and a client that wants it needs debsig-verify installed plus an XML policy document per key. The signature apt does check on every update is the one on the repository's InRelease file, which carries the hashes of the Packages index, which carries the hash of each .deb. Sign the repository first; treat debsigs as an extra for controlled fleets.

    How do I fix a NO_PUBKEY error from apt?

    NO_PUBKEY means apt fetched a signed InRelease file and has no key that matches the signature. Get the repository's public key from the publisher over HTTPS, put it in its own file under /etc/apt/keyrings, and reference that file with signed-by= in the matching source entry. Do not pipe it into apt-key add: that puts the key in a keyring trusted for every repository on the machine, which is the behaviour the signed-by pattern exists to remove.

    Is apt-key still safe to use?

    It still runs on current releases, and it is on the way out. The manual page describes apt-key as deprecated and scheduled for removal after Debian 12 and Ubuntu 24.04, with apt-key del in maintainer scripts as the one remaining sanctioned use. Since APT 2.4, /etc/apt/keyrings is the recommended home for keys APT does not manage. The reason to move is scoping rather than the warning message: a key added with apt-key is trusted for all repositories, not just yours.

    Does a Linux package signature expire the way a certificate does?

    There is no equivalent of the RFC 3161 timestamp that keeps an Authenticode signature valid after its certificate expires. An OpenPGP signature has no certificate lifetime behind it at all — but if you set an expiry date on your signing key, which is good practice, tooling that checks key validity will start to complain once that date passes. Plan the rollover: extend the expiry on the existing key or publish the replacement well before the old one lapses.

    Do I need to sign packages in an internal-only repository?

    Yes, and the tooling is steadily making it non-optional. RPM 6.0 enforces signature checking by default, and apt has refused unsigned repositories without an explicit override for years. An internal repository is also the one most likely to be served over plain HTTP from a box nobody patches, which is exactly the case a signature covers. The same OpenPGP key can sign both your RPM packages and your APT repository metadata.

    Still Have Questions?

    Contact our support team with questions about certificates, installation, or technical issues.

    Shipping Windows or Java builds from the same pipeline?

    Your OpenPGP key handles the packages. The Windows installer and the signed .jar need a publicly trusted certificate, with the key generated in certified hardware because the rules have required that since June 2023. Compare the OV and EV code signing options and what each one verifies before issuing.

    Related reading