Skip to content

Instantly share code, notes, and snippets.

Created October 13, 2017 20:11
What would you like to do?
Set cache headers on WordPress 404 pages.
* Force cache headers on 404 pages and prevent WordPress from handling 404s.
* @param bool $preempt determines who handles 404s.
* @param obj $wp_query global query object.
function change_404_headers( $preempt, $wp_query ) {
if ( ! is_admin() && ! is_robots() && count( $wp_query->posts ) < 1 ) {
header( 'Cache-Control: max-age=30000, must-revalidate' );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', strtotime( '+5000 minutes' ) ) . ' GMT' );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', strtotime( '-5000 minutes' ) ) . ' GMT' );
status_header( 404 );
// prevents the default 404 from firing.
return true;
return $preempt;
add_filter( 'pre_handle_404', 'change_404_headers', 2, 99 );
Copy link

Please note, this has uncovered some unexpected behavior in 4.9 and should not be used. A bug has been filed to get to the bottom of things. I'll update this gist as the ticket progresses.

Copy link

kshaner commented May 9, 2018

The issue discovered is when the site is using a permalink structure where the first variable is one of: postname, category, tag, or author. This throws WP_Rewrite into 'use_verbose_page_rules' mode. This means that WP_Query is always in a state of error because no rewrite rule matches but the query still runs and performs a standard blog query where the most recent posts are returned but the query still calls $wp_query->set_404. This causes the count( $wp_query->posts ) < 1 check to fail.

Also, the action and argument count arguments are inverted.

Copy link

Is there any update on the issue? @MikeNGarrett @kshaner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment