Make WordPress Core

Opened 7 weeks ago

Closed 5 weeks ago

#65936 closed defect (bug) (duplicate)

Core sitemaps rendered with HTTP 404 status since redirect_sitemapxml() deprecation

Reported by: abrahamfariaz Owned by:
Priority: normal Milestone:
Component: Sitemaps Version: 7.1
Severity: normal Keywords: has-patch
Cc: Focuses:

Description (last modified by ocean90)

On WordPress 7.1, /wp-sitemap.xml and all sub-sitemaps render VALID sitemap XML
but respond with HTTP status 404. Search engines discard sitemaps returning 404,
silently breaking indexing.

Steps to reproduce

  1. WordPress 7.1, default core sitemaps enabled (blog_public = 1), pretty permalinks.
  2. curl -sI ​https://example.com/wp-sitemap.xml
  3. Observe: "HTTP/1.1 404 Not Found" + Content-Type: application/xml, while the body is a valid <sitemapindex>. Same for ?sitemap=index (plain query var, no rewrite involved), and for all sub-sitemaps and the XSL stylesheet route.

Analysis

  • The main query for a sitemap request matches no posts, so WP::handle_404() sends status_header(404) immediately.
  • class-wp.php in 7.1 contains NO exemption for sitemap requests (grep 'sitemap' in wp-includes/class-wp.php: zero matches).
  • The historical escape hatch, WP_Sitemaps::redirect_sitemapxml() hooked on 'pre_handle_404', is deprecated since 6.7 (in favor of WP_Rewrite::rewrite_rules()) and nothing re-implements the 404 exemption.
  • WP_Sitemaps::render_sitemaps() runs later on template_redirect and outputs the XML + exit, but WP_Sitemaps_Renderer never calls status_header(), so the 404 already sent on the wire stands.

Expected

Sitemap routes should respond 200 (as before the deprecation), either via a
sitemap exemption in WP::handle_404() or an explicit status_header(200) in the
renderer before output.

Workaround we deployed

<?php
add_filter( 'pre_handle_404', function ( $preempt, $wp_query ) {
    if ( false !== $preempt ) return $preempt;
    if ( ! empty( $wp_query->query_vars['sitemap'] )
        || ! empty( $wp_query->query_vars['sitemap-subtype'] )
        || ! empty( $wp_query->query_vars['sitemap-stylesheet'] ) ) {
        status_header( 200 );
        return true;
    }
    return $preempt;
}, 10, 2 );

Environment: WordPress 7.1 · PHP 8.3 · nginx · standard %postname% permalinks ·
no SEO plugin (active plugins: one custom plugin + wp-mail-smtp) · Divi theme.

Change History (12)

#1 @ocean90
7 weeks ago

  • Description modified (diff)
  • Milestone Awaiting Review → 7.1.1

#2 @ocean90
7 weeks ago

#65941 was marked as a duplicate.

This ticket was mentioned in ​PR #13240 on ​WordPress/wordpress-develop by ​@harishtewari.


7 weeks ago
#3

  • Keywords has-patch added

https://core.trac.wordpress.org/ticket/65936

Trac ticket:

## Use of AI Tools

#4 @mrkenobi
7 weeks ago

Happened to me after update to 7.1. I rolled back to 7.0.4 and everything works normally. I did not know about this ticket, so I opened and described my case in detail here: ​https://wordpress.org/support/topic/wordpress-7-1-breaks-native-xml-sitemaps-on-nginx-reverse-proxy-apache/

The difference is that I use default wp installation, default theme, and no plugins.

Last edited 7 weeks ago by mrkenobi (previous) (diff)

#5 @l1onofjudah
6 weeks ago

The cause is not the redirect_sitemapxml() deprecation. That method is not hooked in 7.0.4 either — it exists only as a deprecated method and init() never registers it — and grep -rn "add_filter( *'pre_handle_404'" wp-includes/ returns nothing in both 7.0.4 and 7.1. Yet the same site returns 200 on 7.0.4 and 404
on 7.1, measured minutes apart across the update.

The actual trigger is in 7.1's WP_Query::parse_query(), which added || $this->is_sitemap to the negated condition that otherwise sets is_home = true (class-wp-query.php, line 1053). is_sitemap appears 0 times anywhere in 7.0.4.
Sitemap requests were previously protected from handle_404() only incidentally, by falling through to is_home — 7.1 removed that protection without adding a deliberate one, which is exactly the exemption this ticket is restoring.

That also explains the "no post" condition in the PR title: handle_404()'s earlier } elseif ( $wp_query->posts ) { branch is a second incidental rescue, so the failure only surfaces where the main query returns nothing. Measured on one site, changing only the post count: 0 published posts → 404, 1 published post → 200.

Practical consequence: the regression window looks like 7.1 only, not 6.7+, so 6.7.x–7.0.x should not need this backported. (I tested 7.0.4 and 7.1 directly; I have not tested 6.7–7.0.3.)

Nit on the PR (​https://github.com/WordPress/wordpress-develop/pull/13240): prevent_sitemap_404() is documented @since 6.8.0; given Version 7.1 / Milestone 7.1.1 that looks like it should be 7.1.1.

Minor scope note, in support of the patch rather than against it: I could not reproduce the 404 on the XSL stylesheet routes — /wp-sitemap-index.xsl and /wp-sitemap.xsl both returned 200 on 7.1 unpatched, while /wp-sitemap.xml and
/wp-sitemap-posts-page-1.xml returned 404. That is consistent with the mechanism, since stylesheet requests set sitemap-stylesheet rather than sitemap and so never set is_sitemap. So the PR checking only get_query_var( 'sitemap' ) appears sufficient, even though the description lists the stylesheet route as affected.

Environment: WP 7.1, PHP 8.2, Apache with no reverse proxy, no SEO plugin.

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Investigating the regression and drafting this comment. The version
comparison, code reading, and every measurement above were run against a live
WordPress 7.1 install rather than recalled by the model; I reviewed the analysis
and take responsibility for it.

#6 in reply to: ↑ description @luksusspokoju
6 weeks ago

Replying to abrahamfariaz:

On WordPress 7.1, /wp-sitemap.xml and all sub-sitemaps render VALID sitemap XML
but respond with HTTP status 404. Search engines discard sitemaps returning 404,
silently breaking indexing.

Steps to reproduce

  1. WordPress 7.1, default core sitemaps enabled (blog_public = 1), pretty permalinks.
  2. curl -sI ​https://example.com/wp-sitemap.xml
  3. Observe: "HTTP/1.1 404 Not Found" + Content-Type: application/xml, while the body is a valid <sitemapindex>. Same for ?sitemap=index (plain query var, no rewrite involved), and for all sub-sitemaps and the XSL stylesheet route.

Analysis

  • The main query for a sitemap request matches no posts, so WP::handle_404() sends status_header(404) immediately.
  • class-wp.php in 7.1 contains NO exemption for sitemap requests (grep 'sitemap' in wp-includes/class-wp.php: zero matches).
  • The historical escape hatch, WP_Sitemaps::redirect_sitemapxml() hooked on 'pre_handle_404', is deprecated since 6.7 (in favor of WP_Rewrite::rewrite_rules()) and nothing re-implements the 404 exemption.
  • WP_Sitemaps::render_sitemaps() runs later on template_redirect and outputs the XML + exit, but WP_Sitemaps_Renderer never calls status_header(), so the 404 already sent on the wire stands.

Expected

Sitemap routes should respond 200 (as before the deprecation), either via a
sitemap exemption in WP::handle_404() or an explicit status_header(200) in the
renderer before output.

Workaround we deployed

<?php
add_filter( 'pre_handle_404', function ( $preempt, $wp_query ) {
    if ( false !== $preempt ) return $preempt;
    if ( ! empty( $wp_query->query_vars['sitemap'] )
        || ! empty( $wp_query->query_vars['sitemap-subtype'] )
        || ! empty( $wp_query->query_vars['sitemap-stylesheet'] ) ) {
        status_header( 200 );
        return true;
    }
    return $preempt;
}, 10, 2 );

Environment: WordPress 7.1 · PHP 8.3 · nginx · standard %postname% permalinks ·
no SEO plugin (active plugins: one custom plugin + wp-mail-smtp) · Divi theme.

#7 @luksusspokoju
6 weeks ago

I can confirm the same issue on a production site running WordPress 7.1.

The hosting provider (LH.pl) investigated the issue server-side and confirmed that the request to /wp-sitemap.xml is correctly routed to WordPress index.php.

They also confirmed that:

Apache/mod_rewrite is not bypassing WordPress rewrite rules,
there is no server-side cache or proxy causing the 404,
.htaccess contains standard WordPress rewrite rules only,
the request reaches WordPress and is processed by the application,
the server log shows the request reaching index.php and WordPress returning the 404.

The sitemap XML itself is generated, but the HTTP response status is 404.

Environment:

WordPress 7.1
Apache hosting at LH.pl
pretty permalinks enabled
site: luksusspokoju.com

This provides another independent reproduction where the hosting provider has ruled out the web-server configuration as the cause.

This ticket was mentioned in ​Slack in #core by adrianduffell. ​View the logs.


5 weeks ago

​@adrianduffell commented on ​PR #13240:


5 weeks ago
#9

This was discussed in today’s bug scrub for 7.1.1. We recommend closing it in favor of ​https://github.com/WordPress/wordpress-develop/pull/13247/ as that one doesn't need a filter. Your efforts on the PR are much appreciated though, @tewariharish 🙏

This ticket was mentioned in ​Slack in #core by adamsilverstein. ​View the logs.


5 weeks ago

#12 @adamsilverstein
5 weeks ago

  • Milestone 7.1.1
  • Resolution → duplicate
  • Status new → closed

Duplicate of #65945.

Closing as a duplicate of https://core.trac.wordpress.org/ticket/65945 since the current pr is for that ticket.

Note: See TracTickets for help on using tickets.