Table of contents
- Quick summary
- Why a sitemap gets graded as a file and used as a promise
- The corpus and the three numbers D2 gives
- The method: open the file, then open the pages
- Finding 1: the file is usually real
- Finding 2: a third of sitemaps cannot say when anything changed, or say it was long ago
- Finding 3: the URLs mostly resolve, and the exceptions are instructive
- The funnel: from "has a sitemap" to "has a sitemap that works"
- Finding 4: most D2 failures are real, and some are not
- What AIScan checks, and what it cannot see
- How to fix each leg
- Why `lastmod` is the field worth getting right
- What this study does not claim
- A ten-minute monthly review
- Common mistakes
- Your next action
A sitemap that returns HTTP 200 is not the same as a sitemap an agent can use. In AIScan's corpus of 669 sites, check D2 passes 499 of them (74.6%), and every one of those passes carries the same evidence string: a URL and "200 OK". We took 90 of the passing sites and opened the file. 87 were real sitemaps, but only 53 (58.9%) were also dated within the last 90 days and linked to pages that all resolved. That 58.9% is the number to fix.
Quick summary
| Question | What we measured | Result |
|---|---|---|
| How many sites pass D2 (XML sitemap)? | 669 sites, rubric 2026.08.2, latest scan per host, aiscan.site excluded | 499 pass (74.6%), 170 fail (25.4%) |
| What does a D2 pass assert? | Evidence strings, grouped | A URL and "200 OK", nothing else |
| Is the passing file a sitemap? | 90 passing sites re-probed on 1 October 2026 | 87 serve valid sitemap XML (96.7%) |
| Does it say when pages changed? | The same 87 sites | 72 carry lastmod (82.8%); 15 carry none |
| Is it fresh? | Newest lastmod, same 87 | 58 within 90 days; 14 older (7 of them over a year) |
| Do its URLs resolve? | 5 random URLs per sitemap, 86 sampled | 80 sites had all 5 return 200 (93.0%) |
| Discoverable, valid, fresh and resolving at once | The funnel below | 53 of 90 (58.9%) |
| Are D2 failures real? | 60 failing sites re-probed | 48 (80%) serve no sitemap on any path we tried; 12 (20%) now serve one |
Industry guidance on what makes a sitemap worth fetching: According to Google Search Central, a single sitemap is limited to 50MB uncompressed or 50,000 URLs, Google ignores <priority> and <changefreq>, and it uses <lastmod> only if the value is "consistently and verifiably" accurate. That last condition is the one most sites fail quietly.
Best for a quick check: run the scan, read D2 and D1, then run the three curl commands later in this post. Best for a WordPress site: pick one plugin to own sitemap, robots.txt and llms.txt so they stop contradicting each other. Best for a large site: split by content type and make lastmod come from the content database, not the build clock.
Why a sitemap gets graded as a file and used as a promise
An agent that wants to know what a site contains has two options. It can crawl links from the homepage, which is slow and misses orphaned pages, or it can read the sitemap, which is the site's own list of what matters. The sitemaps.org protocol defines that list, and RFC 9309 lets robots.txt point to it with a Sitemap: line.
The sitemap is useful only if three things hold. The agent can find it. It parses. And what it says is true: the URLs exist and the dates mean something. A check that only tests the first leg gives a site credit for a promise it has not made yet.
AIScan's D2 check is a good example, and we say that about our own check deliberately. Its failure string reads "no /sitemap.xml or /sitemap_index.xml resolved". That names exactly two paths. Its pass string is a URL followed by a status code. Grouping the evidence strings across all 669 sites shows no pass that mentions XML validity, URL counts, dates or link health. D2 is a presence check, and it is honest about being one, but the 74.6% headline reads as more than it is.
This post measures the gap. It extends the earlier piece on llms.txt, robots.txt and sitemap.xml and the job each one does by asking how many of the sitemaps that exist are doing that job. The soft 404 write-up covers a related trap: a path that answers 200 with a web page where a file should be.
The corpus and the three numbers D2 gives
Query: the latest scan of each host on rubric 2026.08.2, aiscan.site excluded, 669 hosts, scans dated 25 August to 1 October 2026.
| Check | Pass | Partial | Fail | Evidence on a pass |
|---|---|---|---|---|
| D2, XML sitemap | 499 (74.6%) | none | 170 (25.4%) | A URL and "200 OK" |
| D1, robots.txt present and sane | 479 (71.6%) | 86 (12.9%) | 104 (15.5%) | Status, byte size, "declares Sitemap" |
| E5, content feed | 175 (26.2%) | 25 (3.7%) | 92 (13.8%) | Feed URL and how it was found |
E5 reports "not applicable" on 377 sites that show no periodical content section, so its denominator is 292, not 669. Of those 292, 175 pass (59.9%), 25 find a feed by convention but never declare it in <head>, and 92 have none.
D1 carries the part of this story that is easy to miss. Its pass string can end "declares Sitemap", which means robots.txt contains a Sitemap: line. That is the discoverability leg, and it comes from a different check than D2. A site can pass one and fail the other.
Two things this dataset is not. It is not a random sample of the web: these are sites someone chose to scan, which skews toward people who care about the question, and toward WordPress and small business sites. And one scan per host means a verdict is a snapshot. Half the point of the next section is how fast that snapshot ages.
The method: open the file, then open the pages
We drew 90 hosts from the 499 D2 passes and 60 from the 170 D2 fails, ordered by an MD5 hash of the hostname so the pick did not depend on scan order. On 1 October 2026 each host got this treatment, with a desktop Chrome user agent, redirects followed:
- Fetch
/robots.txtand collect everySitemap:line. - Fetch
/sitemap.xmland/sitemap_index.xml, plus any declared URL that differs. - Parse the response. Count it as a sitemap only if it contains a
<urlset>or<sitemapindex>element. A 200 that returns HTML does not count. - For an index, open the first child sitemap and use that.
- Record the newest
<lastmod>, ignoring any date after 2 October 2026. - Pick five URLs at random from the sitemap and request each one. A URL counts as resolving only on 200 after redirects.
The limits are real. One cloud IP means a site that blocks datacentre traffic can look worse than it is; we saw this and name it where it happened. Five URLs per site is a spot check, not a crawl. For indexes we read only the first child sitemap, which can be the oldest one, so index freshness numbers lean low. A lastmod date is a claim the site makes. We did not compare it to the page's real modification time, which is the test Google describes.
Finding 1: the file is usually real
Of 90 passing sites, 87 serve valid sitemap XML. That is the good news, and it is worth stating plainly: a D2 pass is a reliable sign that a sitemap exists.
The 3 that did not parse, and what each looked like:
| Site | What we saw | Reading |
|---|---|---|
| A | HTTP 403 on both conventional paths | A bot wall on our IP; the file passed at scan time. We do not count this as dead |
| B | /sitemap.xml and /sitemap_index.xml return 404; robots.txt declares a URL with a .xmls extension | A typo in the declared path, and the file at the conventional path is gone |
| C | /sitemap_index.xml returns 200 with a web page, not XML | A soft 404: the status passes, the content is not a sitemap |
Half the valid files (44 of 87) are sitemap indexes rather than single files. Opening the first child worked for 43 of them. In the other the child failed to parse. Across the 60 failing sites, 7 more indexes turned up and 2 had a broken first child. So in 3 of 51 sitemap indexes the index existed and its first listed file did not.
A small observation on discovery. 81 of the 90 passing sites (90.0%) declare a sitemap in robots.txt, and 26 of them declare a URL other than the two conventional paths. For 23 of those, the conventional paths work too. For 3, the declared URL is the only place the sitemap lives. An agent that probes only /sitemap.xml misses those 3; an agent that reads robots.txt finds them. That is the argument for doing both.
Finding 2: a third of sitemaps cannot say when anything changed, or say it was long ago
Freshness is where the funnel narrows. Of the 87 valid sitemaps:
| Freshness | Sites | Share of 87 |
|---|---|---|
Newest lastmod within 30 days | 47 | 54.0% |
Newest lastmod 31 to 90 days ago | 11 | 12.6% |
Newest lastmod 91 to 365 days ago | 7 | 8.0% |
Newest lastmod over 365 days ago | 7 | 8.0% |
No lastmod anywhere | 15 | 17.2% |
15 sites (17.2%) publish a list of URLs with no dates at all. For an agent deciding what to re-fetch, that is a list with no signal: every URL looks equally stale or equally new. The protocol does not require lastmod, so none of these sites is wrong, but each has given up the one field that tells a crawler where to spend its requests. This is the same logic as our earlier measurement of who can answer "nothing changed" with a 304: a site that cannot signal change makes every visit a full download.
Across the whole 87, 58 are fresh by our 90-day rule. The 14 stale ones split in half: 7 are 91 to 365 days old, 7 are older than a year. Stale does not mean wrong. A brochure site that has not changed in 18 months has an honest sitemap. The test is whether the date is accurate, and for a site with a blog and a newest lastmod of two years ago, it is not.
The oldest sitemap in the whole probe, from a site in the failing group, lists 16,741 URLs whose newest lastmod is October 2018. That file is large and confident about a date that cannot be right for a site still publishing in 2026. It is a good picture of the failure: a sitemap generated once and never touched.
Finding 3: the URLs mostly resolve, and the exceptions are instructive
We sampled five URLs from each of 86 valid sitemaps (one valid index had no readable child). 80 sites (93.0%) had all five return 200. That is a better number than the freshness one, and it deserves credit.
The other 6 split into two groups:
| Result | Sites | Examples of what happened |
|---|---|---|
| Some of the 5 failed | 3 | One 404 among five; one 307 that never settled on a 200; four of five 404 on a staging-style hostname |
| None of the 5 resolved | 3 | All five returned 403 (a bot wall); connection failures on all five; 404 or timeouts on all five |
The 403 case matters for how you read every measurement here, ours included. A site that lists its own pages in a sitemap and then refuses to serve them to the client that read the sitemap has told an agent where to go and then shut the door. That could be a deliberate rule for unrecognised clients, or a CDN setting nobody reviewed. From outside, the effect is identical. The Cloudflare bot-block change shows how easily the second case arises.
The staging hostname case is the pattern we expected to see more of: a sitemap generated on one environment and deployed with absolute URLs from another. It is a one-line fix in most build tools, and it is invisible to a check that stops at the status code.
The funnel: from "has a sitemap" to "has a sitemap that works"
Sequential survivors among the 90 sites that D2 passed:
| Stage | Sites remaining | Share of 90 | Dropped |
|---|---|---|---|
| D2 passes | 90 | 100% | |
| Serves valid sitemap XML today | 87 | 96.7% | 3 |
Declares any lastmod | 72 | 80.0% | 15 |
Newest lastmod within 90 days | 58 | 64.4% | 14 |
| All 5 sampled URLs return 200 | 53 | 58.9% | 5 |
The sitemap funnel is the original framework here: discoverable, valid, fresh, resolving. A site clears it only if it survives all four stages. 58.9% is a conservative figure. It treats the 15 undated sitemaps as unproven rather than fresh, and it uses the first child of an index, which tends to be the least recently updated one.
The biggest single drop is not a broken file. It is the 15 sites that never say when anything changed. The smallest is the 3 that no longer serve a sitemap at all. The practical lesson is that the cheapest improvement for most sites is to emit lastmod correctly, not to build or repair a sitemap.
Finding 4: most D2 failures are real, and some are not
We probed 60 of the 170 D2 failures the same way, and added four more paths for the ones with no sitemap: /wp-sitemap.xml, /sitemap-index.xml, /sitemap/sitemap.xml and /sitemaps.xml.
| Result | Sites | Share of 60 |
|---|---|---|
| No sitemap at either conventional path, none declared and valid, none at four alternatives | 48 | 80.0% |
Valid sitemap now served at /sitemap.xml or /sitemap_index.xml | 11 | 18.3% |
| Valid sitemap served only at the URL robots.txt declares | 1 | 1.7% |
Of those 48, none answered on any of the four alternative paths. That settles a worry we had going in: that D2 might be failing sites whose sitemap lives under a name it does not request. For this sample it was not. WordPress core publishes /wp-sitemap.xml, and not one of the 48 had it. D2's failures here are mostly genuine absences.
The 12 sites with a sitemap now are the interesting part. Their last scans were dated 28 August to 28 September: 9 were scanned before 15 September, and 3 within the last nine days. A site that scanned as a fail on 25 August and has a sitemap on 1 October most likely added one in the meantime, which is a fix and is good news. For the 3 recent ones the explanation is less comfortable: either the site fixed itself within days of the scan, or the scanner met a transient error. We cannot tell which from the evidence stored, and the evidence string, "no /sitemap.xml or /sitemap_index.xml resolved", does not record a status code. That is a limit of D2 worth fixing and we have logged it as a product note.
One more caution for readers who quote a failing D2 to someone else: re-run the scan first. A fail is a snapshot.
What AIScan checks, and what it cannot see
Run the scan first. npx aiscan-cli yoursite.com, or paste your URL at aiscan.site (free, no account). Three checks matter for this post:
| Check | What it asserts | What it does not assert |
|---|---|---|
| D2, XML sitemap | A sitemap URL answers 200 at /sitemap.xml or /sitemap_index.xml | That it is valid XML, how many URLs it lists, how fresh it is, or whether the URLs resolve |
| D1, robots.txt present and sane | robots.txt answers, and whether it declares a Sitemap: line | That the declared URL works |
| E5, content feed | An RSS, Atom or JSON feed exists and whether <head> declares it | That the feed's items are current |
Read the three together. A site that passes D2 and D1 has a sitemap and tells agents where it is. Add E5 for sites that publish regularly: a feed is the other freshness signal, and the guide to adding an RSS feed AI agents can find walks through it. Our own check cannot see the four legs of the funnel above. That is why we ran the probe ourselves, and why a clean scan is where this work starts, not where it ends.
If you would rather check by hand, three commands cover it:
# 1. Is it declared? (D1)
curl -sL https://yoursite.com/robots.txt | grep -i '^sitemap:'
# 2. Is it XML, and how many URLs? (D2, plus validity)
curl -sL https://yoursite.com/sitemap.xml | grep -c '<loc>'
# 3. Is it fresh? Newest lastmod in the file
curl -sL https://yoursite.com/sitemap.xml | grep -o '<lastmod>[^<]*' | sort | tail -1
For an index, repeat commands 2 and 3 against each child file it lists. To spot-check the links, take five <loc> values and request each one; anything but 200 is a finding.
How to fix each leg
Not discovered. Add Sitemap: https://yoursite.com/sitemap.xml to robots.txt, with an absolute URL. The line is independent of any User-agent group. If your sitemap lives somewhere unusual, this line is the only way an agent finds it, which is what happened to 3 of our 90 sites.
Not valid. If the URL returns 200 with an HTML page, your router is serving a catch-all page. Return a real 404 for files that do not exist and the XML for the file that does. The soft 404 guide covers the mechanics. Check size too: Google's limit is 50,000 URLs or 50MB uncompressed per file, and larger sites need an index.
Not fresh. Take lastmod from the content record's modification timestamp, not from the time the sitemap was built. Rebuilding on every deploy stamps every URL with today's date, which Google's guidance warns against by requiring dates to be verifiably accurate. If your tooling cannot give a true date, omit the field for those URLs rather than faking it. An absent date is honest; a wrong date trains crawlers to ignore all your dates.
Not resolving. Generate absolute URLs from your production hostname and fail the build if the sitemap host differs from it. Then add a post-deploy check that fetches a sample of the URLs. If a CDN rule returns 403 to non-browser clients, decide on purpose whether the listed pages should be reachable by crawlers, because the sitemap says they should be.
On WordPress. Core already serves /wp-sitemap.xml, but D2 does not request that path, so declare it in robots.txt or alias it to /sitemap.xml. ThinkRank is the plugin we recommend as the default here because it manages sitemap settings, robots.txt, robots meta, schema and llms.txt from one place, so there is one plugin deciding what each file says instead of three fighting over robots.txt, and it migrates from Rank Math, Yoast, AIOSEO and SEOPress. Yoast, Rank Math and AIOSEO each generate sitemaps well, and any of them is fine if it already runs your site, provided only one is writing the file.
On Shopify. The platform generates /sitemap.xml for you, so the work is checking that robots.txt points to it. For stores, StoreSEO is the default recommendation for the half of the stack the platform does not touch: schema, llms.txt and the AEO side.
Why lastmod is the field worth getting right
Google states the rule precisely: the date is used if it is consistently and verifiably accurate, and the value should reflect the last significant change to the page. "Verifiably" is the word that matters. A crawler can request the page, read its Last-Modified header or compare the content to the last copy it saw, and decide whether your date was telling the truth. A site that stamps every URL with the build time fails that comparison on the second visit. After that the crawler has a reason to treat the whole field as noise, including the dates that were right.
You can run the same test on your own site with two requests. Take one URL and its sitemap date:
# date the sitemap claims for one URL
curl -sL https://yoursite.com/sitemap.xml | grep -A2 'yoursite.com/some-post' | grep -o '<lastmod>[^<]*'
# date the page itself reports
curl -sIL https://yoursite.com/some-post | grep -i '^last-modified'
If the two disagree by months, one of them is wrong, and an agent that checks will not guess which. If the page sends no Last-Modified header at all, the sitemap date is unverifiable from outside, which is the position most dynamic sites are in. That is a reason to keep the sitemap date honest, since it may be the only date you publish.
There is a cost argument as well. Our probe fetched five pages per site and that was enough to find broken hosts and bot walls. An agent working at scale fetches far more. Each stale or dateless entry makes it choose between re-fetching everything, which costs you bandwidth and server time, or trusting an old copy, which costs the reader accuracy. Accurate dates let it do neither.
What this study does not claim
Four limits are worth restating, because the headline number is easy to quote without them.
First, 90 passing and 60 failing sites is a spot check. The 58.9% funnel figure has a wide interval at this size, and it describes sites that were scanned, not the web. We would expect the funnel to look worse on a random sample, because people who run a scanner tend to have looked at their discovery files at least once.
Second, five sampled URLs per sitemap finds a site where nothing resolves and misses a site where 3% of a 20,000-URL file is dead. The 93.0% all-five-resolve figure is an upper bound on link health.
Third, we read the first child of each sitemap index. For a WordPress site that is often the posts file or the pages file, depending on the plugin. Either may be the least recently updated, so the freshness numbers for the 44 indexes lean pessimistic.
Fourth, we probed from one cloud address on one day. Three sites refused us outright. Some of those would answer a residential browser, and some would answer an AI crawler that the site has allow-listed. The measurement shows what an unknown client sees, which is the situation most agents are in on first contact.
None of this changes the direction of the findings. A passing D2 is a sound sign that a sitemap exists, and it is a weak sign that the sitemap is useful.
A ten-minute monthly review
The fastest fix we saw in the data was a site that scanned as a fail in late August and serves a valid, dated sitemap now. The slowest was a file that has not been touched since 2018. The difference is whether anyone looks. A review that takes ten minutes:
- Run the scan and note D2, D1 and E5. Anything that changed since last month is the first thing to read.
- Fetch robots.txt and confirm every
Sitemap:URL returns 200 and parses. - Pull the newest
lastmodand compare it with your newest published page. They should be within a day or two of each other. - Request five URLs from the sitemap, chosen at random, and confirm 200.
- If you run a CDN or bot filter, repeat step 4 with a non-browser user agent and read the status codes.
Put the commands in a script and run it in CI after each deploy if you can. The guide to gating AI readiness in CI shows how to fail a build when a check regresses.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
Sitemap built at deploy, lastmod set to build time | Every URL looks changed daily; agents learn to ignore the field | Source dates from content records |
| Declared path has a typo | robots.txt promises a file that 404s | Fetch the declared URL after every robots.txt change |
Sitemap lists staging or http:// URLs | Agents follow links to the wrong host | Build URLs from one production base setting |
| CDN blocks unrecognised clients | Listed pages return 403 to the reader of the list | Allow the clients you want; test with curl |
| One huge file | Over 50,000 URLs or 50MB breaks the limit | Split into an index of child files |
| Never reviewed after launch | Dates freeze, pages disappear, the file stays | Add the three curl checks to a monthly review |
Your next action
Run npx aiscan-cli yoursite.com and read D2, D1 and E5 in the discoverability and content results. Then run the three curl commands above. If the newest lastmod is older than your newest post, fix the date source first, because that was the largest drop in our funnel. The full set of fixes is in the AIScan guides, and the llms.txt generator covers the sibling file agents read next.
Frequently asked questions
Does a passing D2 check mean my sitemap is fine?
No. D2 passes when /sitemap.xml or /sitemap_index.xml answers 200, and its evidence string records only the URL and the status. In our probe of 90 passing sites, 87 served valid sitemap XML, but only 53 (58.9%) also had a lastmod within 90 days and five sampled URLs that all resolved. Read D2 as proof a file exists, then check the file.
What does it mean if my sitemap returns 200 but D2 still fails or my tools say it is invalid?
The response is probably an HTML page, not XML. A catch-all route or soft 404 can answer 200 on any path, including /sitemap_index.xml. Fetch the URL with curl, confirm the body starts with an XML declaration and contains a urlset or sitemapindex element, and make the server return a real 404 for files that do not exist.
My sitemap is listed in robots.txt but agents cannot fetch it. What should I check?
Fetch the exact declared URL, character for character. In our sample, one site declared a path with a .xmls extension that returned 404, and the real file at the conventional path was gone. Declared URLs must be absolute, and they must be reachable by the clients you want to read them, so also check for a CDN or bot rule that returns 403 to non-browser user agents.
The sitemap lists pages that return 403 or 404. Is that a problem?
Yes. In our sample, 6 of 86 valid sitemaps contained sampled URLs that failed, and 3 had none of five resolve. A listed URL that refuses the reader of the list wastes a crawler's request and may lower its trust in the file. Generate URLs from the production hostname, and review any CDN rule that blocks crawlers from pages the sitemap names.
Do I need lastmod in my sitemap?
It is optional in the protocol, but it is the field that tells an agent what to re-fetch. 15 of 87 valid sitemaps in our sample had no lastmod at all. Google says it uses lastmod only when it is consistently and verifiably accurate, so source the date from the content record, not the build time, and omit it for any URL where you cannot give a true date.
Is a sitemap index fine, or does D2 need a single file?
A sitemap index is fine. Half of the valid files in our sample (44 of 87) were indexes. Google's limit is 50,000 URLs or 50MB uncompressed per file, so large sites need one. Make sure each child file parses, because in 3 of 51 indexes we found the first listed child did not.
My WordPress site has /wp-sitemap.xml but D2 fails. Why?
D2 requests /sitemap.xml and /sitemap_index.xml only. WordPress core serves /wp-sitemap.xml, so declare it with a Sitemap line in robots.txt or alias it to /sitemap.xml. A single SEO plugin that owns the sitemap, robots.txt and llms.txt, such as ThinkRank, avoids two tools writing conflicting files.
How often should I recheck my sitemap?
Monthly at minimum, and after every deploy that changes URL structure or hostname. A D2 verdict is a snapshot: 12 of the 60 sites we probed that had failed D2 now serve a valid sitemap. Run the AIScan scan, then the three curl checks in this post, and compare the newest lastmod against your newest published page.
Related guides
What a Failing AIScan Check Really Says: 2,961 Fail Lines Audited
A failing check on a scan report is supposed to be an instruction. The site owner reads the line, knows what the scanner asked for, knows what came back, and knows what to change. We audited whether…
Rate-limit AI crawlers without blocking them
Most advice on rate limiting AI crawlers stops at "add a limit". The part that costs you is where the limit applies and what it answers. A limit that covers /robots.txt and /llms.txt makes your own…
Which Agent-Readiness Checks Travel Together: 739 Sites, Three Bundles
Fix one agentreadiness check and a second one often moves with it. Fix another and nothing else changes. A scan report lists every check as its own line, so the reader cannot see which lines are…
Blocked Probes: 85 of 712 Sites Scored the Firewall, Not the Site
A failing score has two possible meanings. Either the site does not have the thing, or the scanner never got to look. A scan report prints both as the same red mark, and the owner reading it cannot…
