#65945 closed defect (bug) (fixed)
Native sitemap is served with a 404 status on sites with no published posts
| Reported by: | fernandot | Owned by: | iamchitti |
|---|---|---|---|
| Priority: | normal | Milestone: | 7.1.1 |
| Component: | Sitemaps | Version: | 7.1 |
| Severity: | normal | Keywords: | has-test-info has-patch has-unit-tests fixed-major dev-reviewed |
| Cc: | Focuses: |
Description
On a site with no published posts, /wp-sitemap.xml returns the correct sitemap index XML
but with an HTTP 404 status. Search engines discard a sitemap that does not answer 200, so
the site effectively has no sitemap while looking perfectly fine in a browser.
This affects any site without a blog: shops, LMS, brochure sites, portfolios. It is not
caused by plugins or by the server. It reproduces on a clean install with no plugins.
Steps to reproduce
- Install WordPress and set any permalink structure other than plain.
- Delete the sample "Hello world!" post, so the site has zero published posts of type
post. Pages may still exist, it makes no difference. - Request
/wp-sitemap.xml.
Expected: HTTP 200 with the sitemap index.
Actual: HTTP 404 with the sitemap index. The body is correct and complete.
Publishing a single post of type post makes the same URL return 200 again. Deleting it
brings the 404 back.
Verified on a clean install with no plugins, with both show_on_front=posts and
show_on_front=page:
home posts pages /wp-sitemap.xml blog 1 1 200 blog 0 1 404 static page 1 1 200 static page 0 1 404
/?sitemap=index behaves the same way, so this is not about rewrite rules. On a healthy
site that URL 301s to the pretty permalink; on an affected site it renders the XML directly
under the 404.
/wp-sitemap-index.xsl is not affected and returns 200, because that branch of
WP_Sitemaps::render_sitemaps() exits before anything else.
Why it happens
WP::handle_404() runs during WP::main(), before template_redirect. For a sitemap
request the main query is the default query, and none of the conditionals are set. Dumped
from inside pre_handle_404 on a clean install:
no posts: is_404=false is_home=false is_singular=false is_archive=false is_feed=false is_paged=false posts=0 -> 404 one post: is_404=false is_home=false is_singular=false is_archive=false is_feed=false is_paged=false posts=1 -> 200
Every exception in handle_404() is false, including is_home(), so the only thing
standing between a sitemap request and a 404 is whether $wp_query->posts happens to be
non-empty. That is incidental: the posts in that query have nothing to do with the sitemap
being rendered.
WP_Sitemaps::render_sitemaps() then runs on template_redirect, renders the XML and
exits without ever calling status_header( 200 ) or clearing is_404, so the status set
earlier stands.
Precedent
#39157 reported exactly this for feeds ("Feed returns 404 when there are no posts") and was
fixed in 4.7.3. The same reasoning applies here: an empty result set is not a missing
resource, and a machine-readable endpoint that renders its content should not answer 404.
Possible fixes
Either of these works. A one-line drop-in confirmed on a clean install and on a live site:
<?php add_filter( 'pre_handle_404', function ( $preempt, $wp_query ) { if ( ! get_query_var( 'sitemap' ) && ! get_query_var( 'sitemap-stylesheet' ) ) { return $preempt; } $wp_query->is_404 = false; status_header( 200 ); return true; }, 10, 2 );
- Add sitemap requests to the exceptions in
WP::handle_404(), next tois_feed(). - Or have
WP_Sitemaps::render_sitemaps()set its own status, clearingis_404and sending a 200 before rendering, which keeps the fix inside the sitemaps component.
Note that the 404 render_sitemaps() forces when a provider returns no URLs must stay: with
the filter above, /wp-sitemap-posts-post-1.xml on a site with no posts still correctly
returns 404. That case is #61293 and is a separate discussion.
Related
Change History (23)
#2
in reply to: ↑ 1
@
8 weeks ago
Replying to audrasjb:
Removing version 7.1 as the issue wasn't introduced in 7.1, and moving to milestone 7.2 as it sounds like something we can fix in short term.
I think this issue was introduced in 7.1? There are a bunch of people reporting this (or something very similar to this) in just the last few days since 7.1 was released.
See:
#3
@
8 weeks ago
This is reproducible on a clean install. The cause looks to be in WP::handle_404() rather than in the sitemaps component: sitemap requests aren't in its list of exceptions, so an empty main query is enough to 404 them.
One note on the version question: there appear to be two distinct cases here, one that changed in 7.1 and one that predates it, so the exception may need to go somewhere other than next to is_feed() as suggested above. Will raise a fix PR after confirming a few details in codebase.
This ticket was mentioned in PR #13247 on WordPress/wordpress-develop by @iamchitti.
8 weeks ago
#4
- Keywords has-patch has-unit-tests added; needs-patch removed
## Ticket
Trac ticket: https://core.trac.wordpress.org/ticket/65945
## Summary
- On a site with zero published posts,
/wp-sitemap.xmlrenders correct XML but responds with HTTP 404, so search engines discard it. - `WP::handle_404()` 404s the main query when it has no posts and no exception applies. Sitemap requests aren't in that list — they were only shielded by falling through to `is_home = true`, which changeset 62664 (#51542, #51543) correctly removed.
- Same class of issue as #39157, fixed for feeds in 4.7.3.
## Changes
WP::handle_404()— exempt sitemaps alongside the other machine-readable endpoints. Placed in the first branch rather than next tois_feed(), since provider routes mappagedfrom the URL and so never reach theelseif ( ! is_paged() )branch.WP_Sitemaps::render_sitemaps()— 404 explicitly for an unregistered provider, which previously relied onhandle_404()doing it.
Intentional 404s are unaffected — sitemaps disabled and empty URL lists (#61293) call set_404() directly, later in the request.
## Test
New tests/phpunit/tests/wp/handle404.php (12 tests): index, provider, paged, taxonomy, user and stylesheet routes with no posts, plus guards that unknown URLs and unregistered providers still 404.
--group sitemaps--group wp --group query --group feed --group canonical --group rewrite
## Screenshots
NA
## Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Root-cause investigation, tracing history, and drafting the fix and tests.
#5
@
8 weeks ago
- Milestone 7.2 → 7.1.1
I think this issue was introduced in 7.1? There are a bunch of people reporting this (or something very similar to this) in just the last few days since 7.1 was released.
See:
If this is the case, it should be fixed in 7.1.1. First, let's clarify which version this issue occurred in and why.
#6
follow-up:
↓ 8
@
8 weeks ago
Based on my findings, this issue occurred in r62664, so I believe it should be fixed in a minor release.
cc @masteradhoc @westonruter
This ticket was mentioned in Slack in #core by adrianduffell. View the logs.
6 weeks ago
@adamsilverstein commented on PR #13247:
6 weeks ago
#10
Please note the RC for 7.1.1 is September 10th if we want it included in that version. Otherwise it is fine to punt to 7.1.2.
This ticket was mentioned in Slack in #core by adamsilverstein. View the logs.
6 weeks ago
This ticket was mentioned in Slack in #core by adrianduffell. View the logs.
6 weeks ago
@westonruter commented on PR #13247:
5 weeks ago
#15
Sorry for the back-and-forth on this. I think for 7.1.1 we'll go back to just the theme's 404 template as it is a smaller change with less risk, and then in 7.2 we can improve the error handling with wider testing. I'm going to have Claude share some review feedback that led me to this.
#17
@
5 weeks ago
- Keywords dev-feedback fixed-major added
- Resolution fixed
- Status closed → reopened
Re-opening for 7.1.1 consideration.
#18
@
5 weeks ago
- Keywords dev-reviewed added; dev-feedback removed
+1 for landing in 7.1 @westonruter - looks good, thanks!
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Removing version 7.1 as the issue wasn't introduced in 7.1, and moving to milestone 7.2 as it sounds like something we can fix in short term.