Home/Security/NO_PUBKEY apt Error Recovery Runbook — Registering a signed-by Keyring
SecurityNO_PUBKEYapt-key deprecated

NO_PUBKEY apt Error Recovery Runbook — Registering a signed-by Keyring

Is apt update blocked by NO_PUBKEY or "public key is not available"? This diagnostic table and copy-paste runbook shows how to recover safely with signed-by keyring registration and gpg dearmor—instead of the deprecated apt-key.

NO_PUBKEY apt Error Recovery Runbook — Registering a signed-by Keyring
Contents
  1. NO_PUBKEY apt Error 30-Second Recovery Runbook — Registering a signed-by Keyring
  2. A Deployment Pipeline Stalled on One Line of `apt update`
  3. 30-Second Diagnostic Table: Pin Down the Cause from the Error Text
  4. Recovery Path A — The Proper Replacement for apt-key: keyrings + signed-by
  5. Recovery Path B — When You Don't Know the Key URL: Retrieve by key ID from a Keyserver
  6. ⚠️ Security Notes
  7. What Not to Do
  8. One Line from the Field
  9. Recurrence-Prevention Checklist
  10. FAQ

NO_PUBKEY apt Error 30-Second Recovery Runbook — Registering a signed-by Keyring

A Deployment Pipeline Stalled on One Line of apt update

You've probably seen this red line after adding a Docker, Kubernetes, or PostgreSQL repository on a CI runner or a new server and running apt update.

CODE
W: GPG error: https://download.docker.com/linux/ubuntu jammy InRelease:
   The following signatures couldn't be verified because
   the public key is not available: NO_PUBKEY 7EA0A9C3F273FCD8

The most common misread is "Is this a network or firewall problem?" It isn't. apt successfully downloaded the repository metadata (InRelease) and rejected it because the public key needed to verify the signature is not available locally. In other words, NO_PUBKEY is not a download problem—it's a trust problem. So the fix is not "fetch it again" but "safely register that repository's key."

30-Second Diagnostic Table: Pin Down the Cause from the Error Text

Match the string on your terminal against the left column below and jump straight to the matching path.

Error text patternCauseGo to
NO_PUBKEY 7EA0A9C3F273FCD8 (key ID is visible)The repository signing key is not on the local machine. If you know the official key URL, register it the proper wayPath A
The following signatures couldn't be verified because the public key is not availableSame as above. Key not registeredPath A
You don't know the key URL, only NO_PUBKEY <16-digit key ID>Retrieve the key from a keyserver by key IDPath B
EXPKEYSIG 1234ABCD ... <Signing Key>The registered key has expired. Do not bypass; re-download the new keyPath A (key refresh)
Warning: apt-key is deprecated... (see apt-key(8))Using legacy apt-key/trusted.gpg. Slated for removalMigrate via Path A

Recovery Path A — The Proper Replacement for apt-key: keyrings + signed-by

Instead of the deprecated apt-key add, the current standard is to create a per-repository keyring file and explicitly bind it in .sources with signed-by=.

Docker

Bash
# 1) keyring dedicated ( 0755)
sudo install -m 0755 -d /etc/apt/keyrings

# 2) GPG keyring (dearmor)
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# 3) save (signed-by when)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update # NO_PUBKEY check

Kubernetes (pkgs.k8s.io — migrated from the old apt.kubernetes.io)

Bash
# (v1.30) Release dearmor
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key \
  | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /" \
  | sudo tee /etc/apt/sources.list.d/kubernetes.list > /dev/null

In 2023, apt.kubernetes.io was retired and migrated to pkgs.k8s.io, with keys split by version. That's when a lot of servers still pointing at the old URL started hitting NO_PUBKEY.

PostgreSQL (apt.postgresql.org)

Bash
# PGDG
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
  | sudo gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg

echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] \
https://apt.postgresql.org/pub/repos/apt $(. /etc/os-release && echo $VERSION_CODENAME)-pgdg main" \
  | sudo tee /etc/apt/sources.list.d/pgdg.list > /dev/null

If you prefer the deb822 format (.sources), you can write it like this instead of a .list file.

INI
# /etc/apt/sources.list.d/docker.sources
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: jammy
Components: stable
Signed-By: /etc/apt/keyrings/docker.gpg

Recovery Path B — When You Don't Know the Key URL: Retrieve by key ID from a Keyserver

If all you have is the key ID from the error (NO_PUBKEY 7EA0A9C3F273FCD8) and you couldn't find the official key URL, fetch it from a keyserver. This path has man-in-the-middle risk, so you must verify the fingerprint.

Bash
# 1) when keyring ID ( keyring )
gpg --no-default-keyring --keyring /tmp/tmp.gpg \
  --keyserver keyserver.ubuntu.com \
  --recv-keys 7EA0A9C3F273FCD8

# 2) fingerprint check — fingerprint (★required)
gpg --no-default-keyring --keyring /tmp/tmp.gpg --fingerprint 7EA0A9C3F273FCD8

# 3) fingerprint export → dearmor save keyring
gpg --no-default-keyring --keyring /tmp/tmp.gpg \
  --export 7EA0A9C3F273FCD8 \
  | sudo tee /etc/apt/keyrings/<repo>.gpg > /dev/null

# 4) when keyring
rm /tmp/tmp.gpg

From there, bind signed-by=/etc/apt/keyrings/<repo>.gpg in .sources/.list the same way as Path A.

⚠️ Security Notes

  • Why was apt-key add deprecated? The legacy approach dumps every key into a single global keyring (trusted.gpg). That means a key you added for Docker can also verify signatures for the PostgreSQL repository. Compromise one key and the trust boundary expands to the entire system. Per-repository keyrings + signed-by= isolate that trust boundary to a single repository.
  • Risk of the keyserver path: Anyone can upload keys to a keyserver, so a spoofed key with the same key ID can appear. Always compare the full fingerprint from gpg --fingerprint against the official documentation.
  • EXPKEYSIG is not something to bypass: This is a key-expiration signal. Don't ignore it or turn off verification—re-download the new key.

What Not to Do

  • [trusted=yes] — Turns off signature verification entirely, so tampered packages install without complaint.
  • apt-get --allow-unauthenticated — Forces past authentication failures. This opens the door to supply-chain attacks.
  • Dumping keys indiscriminately into /etc/apt/trusted.gpg.d — That regresses to global trust. Keep keyrings in /etc/apt/keyrings and bind them only with signed-by.

One Line from the Field

When you manage a fleet of servers, leftover apt-key add in an Ansible playbook will freeze every deployment the moment you upgrade to Ubuntu 24.04. I standardized keyring registration in the role itself, stored key fingerprints as variables, and asserted them against gpg --fingerprint output—so EXPKEYSIG refreshes became predictable too.

Recurrence-Prevention Checklist

  • Ban global apt-key entirely → /etc/apt/keyrings + signed-by=
  • Keep a separate keyring file per repository (isolation principle)
  • Always compare against the official fingerprint with gpg --fingerprint when registering a key
  • Monitor key expiration to stay ahead of EXPKEYSIG → re-download before expiry
  • Standardize new servers on deb822 .sources instead of .list

FAQ

Q. It still works if I register with apt-key add. Do I really have to switch? A. Ubuntu 22.04 already prints a deprecated warning, and the 24.04 trajectory is toward removing apt-key. Global keyrings also expand the trust boundary, so migrating to signed-by now is the safer move.

Q. Why is gpg --dearmor necessary? A. .asc/.gpg keys are usually ASCII-armored (text), but the keyring that signed-by references must be binary. --dearmor converts the text key into a binary keyring.

Q. EXPKEYSIG appeared. Can I just ignore it and install anyway? A. No. Bypassing an expired key means you can no longer detect supply-chain tampering. The right answer is to re-download the repository's latest key and refresh the keyring.

✦ ✦ ✦
Sources and verification

Sources and checks vary by article. Only articles with a recorded check date, scope and sources below were checked, and only within that scope. For other articles, verify against the current docs for your version before applying anything, and email us about errors. Editorial policy · email us

This article has no recorded human check (date and scope) yet.

Published ·
Topic-related official docs (auto-linked · not per-claim sources)OWASP documentation ↗

Comments

Be the first to comment.