Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Set cache headers on WordPress 404 pages.
<?php
/**
* 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' );
$wp_query->set_404();
status_header( 404 );
// prevents the default 404 from firing.
return true;
}
return $preempt;
}
add_filter( 'pre_handle_404', 'change_404_headers', 2, 99 );
@MikeNGarrett

This comment has been minimized.

Copy link
Owner Author

@MikeNGarrett MikeNGarrett commented May 8, 2018

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.

@kshaner

This comment has been minimized.

Copy link

@kshaner 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.