18 April 2026 · 13 min read
"DKIM public key not found in DNS" is one of those errors that sounds catastrophic and is almost always trivial. The signature your mail server attached is fine. The private key on the sending side is fine. The only thing that has gone wrong is that the receiving server looked up a specific DNS name, got nothing back, and gave up. Fix the lookup and the signature verifies. This guide is about that lookup: exactly which name gets queried, why it so often returns nothing, and how to confirm the key resolves before you waste another day blaming your email platform.
The error shows up in a few different wordings depending on who is reporting it. A Gmail or Microsoft DMARC aggregate report will show dkim=fail (or dkim=none) for the relevant selector. A message you send to yourself, opened with Show original in Gmail, will say DKIM: 'FAIL' with domain example.com or dkim=temperror. A mail server log might say DKIM-Signature missing public key or no key for signature. Postmark, SendGrid, Amazon SES and similar platforms surface it in their dashboards as "DKIM not verified" or "pending". They are all describing the same thing: the p= value (the public key) could not be retrieved from the DNS name the signature pointed at.
If you want to confirm what receivers actually see before reading further, run your domain and selector through the DKIM checker. That tool does the same query a receiver does, and it will tell you immediately whether the answer is "no record", "record present but malformed", or "key resolves correctly".
How the lookup actually works
A DKIM signature is a header that mail servers add to outgoing mail. It looks roughly like this:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=s1;
h=from:to:subject:date:message-id;
bh=...; b=...
Two tags in there drive the DNS lookup, and they are the only two that matter for this error:
d=is the signing domain. Here it isexample.com.s=is the selector. Here it iss1.
The receiving server takes those two values and constructs exactly one DNS name to query. The construction is mechanical and never varies:
<selector>._domainkey.<domain>
So for s=s1 and d=example.com, the receiver looks up:
s1._domainkey.example.com
It queries that name for a TXT record. The answer is expected to contain the public key in a p= tag. If the query returns NXDOMAIN (the name does not exist), or returns a record with no usable p=, you get "public key not found". There is no fallback, no second guess, no wildcard. The name is either there with a valid key, or the signature fails.
This is the single most important thing to internalise: the receiver does not know or care what you intended to publish. It only knows the two strings inside the signature header, and it builds one name from them. Ninety per cent of "key not found" problems are a mismatch between the name the signature points at and the name where the key actually lives.
Find the exact name being queried
Before touching DNS, get the literal selector and domain out of a real message. Do not guess them. Send an email from the affected system to a Gmail account you control, open it, and use Show original. Look at the DKIM-Signature header and read off the d= and s= values. Concatenate them as s=._domainkey.d=.
If your provider uses multiple selectors (rotating keys, or separate selectors for different mail streams) you may see more than one signature on the same message, each with its own s=. Every one of them needs a resolvable record. A common failure is publishing one selector and missing the second.
Once you have the exact name, query it directly. On macOS or Linux:
dig +short txt s1._domainkey.example.com
On Windows PowerShell:
Resolve-DnsName -Type TXT s1._domainkey.example.com
Three outcomes, and each tells you what to do next:
- Nothing comes back / NXDOMAIN. The record is missing, published at the wrong name, or has not propagated yet. This is the most common case. Read the naming and CNAME sections below.
- A record comes back but it is not a key (for example an A record, or a TXT that does not start with
v=DKIM1and has nop=). You published the right name but the wrong type or value. - A
v=DKIM1; ... p=MIIB...record comes back. The key resolves. If receivers still report failure, your problem is not "key not found"; it is alignment, key mismatch, or a rotated key. Skip to the verification section.
Selector and _domainkey naming: the mistakes that cause NXDOMAIN
The naming convention has three parts and people drop or mangle each of them.
The selector must match the signature exactly. Selectors are case-insensitive in DNS but otherwise literal. If your platform signs with s=google you must publish under google._domainkey, not s1._domainkey or default._domainkey. Different platforms use wildly different selector names: Google Workspace defaults to google, Amazon SES uses generated tokens like abcdef1234._domainkey, Microsoft 365 uses selector1 and selector2, Mailchimp uses k1, Postmark uses a per-server selector. There is no universal selector. Whatever is in the s= tag is the only correct answer.
_domainkey is mandatory, literal, and keeps its underscore. The label is _domainkey, not domainkey, not _dkim, not dkim. The leading underscore is part of the name. The most common typo here is publishing s1.domainkey.example.com (no underscore) or s1._dkim.example.com. Both resolve to nothing because the receiver is querying s1._domainkey.example.com.
The domain in d= must be the zone you published in. If you sign with d=mail.example.com but published the selector under example.com, the receiver queries s1._domainkey.mail.example.com and finds nothing. Subdomain signing is legitimate and common (many platforms sign with a subdomain), but the key has to live under that exact subdomain.
The other naming trap is host-specific and worth calling out because it bites almost everyone at least once. Many DNS editors auto-append the zone name to whatever you type in the Name or Host field. On Cloudflare, GoDaddy, Namecheap and others, if you type the full name s1._domainkey.example.com into the Name field, the editor stores it as s1._domainkey.example.com.example.com. The record exists, but at a name no receiver will ever query, so the lookup returns nothing. The fix is to enter only the prefix: s1._domainkey. Conversely, a few editors do not auto-append, and there you must type the full name. The only way to be sure which behaviour you are dealing with is to publish, then query the resulting name with dig and see where it actually landed. Our host-specific guides cover this for each provider; for example adding DMARC, SPF and DKIM on Cloudflare, on GoDaddy and on Namecheap.
CNAME versus TXT publishing
There are two legitimate ways to publish a DKIM key, and confusing them is a frequent cause of "key not found".
Direct TXT publishing. You hold the private key, and you paste the matching public key into a TXT record at the selector name. The value looks like this, on a single logical line:
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
This is what you get when you generate your own key pair, or when a platform gives you a TXT value to paste. The whole key has to be present and unbroken. If the p= value is truncated or has stray spaces in the middle, the lookup "succeeds" but verification fails with a malformed-key error rather than "not found".
CNAME delegation. This is how Google Workspace, Amazon SES, Mailchimp, SendGrid, Postmark and most managed platforms prefer to do it. You do not hold the key at all. Instead, you publish a CNAME at your selector name that points at a hostname the platform controls, and the actual key lives there. The platform can then rotate the key whenever it likes without you touching DNS. A typical SES setup looks like:
abcdef1234._domainkey.example.com. CNAME abcdef1234.dkim.amazonses.com.
The receiver queries abcdef1234._domainkey.example.com, follows the CNAME to abcdef1234.dkim.amazonses.com, and reads the TXT key from there. To you it looks like one hop; to the receiver it is transparent.
The CNAME-specific failure modes:
- You published a TXT where a CNAME was expected (or vice versa). If your platform gives you a CNAME target, do not paste it into a TXT record. A TXT containing
abcdef1234.dkim.amazonses.comis not a key and will fail. It must be a CNAME record type. - You created a CNAME but the target has no key yet, or the target is wrong by one character. Follow the chain manually:
dig +short abcdef1234._domainkey.example.comshould return the CNAME target; thendig +short txt abcdef1234.dkim.amazonses.comshould return the actualv=DKIM1; ... p=...key. If the second query is empty, the platform side is not ready or the target is mistyped. - A CNAME cannot coexist with other records at the same name. DNS forbids a CNAME alongside any other record type on the same name. If you already have a TXT at
s1._domainkeyand try to add a CNAME there, your provider will either reject it or silently misbehave. Remove the conflicting record. - Proxying / "orange cloud" on the CNAME. On Cloudflare specifically, a DKIM CNAME must be set to DNS only (grey cloud). If it is proxied, Cloudflare answers the query itself instead of returning the CNAME, and the receiver never reaches the real key.
When in doubt about which model your provider uses, the provider's own setup screen is authoritative: it will either show you a long p=... string to paste as TXT, or a host -> target pair to add as CNAME. Match the record type to what they show. Our platform guides spell out the exact record type per provider, for instance Amazon SES, SendGrid and Mailchimp.
Propagation: when it is right but still "not found"
If you have confirmed the record is at the correct name with the correct type and value, but receivers (or your platform's verify button) still say "not found", the remaining suspect is time.
DNS changes are not instant. A receiver may have cached the previous answer for that name, including a cached NXDOMAIN (the "negative cache") if it looked up the name before you created the record. Negative caching is the sneaky one: the receiver remembers that the name did not exist, and keeps returning that cached "no" until the timer expires, even though you have since created the record.
How long this lasts depends on TTL:
- The record's own TTL governs how long a positive answer is cached once it is seen. Lower TTL (300 to 3600 seconds) means faster future updates.
- The zone's SOA minimum / negative TTL governs how long an NXDOMAIN is cached. If your SOA negative TTL is, say, 3600 seconds, a receiver that queried too early may keep saying "not found" for up to an hour after you publish.
Practical guidance:
- After publishing, query an authoritative nameserver directly to confirm the record is live at the source, bypassing caches:
dig +short txt s1._domainkey.example.com @<your-authoritative-ns>. If the authoritative server returns the key, the record is correct and you are now only waiting for caches elsewhere to expire. - Check propagation across multiple public resolvers with the DNS propagation checker. If some resolvers show the key and others do not, you are mid-propagation; wait and re-check.
- Do not hammer the platform's "verify" button every thirty seconds. Each failed check can re-prime a negative cache on the path between the platform and your DNS. Publish, wait for at least the negative TTL, then verify once.
- If you are migrating or you set a long TTL by mistake, lower the TTL on the selector to 300 seconds, wait out the old TTL, and future changes will settle in minutes.
As a rule of thumb, most correctly published DKIM records resolve everywhere within an hour, and very often within a few minutes. If a name is still returning nothing after a few hours and the authoritative server clearly has it, the problem is not propagation: it is that the resolver you are testing from is querying a different name than the one you published, which loops you back to the naming section.
Confirm the key resolves, end to end
Once the record is published and propagated, verify it properly rather than trusting a green tick on one dashboard. Work through these in order:
- Resolve the name yourself.
dig +short txt s1._domainkey.example.comshould return a string startingv=DKIM1and containing a longp=value. For a CNAME setup, follow the chain as shown above until you reach thep=. - Check the key with the DKIM checker. Enter your domain and the exact selector from the signature. It validates the record syntax, confirms the
p=is well formed and decodes to a usable key, and flags common issues like an emptyp=(which is how a revoked key is published) or a key that is too short. - Send a live test and read the result. Mail yourself, open Show original in Gmail or view the authentication headers, and confirm it now says
dkim=passwith the rightheader.d=domain. - Confirm alignment, not just a pass. A DKIM signature can verify perfectly and still fail DMARC if the
d=domain does not align with theFrom:domain. That is a separate problem with a separate fix; if your signature passes but DMARC still fails, read DMARC alignment explained and SPF versus DKIM, which one aligns.
A note on the empty-key case, because it confuses people who are debugging a key that "used to work". A DKIM record published as v=DKIM1; p= with nothing after p= is not an error in the DNS sense; the lookup succeeds. But it explicitly means "this key is revoked". Platforms publish this when you rotate or remove a key. If your test shows the name resolving to an empty p=, the key was deliberately revoked, and you need to publish the current selector, not resurrect the old one. See DKIM key rotation for how rotation is meant to work without breaking mail.
A worked example
Suppose Gmail reports dkim=fail for mail from news.example.com sent via Amazon SES. Walking the process:
- Open a delivered message, Show original, read the signature:
d=news.example.com; s=hk7w2egcc. - The name the receiver queries is therefore
hk7w2egcc._domainkey.news.example.com. dig +short txt hk7w2egcc._domainkey.news.example.comreturns nothing.dig +short hk7w2egcc._domainkey.news.example.com(any type) also returns nothing, so the name does not exist at all. This is a publishing problem, not a key problem.- Check the DNS editor: the CNAME was added with Host set to
hk7w2egcc._domainkey.news.example.com, and the provider auto-appended the zone, so it actually lives athk7w2egcc._domainkey.news.example.com.example.com. Confirmed by querying that mangled name and getting the record back. - Recreate the CNAME with Host set to just
hk7w2egcc._domainkey.news(the prefix only), targethk7w2egcc.dkim.amazonses.com, DNS only if on Cloudflare. - Query the authoritative nameserver directly: the CNAME now resolves and chains to a real
p=key. - Wait out the negative TTL, click verify once in the SES console, and it flips to verified. Next aggregate report shows
dkim=pass.
Every step there is mechanical. There is no point in the process where you have to "try something and see"; you find the exact name, you query it, and you reconcile where the record actually lives against where it needs to live.
The practical takeaway
"DKIM public key not found in DNS" is a naming and publishing error in almost every case, not a cryptographic one. The fix is always the same shape:
- Read the literal
d=ands=from a real signature; never assume the selector. - Build the one name the receiver queries:
<selector>._domainkey.<domain>. - Query that exact name with
digor the DKIM checker and see whether it returns nothing, the wrong type, or a validp=key. - Reconcile the three usual culprits: a mangled name from an auto-appending DNS editor, a TXT-versus-CNAME mismatch, or a record that is correct but still inside its negative-cache window.
If you would rather not chase selectors and propagation by hand every time a platform rotates a key, that is exactly what a hosted setup removes. DMARC Engine publishes and watches your DKIM, SPF and DMARC records for you, alerts you the moment a selector stops resolving, and walks a domain from p=none to p=reject without breaking mail. See managed DKIM and how monitoring and change alerts work, or start by confirming the current state of your selector with the DKIM checker.