BettaDev

The redirect that quietly
stops AdMob paying you

What the file is for

app-ads.txt is a plain text file you host on your own domain that says, in effect, these ad networks are allowed to sell inventory in my app. Your app's store listing points at a developer website; the crawler goes to that website, looks for /app-ads.txt, and checks that your ad network's publisher ID is listed. It is one line:

google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0

The point of it is fraud prevention. Without the file, anyone can claim to be selling your app's ad space and buyers have no way to check. So buyers increasingly refuse unverified inventory outright, which is why a missing or unreadable file does not produce an error anywhere — it produces lower bids. Your app keeps running, ads keep showing, and the revenue is simply worse than it should be. There is no alert for this.

The rule everyone misses

The specification is short and the important sentence is easy to skim past: the crawler fetches the file over HTTP and does not follow redirects outside a narrow set of cases. A 301 or 302 on that path is treated as "no file here".

Three ways to fail it, all of which look completely fine in a browser:

  • A redirect. Your host normalises the URL, or bounces the apex to www, or a framework's router catches unknown paths and sends them somewhere friendly.
  • The wrong content type. The file has to arrive as text/plain. Serve it as text/html and it is rejected, even when the bytes are correct.
  • The wrong host. It belongs on the exact domain in your store listing. If the listing says example.com, then www.example.com/app-ads.txt is not the file being looked for.

How this breaks on Cloudflare

On a Cloudflare Workers static site the failure mode is specific and worth knowing about, because it is a side effect of a feature you probably want.

Workers assets have an html_handling setting that gives you clean URLs — /privacy serving privacy.html, with the .html form redirecting to the clean one. That redirect behaviour is the whole point of the feature. The risk is what happens to non-HTML files that sit near a rule you did not think about: a trailing-slash normalisation, a Bulk Redirect rule, a Page Rule left over from a migration, or an apex-to-www redirect set up before the site had a Worker at all.

Any one of those can put a 301 in front of /app-ads.txt. The page still loads perfectly when you click it, which is exactly why nobody catches it.

Keep the file at the root of your published directory, keep it out of any routing logic, and check that no Redirect Rule matches its path. On Workers assets there is a second, quieter benefit to putting it in the served directory rather than generating it: it cannot be affected by a change to your routing later.

Proving it works

Do not test in a browser. A browser follows redirects silently and tells you nothing. Ask curl for the whole story instead:

curl -sS -o /dev/null -w '%{http_code} %{content_type} %{num_redirects}\n' \
  https://example.com/app-ads.txt

You want exactly 200 text/plain 0. A 301, a text/html, or any redirect count above zero is a problem, and each of the three fails you for a different reason.

Then wait. Crawls are periodic, not instant — it is normal for a fix to take days to show up as "found" on the ad network's side. Fix it, verify it with curl, and stop refreshing the dashboard.

Also check the file after every DNS or hosting change. This is the kind of thing that works for a year and then silently stops the week you move a domain between accounts.

The website version, and subdomains

app-ads.txt covers apps. The equivalent for a website is ads.txt, same format, same no-redirect rule, same silent failure. If you run both an ad-supported app and an ad-supported site, you need both files, and they can happily sit side by side at the root.

Subdomains are the part that trips people up. The crawler looks for the file on the exact host it saw the ad on, and only falls back to the root domain afterwards. If you host projects on subdomains — and separate Workers or separate deployments make that easy — the safest thing is to ship an identical copy of ads.txt from every one of them. It is a fifteen-byte file. Copying it is cheaper than discovering six months later that one subdomain has been serving unverified inventory the whole time.