Index: src/wp-includes/class-wp.php
===================================================================
--- src/wp-includes/class-wp.php	(revision 53630)
+++ src/wp-includes/class-wp.php	(working copy)
@@ -407,7 +407,8 @@
 	 * If showing a feed, it will also send Last-Modified, ETag, and 304 status if needed.
 	 *
 	 * @since 2.0.0
-	 * @since 4.4.0 `X-Pingback` header is added conditionally after posts have been queried in handle_404().
+	 * @since 4.4.0 `X-Pingback` header is added conditionally for single posts that allow pings.
+	 * @since 6.1.0 Runs after posts have been queried.
 	 */
 	public function send_headers() {
 		$headers       = array();
@@ -503,6 +504,15 @@
 			}
 		}
 
+		if ( is_singular() ) {
+			$post = isset( $wp_query->post ) ? $wp_query->post : null;
+
+			// Only set X-Pingback for single posts that allow pings.
+			if ( $post && pings_open( $post ) ) {
+				$headers['X-Pingback'] = get_bloginfo( 'pingback_url', 'display' );
+			}
+		}
+
 		/**
 		 * Filters the HTTP headers before they're sent to the browser.
 		 *
@@ -700,14 +710,9 @@
 
 			if ( is_singular() ) {
 				$post = isset( $wp_query->post ) ? $wp_query->post : null;
+				$next = '<!--nextpage-->';
 
-				// Only set X-Pingback for single posts that allow pings.
-				if ( $post && pings_open( $post ) && ! headers_sent() ) {
-					header( 'X-Pingback: ' . get_bloginfo( 'pingback_url', 'display' ) );
-				}
-
 				// Check for paged content that exceeds the max number of pages.
-				$next = '<!--nextpage-->';
 				if ( $post && ! empty( $this->query_vars['page'] ) ) {
 					// Check if content is actually intended to be paged.
 					if ( false !== strpos( $post->post_content, $next ) ) {
@@ -769,8 +774,6 @@
 
 		$parsed = $this->parse_request( $query_args );
 
-		$this->send_headers();
-
 		if ( $parsed ) {
 			$this->query_posts();
 			$this->handle_404();
@@ -777,6 +780,8 @@
 			$this->register_globals();
 		}
 
+		$this->send_headers();
+
 		/**
 		 * Fires once the WordPress environment has been set up.
 		 *
