XML Sitemap Crawl Optimization from Logs
An XML sitemap is a recommendation, not a command — search engines decide what to crawl, and the only place you can see what they actually did is your server logs. This guide closes that loop: it matches your sitemap's URLs against real crawler hits so you can prove which entries drive crawling, find the ones search engines ignore, and strip out the redirects and 404s that teach crawlers to distrust the file. It is the sitemap-focused companion to the broader work in the Crawl Budget Optimization & Bot Management guide.
The premise is simple and often ignored: a sitemap full of non-canonical, redirecting, or dead URLs wastes crawl budget and erodes the trust signal the file is supposed to send. Logs are the audit that keeps it honest.
What you will accomplish:
- Extract the crawlable URL set from your sitemap (including sitemap-index children).
- Match sitemap URLs against verified crawler hits to see what is actually fetched.
- Find uncrawled sitemap URLs and non-200 entries that should never have been listed.
- Measure whether
lastmodchanges actually trigger recrawl in the logs.
The diagram compares the two URL sets — what the sitemap lists versus what crawlers fetched — and names the four segments that fall out of the overlap.
Prerequisites
You need read access to your web server's access logs with the request path and user-agent (the combined format or better), the public URL of your sitemap or sitemap index, and standard CLI tools plus curl and xmllint (from libxml2). A verified-crawler workflow helps: the matches below are only meaningful against real Googlebot hits, so lean on identifying search engine bots to filter spoofed traffic first. Confirm xmllint is present:
xmllint --version 2>&1 | head -1
Expected Output: xmllint: using libxml version 2xxxx. If missing, install libxml2-utils (Debian/Ubuntu) before continuing.
Environment Setup: Extract the Sitemap URL Set
A sitemap index points to child sitemaps, so first flatten the whole tree into a plain list of <loc> URLs. xmllint with an XPath query pulls them cleanly.
# Fetch a sitemap index and expand it into one URL list
curl -s https://example.com/sitemap_index.xml \
| xmllint --xpath '//*[local-name()="loc"]/text()' - 2>/dev/null \
| tr ' ' '\n' > child_sitemaps.txt
# Pull the page URLs from each child sitemap
: > sitemap_urls.txt
while read sm; do
curl -s "$sm" | xmllint --xpath '//*[local-name()="loc"]/text()' - 2>/dev/null \
| tr ' ' '\n' >> sitemap_urls.txt
done < child_sitemaps.txt
sort -u -o sitemap_urls.txt sitemap_urls.txt
wc -l sitemap_urls.txt
Expected Output: the count of unique canonical URLs your sitemaps advertise, e.g. 18423 sitemap_urls.txt. The local-name() XPath sidesteps the sitemap XML namespace that otherwise makes a naive //loc query return nothing.
Verification command. Spot-check that the URLs are absolute and canonical: head sitemap_urls.txt. Relative paths or http:// entries on an https:// site are a sitemap-generation bug to fix before the file misleads crawlers.
Pipeline / Agent Configuration: Match Sitemap URLs to Crawl Hits
Reduce both sides to comparable path lists, then use comm to compute the three segments. Extract the crawled path set from verified Googlebot hits first.
# Crawled paths (verified Googlebot), normalized to path-only
awk '/Googlebot/ {print $7}' /var/log/nginx/access.log \
| sed 's/?.*//' | sort -u > crawled_paths.txt
# Sitemap paths, stripped to path-only to match
sed -E 's#https?://[^/]+##; s/\?.*//' sitemap_urls.txt | sort -u > sitemap_paths.txt
Expected Output: two path lists on the same footing. Stripping the query string and host is what makes the comparison meaningful; leaving them in produces false mismatches.
Step 1: Uncrawled sitemap URLs. In the sitemap, absent from the crawl log — the entries search engines are ignoring.
comm -23 sitemap_paths.txt crawled_paths.txt | head
Expected Output: paths listed but never fetched in the log window, e.g. /blog/2019/old-post. A large set here means the sitemap is padded with low-value URLs that dilute the signal.
Production Warning: A short log window naturally shows many "uncrawled" URLs simply because crawlers had not reached them yet. Judge this against a window of at least two to four weeks, and treat persistent absence — not a single day's — as the signal.
Step 2: Crawled-but-unlisted URLs. Fetched by crawlers, missing from the sitemap — orphan and parameter candidates.
comm -13 sitemap_paths.txt crawled_paths.txt | head
Expected Output: paths crawlers reach that your sitemap omits — cross-reference these with identifying orphan pages from log analysis to decide whether to link, redirect, or block them.
Parsing Logic & Field Mapping: Validate Sitemap Response Codes
Every URL in a sitemap should return 200. Redirects and 404s in the file waste the crawl budget the sitemap consumes and, at scale, degrade how much a search engine trusts it. Probe each entry.
while read url; do
code=$(curl -s -o /dev/null -w '%{http_code}' -A 'Mozilla/5.0 (compatible; audit)' "$url")
[ "$code" != "200" ] && printf '%s\t%s\n' "$code" "$url"
done < sitemap_urls.txt | tee sitemap_bad.tsv | head
Expected Output: ideally empty. Any line such as 301 https://example.com/old-product is a URL to replace with its canonical target or remove — a redirect in a sitemap points crawlers at a hop instead of the destination, the waste pattern detailed in finding redirect chains in logs with awk.
SEO-relevant field callout. Google treats <lastmod> as a genuine signal only when it is accurate and trustworthy; it largely ignores <priority> and <changefreq>. So the field worth auditing against logs is lastmod: does a change to it actually precede a recrawl? The validation section measures exactly that.
Validation & Troubleshooting
Health check: did lastmod trigger recrawl? After updating a page and its lastmod, watch the log for the next Googlebot fetch of that path.
grep 'Googlebot' /var/log/nginx/access.log | grep '/products/widget ' | tail -3
Expected Output: a fetch timestamp after your lastmod update. If weeks pass with no recrawl despite an honest lastmod, the signal is not being trusted — usually because the site has cried wolf with sitemap-wide lastmod bumps on unchanged pages.
Recovery recipes for named failure modes:
- Nothing crawled from the sitemap. The sitemap may be unreachable or malformed. Confirm it returns
200and validates:curl -sI .../sitemap.xmlandxmllint --noout sitemap.xml. - Namespace makes XPath return nothing. Use the
local-name()form shown above rather than a bare element name. lastmodignored entirely. If every URL shares one recentlastmod, crawlers learn to discount it. Setlastmodonly to a page's real last-content-change date.- Huge uncrawled set that never shrinks. The sitemap lists thin, duplicate, or parameter URLs. Prune it to canonical, indexable pages and re-measure.
Sitemap Index Structure and Crawl Priority
How you split URLs across sitemap files is itself a crawl-budget lever, not just an organizational choice. A sitemap index can reference up to 50,000 child sitemaps, each holding up to 50,000 URLs, and grouping URLs by section or freshness lets you see — in the logs — which groups actually get crawled. Splitting your most important, most-frequently-updated URLs into their own child sitemap means you can measure whether search engines are picking them up promptly, separately from the bulk of stable pages.
# Per-child-sitemap crawl coverage: which sitemap files drive real crawling?
for sm in products.xml blog.xml archive.xml; do
total=$(curl -s "https://example.com/$sm" | grep -c '<loc>')
crawled=$(comm -12 <(curl -s "https://example.com/$sm" | grep -oP '(?<=<loc>)[^<]+' \
| sed -E 's#https?://[^/]+##' | sort -u) <(sort -u crawled_paths.txt) | wc -l)
printf "%-14s %d/%d crawled\n" "$sm" "$crawled" "$total"
done
Expected Output:
products.xml 4820/5000 crawled
blog.xml 1900/2000 crawled
archive.xml 2100/9000 crawled
Here products.xml is well-covered while archive.xml is largely ignored — a signal that the archive is low-priority to search engines and probably does not belong consuming sitemap real estate. Structuring sitemaps so each child maps to a meaningful group turns the coverage diff into a per-section diagnosis, and it lets you keep the high-value groups tight and crawlable while the stale older pages do not dilute them.
Dynamic Sitemaps and the Freshness Signal
For large, frequently-changing sites, a static sitemap file goes stale between regenerations, so the lastmod values drift out of sync with reality — and an inaccurate lastmod is worse than none, because it teaches search engines to distrust the signal. A dynamically-generated sitemap that reads real last-modified timestamps from your content store keeps lastmod honest, and the logs are how you confirm the freshness signal is actually being acted on.
# After a content update, confirm the sitemap's lastmod moved AND a recrawl followed
curl -s https://example.com/sitemap.xml \
| grep -A1 '/products/widget<' | grep lastmod
grep 'Googlebot' access.log | grep '/products/widget ' | tail -1
Expected Output: a lastmod matching your update time, followed by a Googlebot fetch timestamp shortly after — proof the freshness signal propagated and was honored. If the lastmod updated but no recrawl followed within days, the signal is being discounted, usually because earlier sitewide lastmod bumps cried wolf. Generating the sitemap dynamically from real modification times, rather than stamping every URL with the current date on each build, is what keeps lastmod a trusted signal that measurably drives recrawl of exactly the pages that changed.
Handling Very Large Sitemaps at Scale
Beyond a few hundred thousand URLs, sitemap management becomes an engineering problem in its own right: the 50,000-URL-per-file and 50 MB (uncompressed) limits force sharding, and generating the full set on every deploy becomes expensive. The log-driven discipline scales with a shift in emphasis — rather than auditing every URL, sample and monitor coverage by segment, and let the crawl logs tell you which segments need attention.
# At scale, monitor coverage RATE per segment rather than per URL
awk '/Googlebot/ {print $7}' access.log | sed -E 's#(/[^/]+)/.*#\1#' \
| sort | uniq -c | sort -rn | head
Expected Output: crawl volume per top-level segment, so you can see at a glance which large sections are under-crawled relative to their size and sitemap presence. At this scale the goal is not per-URL perfection but keeping each segment's coverage rate healthy and catching a segment that falls off — the same coverage-versus-hits distinction the Crawl Budget Optimization & Bot Management guide applies throughout, now measured per sitemap shard rather than per page. A warehouse like BigQuery SEO log analysis is often the right home for this segment-level coverage reporting once the URL set outgrows command-line tools.
What a Sitemap Can and Cannot Do
Much sitemap confusion comes from expecting the file to do things it cannot, so it is worth being precise about its actual role. A sitemap is a discovery aid and a set of hints — it tells search engines which URLs exist and, through lastmod, roughly when they changed. That is genuinely useful for large sites, deep pages weakly linked internally, and freshly published content that has not yet been discovered through links. For those cases, a clean sitemap measurably speeds discovery, which the logs confirm as crawler hits arriving on newly-listed URLs.
What a sitemap cannot do is force crawling, guarantee indexing, or improve rankings. Listing a URL does not make search engines crawl it — they still apply their own demand and quality judgments, which is exactly why the log-based coverage audit in this guide matters: it shows the gap between what you listed and what was actually fetched. A URL crawled from the sitemap but judged low-quality will not be indexed, and no amount of sitemap prominence changes that. This is the crucial reframing: the sitemap is a recommendation you make to search engines, and the logs are how you measure which recommendations they accepted. Treating the sitemap as a request rather than a command, and using the logs to see the response, is what turns sitemap management from wishful listing into a measurable feedback loop. A site that understands this keeps its sitemap tight and honest and reads the logs for the answer, rather than padding the sitemap in the false hope that more listings mean more crawling.
Sitemap Errors That Erode Trust
Search engines build a trust model of your sitemap over time, and certain recurring errors degrade that trust in ways that reduce how much they rely on the file — a slow, invisible penalty that a one-time validation misses. The most corrosive is inaccurate lastmod: stamping every URL with the current date on each rebuild, or bumping lastmod sitewide when nothing changed, teaches search engines that your freshness signal is noise, and they progressively discount it until a genuine update goes unnoticed. The logs reveal this as the fading correlation between lastmod changes and recrawls that this guide's validation step measures.
Other trust-eroding errors compound the problem. Listing non-canonical URLs, redirecting URLs, or 404s in the sitemap sends crawlers to dead ends and non-preferred versions, wasting the budget the sitemap consumes and signaling that the file is unmaintained. Including URLs blocked by robots.txt sends contradictory instructions — "crawl this" and "do not crawl this" at once — which is a clear sign of an unmanaged sitemap. Mixing indexable and non-indexable URLs dilutes the clear signal a sitemap should send about your preferred, canonical, indexable pages. Each of these individually is minor; together, and repeated over time, they lower the weight search engines give your sitemap, so even the accurate entries get less benefit. Keeping the sitemap to canonical, indexable, 200-returning URLs with honest lastmod values is not pedantry — it is maintaining the trust that makes the file worth having, and the logs are how you confirm the trust is intact by watching listed URLs actually get crawled.
Automating Sitemap Health Checks
For any site large or active enough to matter, sitemap quality drifts between manual audits — a deploy adds redirecting URLs, a bug stamps every entry with today's date, a removed section leaves 404s behind. The durable fix is to automate the health check so problems surface immediately rather than at the next quarterly review. A scheduled job that fetches the sitemap, validates every URL's response code, checks for robots.txt conflicts, and diffs the URL set against crawl logs turns sitemap hygiene from a periodic chore into continuous monitoring.
The check has three parts worth automating together. First, response-code validation: probe a sample (or all) of the listed URLs and alert on any that return a redirect or error, since those should never be in the file. Second, coverage monitoring: run the log-based diff on a schedule and track the uncrawled-percentage trend, alerting when it rises, since a growing uncrawled set signals either sitemap bloat or a crawling problem. Third, freshness verification: after a known content change, confirm both that lastmod updated and that a recrawl followed, so a broken freshness signal is caught early. Wiring these into your CI or a cron job, with alerts to the team, means the sitemap stays healthy as the site changes rather than slowly rotting until someone notices a coverage problem. Automation is what keeps the log-driven discipline of this guide sustainable at scale — the audit you run once teaches you what to look for, and the automation you build makes sure you never stop looking.
Sitemaps for News, Images, and Video
Beyond the standard sitemap, specialized sitemap types carry extra signals for particular content, and the logs confirm whether the specialized crawlers act on them. A news sitemap flags time-sensitive articles for faster discovery by news crawlers; an image sitemap surfaces images that lazy-loading or JavaScript might otherwise hide from crawlers; a video sitemap describes video content that is hard to discover from markup alone. For sites where this content matters, the specialized sitemap is the difference between the content being found promptly and being missed, and the logs — showing hits from the relevant crawler on the listed resources — are how you confirm the specialized signal is being honored.
The same log-driven discipline applies as to the standard sitemap, with the specialized crawler as the subject. After listing images in an image sitemap, watch for image-crawler fetches on those URLs; after adding a news sitemap, confirm the news crawler picks up the flagged articles quickly. If the specialized crawler does not appear in the logs fetching the listed resources, the specialized sitemap is not doing its job — perhaps it is malformed, unreferenced, or the content does not qualify. The broader point is that sitemaps are not one monolithic file but a family of discovery aids, each aimed at a particular crawler and content type, and each measurable in the logs the same way: does the crawler you are signaling actually fetch what you listed. For most sites the standard sitemap is the priority, but where images, video, or news are core to the business, the specialized sitemaps and their log verification are part of a complete crawl-coverage practice.
Common Mistakes
- Listing non-200 URLs. Redirects and 404s in a sitemap waste budget and erode trust. Keep it to canonical
200pages only. - Fake or blanket
lastmod. Bumpinglastmodsite-wide on unchanged pages destroys the one sitemap signal Google actually uses. Set it truthfully. - Judging coverage on a short window. A day or two of logs makes healthy pages look uncrawled. Use weeks of data before concluding a URL is ignored.
- Ignoring the crawled-but-unlisted set. Those are the orphans and parameter URLs quietly eating budget; the sitemap audit is how you surface them.
Frequently Asked Questions
Does a bigger sitemap get more of my pages crawled?
No. Crawl budget is governed by demand and site health, not sitemap size. Padding a sitemap with low-value URLs dilutes the trust signal and wastes budget on pages that will not rank. A tight sitemap of canonical, indexable URLs is more effective than a large, noisy one.
How do I know if search engines trust my lastmod?
Change a page's real content, update only that page's lastmod, and watch the log for the next Googlebot fetch of that URL. A recrawl shortly after is trust; prolonged silence, especially after past sitemap-wide lastmod bumps, means the signal is being discounted.
Should URLs blocked by robots.txt appear in the sitemap?
No. A URL that is disallowed from crawling but listed in the sitemap sends contradictory signals. Keep the sitemap to crawlable, indexable canonical URLs, and manage crawl steering separately as covered in robots.txt and crawl-rate control.
What log window should I use to judge sitemap coverage?
At least two to four weeks for a typical site, longer for large or slowly-crawled ones. Crawlers work through URLs over time, so a short window undercounts crawling and overstates the "uncrawled" set.
Should I split one sitemap into several by section?
Yes, for any non-trivial site. Grouping URLs into per-section child sitemaps under an index lets you measure coverage per group in the logs, so you can see that (for example) your product sitemap is well-crawled while an archive sitemap is largely ignored. That per-group visibility is impossible with one monolithic file, and it lets you keep the high-value groups tight while the stale older pages do not dilute them.
How many URLs can a single sitemap file hold?
A single sitemap file holds up to 50,000 URLs and 50 MB uncompressed; a sitemap index can reference up to 50,000 child sitemaps. Beyond one file's limit you must shard, which is a good thing — sharding by section or freshness is exactly what makes per-group crawl coverage measurable in the logs, so treat the limit as a prompt to structure sitemaps meaningfully rather than an obstacle.
Does gzipping my sitemap affect crawling?
No — search engines accept gzipped sitemaps, and the 50 MB limit applies to the uncompressed size, so compression only saves transfer bandwidth. It has no effect on how the URLs inside are crawled, so gzip large sitemaps freely; the crawl behavior is governed by the URLs and their lastmod signals, not the file's on-the-wire encoding.
Related Guides
- Finding uncrawled sitemap URLs from logs — the comm-based diff, step by step.
- Detecting noindex pages still crawled — spot budget spent on pages you told search engines to drop.
- Identifying orphan pages from log analysis — investigate the crawled-but-unlisted set.
- Robots.txt & crawl-rate control — steer crawling once the sitemap is clean.
Part of the Crawl Budget Optimization & Bot Management series.