ads.txt and the AdSense 'Not found' trap
Why AdSense's ads.txt status shows 'Not found' when the file is demonstrably served, what the apex-vs-subdomain confusion actually is, and how to debug it without resubmitting your site forever.
Written by Hong-Bin Yoon · Founder, zzinDev LLC
Published
If you’ve set up an ads.txt file for a site that AdSense has rejected, and the AdSense console says the file is “Not found” — even though you can curl it and get a 200 with the right content — here’s what’s probably happening, and how to get unstuck.
I spent half a day on this. The answer is annoying but understandable once you see it.
The setup
AnimeRecap runs at animerecap.zzin.dev (a subdomain). The apex zzin.dev is a company landing page for the same LLC. Both are separate static sites on Firebase Hosting.
When I registered with AdSense, I registered the site as zzin.dev — the apex — because AdSense’s “Add site” flow wouldn’t let me add the subdomain directly (it said “Looks like you already added this site,” which turns out to mean “subdomains inherit from the apex you already have”).
I put an ads.txt file at animerecap.zzin.dev/ads.txt with the correct publisher ID. I did NOT put one at zzin.dev/ads.txt. A few days later AdSense rejected the site for “Low value content” and, separately, reported ads.txt as “Not found.”
I assumed these were two separate issues. They weren’t, really.
The ads.txt rules, briefly
ads.txt is an IAB-standard file format. Its job is to let a publisher declare “these ad networks are authorized to sell ads on my inventory.” Every line looks like:
<domain>, <publisher_id>, DIRECT, <cert_id>
For Google AdSense, the line is:
google.com, pub-XXXXXXXXXXXX, DIRECT, f08c47fec0942fa0
Where pub-XXXX... is your AdSense publisher ID and f08c47fec0942fa0 is Google’s fixed certification ID.
The file is plain text. It lives at the root of your domain: https://example.com/ads.txt. That’s the spec.
Where the apex-vs-subdomain confusion comes in
The IAB spec and AdSense’s interpretation don’t fully agree on where the authoritative ads.txt lives for a subdomain.
The IAB spec says: “If the hostname is a subdomain of another host, the crawler should also check the parent domain’s ads.txt.” That means for animerecap.zzin.dev, a well-behaved crawler would check both animerecap.zzin.dev/ads.txt AND zzin.dev/ads.txt.
AdSense’s crawler does, in practice, look at both. But the authoritative file — the one whose absence or presence governs the AdSense console’s “Status” column for that site — is the apex’s. If you registered zzin.dev (even though your ads actually show on animerecap.zzin.dev), AdSense wants ads.txt at zzin.dev/ads.txt.
What I had:
animerecap.zzin.dev/ads.txt— 200, correct content. Where my ads actually serve.zzin.dev/ads.txt— 404. The apex was silent.
AdSense console: “ads.txt — Not found.”
The fix
Put ads.txt at the apex. With the same publisher ID line:
google.com, pub-XXXXXXXXXXXX, DIRECT, f08c47fec0942fa0
The IAB spec has an optional subdomain= directive that you can include in an apex ads.txt to explicitly authorize subdomains:
subdomain=animerecap.zzin.dev
google.com, pub-XXXXXXXXXXXX, DIRECT, f08c47fec0942fa0
This tells ad networks “I own both the apex and the subdomain and both are allowed to serve ads under this publisher ID.” If you only have the subdomain’s ads.txt and not the apex’s, some networks will treat the subdomain inventory as unauthorized. Adding the apex file with the subdomain= directive fixes both.
Deploy the file. Wait. AdSense crawls ads.txt on a ~24-48 hour cycle, not on demand. The console status won’t flip to “Authorized” immediately — it’ll update at the next crawl. If your status is still “Not found” after 72 hours, something else is wrong.
The “something else is wrong” checklist
Things that can keep AdSense from seeing your ads.txt even when it’s live:
1. Not actually served at /ads.txt. Firebase Hosting with cleanUrls: true shouldn’t affect .txt files, but a misconfigured rewrite can. Test with: curl -I https://yourdomain.com/ads.txt and look for 200 OK and Content-Type: text/plain.
2. Redirect chain. If example.com/ads.txt redirects to www.example.com/ads.txt (or vice versa), some crawlers follow the redirect and some don’t. Host the file at whichever canonical form your site uses and make sure your DNS and host config don’t redirect it away. If you have an A record on the apex and a CNAME on www, decide which is canonical and serve the file there directly.
3. Robots.txt disallowing crawl. Check your robots.txt. Disallow: /ads.txt would be an extremely bad move. More subtle: Disallow: / on the apex even though you’re serving real content breaks a lot of things, including ad verification.
4. Blocked by CDN or WAF. Cloudflare, for example, has a “Bot Fight Mode” that can refuse requests from unknown user agents. AdSense’s crawler identifies itself but uses a user agent that naive bot-detection rules sometimes flag. Check your logs for AdSense-looking user agents getting 403s.
5. Content-type wrong. The file should be served as text/plain, not text/html. If your static host is adding an HTML content-type because it thinks the file is a template, AdSense’s parser may reject it.
6. Publisher ID mismatch. The pub-XXXX in your ads.txt has to match the account AdSense is using. If you have multiple AdSense accounts (don’t) or the ID has a typo, the line is syntactically valid but semantically wrong and AdSense will treat it as missing.
What “Low value content” actually means when combined with ads.txt issues
The reason I originally conflated these two issues is that AdSense’s rejection notice bundled them:
Low value content. Make sure your site has unique, high-quality content and a good user experience.
ads.txt status: Not found.
I assumed fixing the content would fix both. It won’t. These are two independent checks:
- Content-policy review is a human-judged (partially automated) assessment of whether the site is “substantive” enough to serve ads against.
- ads.txt status is a crawler-verified assertion about publisher ownership.
You can pass content review with no ads.txt (AdSense will serve ads but warn you about it). You can have a perfect ads.txt and fail content review (AdSense won’t serve ads). They’re orthogonal.
If you’re seeing both, fix both. Don’t assume fixing one fixes the other.
Debugging timeline, for perspective
What my actual debugging path looked like:
- Hour 0: AdSense rejects. I see “Low value content” and “ads.txt Not found.”
- Hour 1: I verify ads.txt is live at
animerecap.zzin.dev/ads.txt. It is. I’m confused. - Hour 2: I check the apex.
zzin.dev/ads.txtis 404. I realize AdSense is looking at the apex. - Hour 3: I add ads.txt at the apex, deploy.
- Day 2: Console still says “Not found.” I assume the crawl hasn’t happened yet.
- Day 3: Console updates to “Authorized.” Status resolved.
The “Low value content” issue was a separate fix that took much longer — expanding the apex site into something substantive is the subject of another post.
The minimum viable debug
If you’re in the same position and just want to unblock:
- Check where AdSense registered your site. If it’s the apex, put ads.txt at the apex too, not just at the subdomain where ads serve.
- Use the apex file to authorize subdomains via the
subdomain=directive. curl -Ithe file to verify 200 +text/plain.- Wait 48 hours for AdSense to recrawl before panicking about the console status.
- Don’t resubmit for review just because ads.txt says “Not found” — the ads.txt status updates independently of review status. Resubmitting resets your review queue position for no reason.
Most AdSense ads.txt confusion boils down to “I put the file at the wrong host because AdSense treats the apex as canonical even when my ads serve on a subdomain.” Once you see that, the rest is obvious.
Spot an error or have a suggestion? Request an edit →