53

I need to force a 404 on some posts based on conditions. I managed to do it ( although I don't know if I did it the right way) and I'm a getting my 404.php template to load as expected.

My code:

function rr_404_my_event() {
  global $post;
  if ( is_singular( 'event' ) && !rr_event_should_be_available( $post->ID ) ) {
    include( get_query_template( '404' ) );
    exit; # so that the normal page isn't loaded after the 404 page
  }
}

add_action( 'template_redirect', 'rr_404_my_event', 1 );

Code 2 from this related question - same problem:

function rr_404_my_event() {
  global $post;
  if ( is_singular( 'event' ) && !rr_event_should_be_available( $post->ID ) ) {
    global $wp_query;
    $wp_query->set_404();
  }
}

add_action( 'wp', 'rr_404_my_event' );

My Issue:

Although it looks good, I get a status 200 OK if I check the network tab. Since it's a status 200, I am afraid that search engines might index those pages too.

Expected Behaviour:

I want a status 404 Not Found to be sent.

2

7 Answers 7

77
+150

You could try the Wordpress function status_header() to add the HTTP/1.1 404 Not Found header;

So your Code 2 example would be:

function rr_404_my_event() {
  global $post;
  if ( is_singular( 'event' ) && !rr_event_should_be_available( $post->ID ) ) {
    global $wp_query;
    $wp_query->set_404();
    status_header(404);
  }
}
add_action( 'wp', 'rr_404_my_event' );

This function is for example used in this part:

function handle_404() {
    ...cut...
    // Guess it's time to 404.
    $wp_query->set_404();
    status_header( 404 );
    nocache_headers();
    ...cut...
}

from the wp class in /wp-includes/class-wp.php.

So try using this modified Code 2 example in addition to your template_include code.

3
  • The Code 2 snippet you posted works perfectly. The set_header() was what was missing.
    – RRikesh
    Commented Mar 25, 2013 at 5:11
  • @birgire you refer to set_header() to add HTTP/1.1 404 Not Found but have used status_header() in your code? Commented Sep 8, 2014 at 13:08
  • @henrywright it looks like a typo there, I updated the answer, thanks ;-)
    – birgire
    Commented Sep 8, 2014 at 13:45
23

This code worked for me:

add_action( 'wp', 'force_404' );
function force_404() {
    global $wp_query; //$posts (if required)
    if(is_page()){ // your condition
        status_header( 404 );
        nocache_headers();
        include( get_query_template( '404' ) );
        die();
    }
}
2
  • Handy. I'm checking for custom query parameters so I'm not using the action, but it makes for a very useful method in my plugin class.
    – John Reid
    Commented Oct 17, 2014 at 8:26
  • 3
    Add the following to fix the page title: global $wp_query; $wp_query->is_404 = true; Commented Feb 24, 2016 at 22:18
4

The most robust way I've found of achieving this is to do it in the template_include filter, like so:

function wpse91900_force_404(string $template): string {
    if ($some_condition) {
        global $wp_query;

        $wp_query->set_404();
        status_header(404);
        nocache_headers();

        $template = get_404_template();
    }

    return $template;
}
add_filter("template_include", "wpse91900_force_404");
3

I wouldn't recommend forcing a 404.

If you're worried about search engines why not just do a "no-index,no-follow" meta on those pages and block it with robots.txt?

This may be a better way to block the content from being viewed

add_filter( 'template_include', 'nifty_block_content', 99 );

function nifty_block_content( $template ) {
  if ( is_singular( 'event' ) && !rr_event_should_be_available( $post->ID ) ) {
        $template = locate_template( array( 'nifty-block-content.php' ) );
     }
    return $template;
}

You could probably also use this method to load 404.php but I feel that using a page template might be a better option.

source

1
  • Thanks a lot for the link, I'll switch to using locate_template() instead. I think that robots.txt. isn't a guaranteed way to protect from indexation. Some search engines might still pick up the page. I do want the page to look like a normal 404 page. Also the posts are going to be dynamically added, editing the robots.txt file will add more trouble.
    – RRikesh
    Commented Mar 22, 2013 at 9:23
2

I wanted to share the way I used the marked solution

function fail_safe_for_authors() {
    if ((is_user_logged_in()) && (is_author()) && ($_COOKIE["user_role"] !== "administrator")) {
            global $wp_query;
            $wp_query->set_404();
            status_header(404);
        }
}
add_action("wp", "fail_safe_for_authors");

I did this to separate all user types from the administrator, in this project, Only the admin can see the author.php page.

I hope it could help somebody else.

1

My solution:

add_action( 'wp', 'my_404' );
function my_404() 
{
    if ( is_404() ) 
    {
        header("Status: 404 Not Found");
        $GLOBALS['wp_query']->set_404();
        status_header(404);
        nocache_headers();
        //var_dump(getallheaders()); var_dump(headers_list()); die();
    }
}
1
  • 2
    Redirecting on errors is terrible for your page ranking. Just show a template at the same location as the bad request. What will happen when you do that is you initially set a 404, and then the redirect alters it to a 301 or 302, which then redirects to a page that returns a 200. That then gets indexed by search engines as a valid page, which is explicitly what OP said he didn't want.
    – mopsyd
    Commented Mar 15, 2018 at 1:43
0

Status codes are sent in the headers of HTTP requests. Your current function is hooked into a hook that will be called too late.

You should try to hook your function rr_404_my_event() into action send_headers.

I'm not sure if at that point in time it's even possible to check the Post ID, but give this a go:

add_action( 'send_headers', 'rr_404_my_event' );
function rr_404_my_event() {
    global $post;
    if ( is_singular( 'event' ) && !rr_event_should_be_available( $post->ID ) ) {
        include( get_query_template( '404' ) );
        header('HTTP/1.0 404 Not Found');
        exit; 
    }
}
3
  • I corrected some syntax errors from your codes. I don't even get my 404 template to load with that.
    – RRikesh
    Commented Mar 22, 2013 at 10:48
  • Perhaps, in your 404.php you could load a different header.php, e.g. <?php get_header('404'); ?> to load header-404.php. In that header, you'd add header('HTTP/1.0 404 Not Found'); in the <head> section. Commented Mar 22, 2013 at 11:07
  • @MarcDingena I guess you have to put the header() line above the one, when you output the template contents, otherwise you will get the correct template page shown, but the real HTTP status code will still be 200.
    – David Wolf
    Commented Feb 26, 2022 at 20:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.