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.
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 7EA0A9C3F273FCD8The 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 pattern | Cause | Go 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 way | Path A |
The following signatures couldn't be verified because the public key is not available | Same as above. Key not registered | Path A |
You don't know the key URL, only NO_PUBKEY <16-digit key ID> | Retrieve the key from a keyserver by key ID | Path B |
EXPKEYSIG 1234ABCD ... <Signing Key> | The registered key has expired. Do not bypass; re-download the new key | Path A (key refresh) |
Warning: apt-key is deprecated... (see apt-key(8)) | Using legacy apt-key/trusted.gpg. Slated for removal | Migrate 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
# 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 checkKubernetes (pkgs.k8s.io — migrated from the old apt.kubernetes.io)
# (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/nullIn 2023,
apt.kubernetes.iowas retired and migrated topkgs.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)
# 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/nullIf you prefer the deb822 format (.sources), you can write it like this instead of a .list file.
# /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.gpgRecovery 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.
# 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.gpgFrom there, bind signed-by=/etc/apt/keyrings/<repo>.gpg in .sources/.list the same way as Path A.
⚠️ Security Notes
- Why was
apt-key adddeprecated? 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 --fingerprintagainst 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/keyringsand bind them only withsigned-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-keyentirely →/etc/apt/keyrings+signed-by= - Keep a separate keyring file per repository (isolation principle)
- Always compare against the official fingerprint with
gpg --fingerprintwhen registering a key - Monitor key expiration to stay ahead of EXPKEYSIG → re-download before expiry
- Standardize new servers on deb822
.sourcesinstead 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 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.
Comments
Be the first to comment.