How to force a 404 on WordPress

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:

Read More
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.

Related posts

Leave a Reply

7 comments

  1. 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.

  2. 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();
        }
    }
    
  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

  4. 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();
        }
    }
    
  5. 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.

  6. 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");
    
  7. 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; 
        }
    }