Skip to content

How to add a third-party repository safely

A vendor publishes its software in its own package repository and asks you to add that repository and its signing key to your system. This guide adds the repository so that its signature is always checked, its key is trusted for as little as the package manager allows, and it can't replace the distribution's own packages without you noticing.

A repository can run code as root

Packages run their installation scripts as root, so whoever controls a repository you add, or its signing key, controls every machine that installs or upgrades from it. Add only repositories you trust as much as the distribution itself, and prefer the distribution's own packages where they're good enough. Never turn signature checking off to make a repository work (trusted=yes on Debian and Ubuntu, gpgcheck=0 on the RHEL family, SigLevel = Never on Arch): an unsigned repository lets anyone between you and the server substitute packages.

Before you begin

  • You need a shell with sudo on the machine.
  • From the vendor's documentation you need three things: the repository's URL for your distribution and release, the URL of its signing key, and the key's full fingerprint. Get the fingerprint from the vendor's HTTPS documentation pages, not from the same place as the key, so that one compromised server can't supply both.

Replace these placeholders with the vendor's values:

Placeholder Meaning Example
<REPO_URL> The repository's base URL for your distribution https://packages.example.com/debian
<KEY_URL> The URL of the vendor's public signing key https://packages.example.com/key.asc
<FINGERPRINT> The key's full fingerprint, as 40 hexadecimal digits without spaces 0123456789ABCDEF0123456789ABCDEF01234567

The steps use example as the repository's local name and example-hello as the package you want from it; use the vendor's names instead.

Download and check the signing key

  1. Download the key to your home directory:

    curl -fsSL -o example.asc <KEY_URL>
    
  2. Make sure gpg is available:

    Debian 13's minimal and cloud images don't include gpg, because apt verifies signatures with sqv instead. Install it if command -v gpg prints nothing:

    sudo apt-get install -y gnupg
    

    gpg is installed by default.

    gpg is installed by default, because pacman depends on it.

    gpg is installed by default, because Portage uses it to verify the Gentoo repository.

  3. Show the key's fingerprint without importing it anywhere:

    $ gpg --show-keys example.asc
    pub   rsa3072 2026-11-07 [SC]
          0123456789ABCDEF0123456789ABCDEF01234567
    uid                      Example Vendor <packages@example.com>
    
  4. Compare the 40-digit fingerprint with <FINGERPRINT> from the vendor's documentation. If it differs, or the file contains more keys than the vendor documents, stop: you don't have the vendor's key.

Add the repository

  1. Install the key where only this repository will use it. /etc/apt/keyrings/ is the directory for keys that are referenced explicitly; apt doesn't trust keys there for any repository by default:

    sudo install -m 0644 example.asc /etc/apt/keyrings/example.asc
    

    Don't put the key in /etc/apt/trusted.gpg.d/: keys there are trusted for every repository. apt-key no longer exists on Debian 13 and Ubuntu 26.04; older instructions that used it put keys in that directory.

  2. Describe the repository in a deb822 .sources file, with Signed-By naming the key. Use the suite and components the vendor documents (here stable and main):

    Types: deb
    URIs: <REPO_URL>
    Suites: stable
    Components: main
    Signed-By: /etc/apt/keyrings/example.asc
    

    Signed-By makes apt accept this repository only if its InRelease file is signed by that key. Because the key is in /etc/apt/keyrings/ rather than trusted.gpg.d, no other repository accepts it.

  3. Give the repository a low priority, so that apt installs only the packages you name from it and never prefers it for packages the distribution also ships. <REPO_HOST> is the host name in <REPO_URL>, for example packages.example.com:

    Package: *
    Pin: origin <REPO_HOST>
    Pin-Priority: 100
    
    Package: example-hello
    Pin: origin <REPO_HOST>
    Pin-Priority: 500
    

    At priority 100, packages from the repository are installed only when nothing else provides them, and installed ones are still upgraded from it; the second entry restores normal priority for the package you want. List each package you install from the repository in the second entry, separated by spaces.

  4. Refresh the package lists and install the package:

    sudo apt-get update
    sudo apt-get install -y example-hello
    

    If apt-get update reports that the signature couldn't be verified (Missing key …, which is needed to verify signature on Debian 13, NO_PUBKEY … on Ubuntu 26.04), the key or the Signed-By path is wrong. Fix it; don't work around it.

  1. Install the key in the directory where the distribution keeps its own:

    sudo install -m 0644 example.asc /etc/pki/rpm-gpg/RPM-GPG-KEY-example
    
  2. Describe the repository in a .repo file:

    [example]
    name=Example Vendor
    baseurl=<REPO_URL>
    enabled=1
    gpgcheck=1
    repo_gpgcheck=1
    gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example
    includepkgs=example-hello
    

    • gpgcheck=1 checks the signature on every package from the repository; repo_gpgcheck=1 also checks the signature on its metadata, if the vendor signs it (remove the line if dnf reports that repomd.xml.asc is missing).
    • includepkgs limits what dnf will take from this repository to the packages you list (separated by commas, with * wildcards allowed), so that the repository can't replace a distribution package with its own build.
  3. Import the key and install the package. dnf asks to import the key on first use; importing it explicitly lets you check it once more:

    sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-example
    sudo dnf install -y example-hello
    

RPM has a single keyring for the whole system (rpm -q gpg-pubkey lists it): once imported, the vendor's key is accepted for packages from any repository, not only this one. includepkgs stops the vendor's repository replacing distribution packages, but a package signed with the vendor's key would still be accepted from another repository, for example from a compromised mirror. The key stays trusted for the whole system until you remove it, as described in Undo.

  1. Add the key to the keyring of pacman and sign it locally, which is how you tell pacman that you trust it:

    sudo pacman-key --add example.asc
    sudo pacman-key --lsign-key <FINGERPRINT>
    

    pacman-key --lsign-key shows the key it signs; check that it's the fingerprint you verified.

  2. Add a section for the repository at the end of /etc/pacman.conf, after [core] and [extra]:

    sudo tee -a /etc/pacman.conf > /dev/null <<'EOF'
    
    [example]
    SigLevel = Required
    Server = <REPO_URL>
    EOF
    

    When two repositories offer a package with the same name, pacman takes it from the one listed first, whatever the versions. Listing the vendor's repository last means it can only add packages, not replace the distribution's. SigLevel = Required requires valid signatures on both its packages and its database.

  3. Synchronise the databases, upgrade the system and install the package in one transaction (Arch doesn't support refreshing the databases without upgrading):

    sudo pacman -Syu --noconfirm example-hello
    

pacman has a single keyring: a key you've locally signed is trusted for packages from any repository, not only this one. The repository order stops the vendor's repository replacing distribution packages, so check that the section stays at the end when you edit pacman.conf; but a package signed with the vendor's key would still be accepted from another repository, for example from a compromised mirror. The key stays trusted for the whole system until you remove it, as described in Undo.

Third-party software for Gentoo usually comes as an ebuild repository (an "overlay") synchronised with Git. Portage can refuse to use it unless its newest commit is signed by a key you name.

  1. Install Git if it isn't already:

    sudo emerge --getbinpkg --noreplace dev-vcs/git
    
  2. Install the key in a directory of your choice, for example /etc/portage/keys/:

    sudo install -d /etc/portage/keys
    sudo install -m 0644 example.asc /etc/portage/keys/example.asc
    
  3. Describe the repository in /etc/portage/repos.conf/:

    [example]
    location = /var/db/repos/example
    sync-type = git
    sync-uri = <REPO_URL>
    sync-git-verify-commit-signature = yes
    sync-openpgp-key-path = /etc/portage/keys/example.asc
    sync-openpgp-key-refresh = no
    auto-sync = yes
    

    With sync-git-verify-commit-signature, every sync checks the signature on the newest commit against that key only, and fails if it's missing or made by another key. The key isn't used for anything else.

    By default, Portage also refreshes the key from the vendor's web key directory (WKD) or a key server before each sync, so that it notices if the key is revoked, and the sync fails if the refresh fails. sync-openpgp-key-refresh = no turns that off for a vendor that publishes its key only as a file, as here; Portage then warns on each sync, and replacing the file when the vendor announces a new key is up to you. If the vendor's key is available through WKD or a key server, leave the line out.

  4. Mask everything in the repository except the packages you want, so that it can't replace packages from the Gentoo repository with its own ebuilds:

    sudo install -d /etc/portage/package.mask /etc/portage/package.unmask
    echo '*/*::example' | sudo tee /etc/portage/package.mask/example
    echo 'app-misc/example-hello::example' | sudo tee /etc/portage/package.unmask/example
    

    install -d creates package.mask and package.unmask as directories if they don't exist yet. If either already exists as a file, add the line to that file instead.

  5. Synchronise the repository and install the package:

    sudo emaint sync --repo example
    sudo emerge app-misc/example-hello
    

Binary package hosts in /etc/portage/binrepos.conf are checked against the keyring in /etc/portage/gnupg/, which is shared by every binary host. Add a third-party binary host only if you trust it with every package name.

Verify

  1. Check that the package came from the vendor's repository and runs:

    $ apt-cache policy example-hello
    example-hello:
      Installed: 1.0-1
      Candidate: 1.0-1
      Version table:
     *** 1.0-1 500
            100 https://packages.example.com/debian stable/main amd64 Packages
            100 /var/lib/dpkg/status
    
    $ dnf list --installed example-hello
    Installed Packages
    example-hello.noarch                 1.0-1                 @example
    
    $ pacman -Si example/example-hello
    Repository      : example
    Name            : example-hello
    
    $ qlist -IRv example-hello
    app-misc/example-hello-1.0::example
    
    $ example-hello
    Hello from the example repository
    
  2. Check that the repository's scope is what you intended: no distribution package should now come from it.

    The repository as a whole has priority 100:

    $ apt-cache policy | grep -B2 'origin <REPO_HOST>$'
     100 https://packages.example.com/debian stable/main amd64 Packages
         release o=Example,a=stable,n=stable,l=Example,c=main,b=amd64
         origin packages.example.com
    

    In apt-cache policy example-hello above, the repository line shows this priority, and the *** line shows the priority 500 you gave the package itself.

    Only the packages in includepkgs are visible in the repository:

    sudo dnf repository-packages example list
    

    The vendor's repository is the last one listed:

    pacman-conf --repo-list
    

    Every other package in the repository is masked by */*::example. emerge --pretend for any of them reports masked by: package.mask.

Troubleshooting

apt-get update reports Missing key …, which is needed to verify signature (Debian 13) or NO_PUBKEY … (Ubuntu 26.04)
The key in Signed-By isn't the one that signed the repository's InRelease file, or the path is wrong. For a repository it had already verified, apt adds that it keeps using the previous index files, so nothing changes until the signature is right. Check the path, and check the fingerprint against the vendor's documentation again; if it still matches, ask the vendor whether it has changed its key.
dnf reports GPG check FAILED or repomd.xml GPG signature verification error
A package or the metadata isn't signed by the key in gpgkey. If the vendor doesn't sign its metadata, remove repo_gpgcheck=1; never set gpgcheck=0.
pacman reports signature from "…" is unknown trust or invalid or corrupted package (PGP signature)
The key hasn't been locally signed, or a different key signed the package. Run sudo pacman-key --lsign-key <FINGERPRINT> with the fingerprint you checked; if the error stays, the package wasn't signed by that key.
emaint sync fails with No valid signature found on Gentoo
The newest commit isn't signed by the key in sync-openpgp-key-path. The local copy of the repository stays at the last commit that was verified. Check that the key file is the vendor's, and ask the vendor whether it signs every commit.

Undo

To remove the repository, uninstall its packages first, then remove the files you added:

sudo apt-get remove -y example-hello
sudo rm /etc/apt/sources.list.d/example.sources /etc/apt/preferences.d/example.pref /etc/apt/keyrings/example.asc
sudo apt-get update
sudo dnf remove -y example-hello
sudo rm /etc/yum.repos.d/example.repo /etc/pki/rpm-gpg/RPM-GPG-KEY-example

Then remove the key from the RPM keyring. RPM stores each key as a gpg-pubkey pseudo-package named after the last eight digits of its fingerprint, in lower case:

$ rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
gpg-pubkey-c2a1e572-668fe8ef    AlmaLinux OS 10 <packager@almalinux.org> public key
gpg-pubkey-01234567-690e1a2b    Example Vendor <packages@example.com> public key
$ sudo rpm -e gpg-pubkey-<KEY_ID>

<KEY_ID> is the eight-digit value from the vendor's line, here 01234567.

Remove the package, delete the [example] section from /etc/pacman.conf (this sed command deletes from the [example] line to the next Server line), and remove the key:

sudo pacman -Rs --noconfirm example-hello
sudo sed -i '/^\[example\]$/,/^Server = /d' /etc/pacman.conf
sudo pacman-key --delete <FINGERPRINT>
pacman-conf --repo-list
sudo emerge --depclean app-misc/example-hello
sudo rm /etc/portage/repos.conf/example.conf /etc/portage/package.mask/example /etc/portage/package.unmask/example /etc/portage/keys/example.asc
sudo rm -r /var/db/repos/example