--- title: "Fix empty WordPress /page/ URLs: a plugin that returns 404 for pages that do not exist" description: Bots probe /page/50/ on every WordPress site. This plugin returns a proper 404 for page numbers beyond your actual archive, stopping crawl waste. source: https://steem.dev/blog/wordpress-pagination-404-plugin published: 2026-09-23 updated: 2026-09-23 site: Steem — AI WordPress plugin generator --- # Fix empty WordPress /page/ URLs: a plugin that returns 404 for pages that do not exist Every WordPress archive has pagination. /page/2/, /page/3/, and beyond exist the moment you activate a theme. The problem is that WordPress does not enforce a ceiling on these URLs. A site with 15 posts showing 5 per page has three real archive pages. /page/4/ through any arbitrarily large number also resolve, and depending on your theme they return either a near-empty loop template that looks like the front page or a blank archive shell. Neither sends a proper 404. Search crawlers probe these URLs routinely. This matters for two reasons. First, crawl budget: every request to /page/400/ is a request that did not reach a page with real content, and on smaller sites or shared hosting the cumulative cost adds up. Second, duplicate content: if /page/4/ renders the same template shell as /page/1/ with an empty loop, Google sees two near-identical documents. Google generally handles this without penalising the site, but it is unnecessary work that serves no purpose. For heavy bot traffic flooding hundreds of requests per minute, the block belongs in a CDN WAF rule or nginx config. A plugin still starts a PHP process for every request. But for day-to-day crawl hygiene, a plugin that sends the correct 404 header and loads your theme's 404 template is the right size of fix: no server access required, installs in thirty seconds, works on any host. ## What one build covers - Returns 404 with the correct HTTP status header and your theme's 404 template for any page number beyond the actual archive count - Works on all archive types: category, tag, author, date, and the front page loop - Optional noindex meta tag for paginated pages beyond page 1, for sites that want in-range paginated pages to exist but not be indexed - No configuration required for the core 404 behaviour: installs and works immediately ## What it does not cover - Query Loop block pagination, which uses query parameters like ?query-7-page=2 rather than /page/ URLs - Paginated single posts that use the nextpage tag inside post content - Server-level or CDN-level blocking: a full PHP process still starts before the 404 fires - Logging which out-of-range page numbers are being requested or by whom - Multisite installations: the plugin runs per-site with no cross-network view ## The prompt ```text Build a WordPress plugin called WP Pagination Guard. Plugin Name: WP Pagination Guard. Version: 1.0.0. Description: Return 404 for out-of-range paginated archive pages to prevent thin content and crawl waste. Single PHP file. At the top level (outside the class): add_action('plugins_loaded', function() { (new WP_Pagination_Guard())->init(); }); Class WP_Pagination_Guard: public init(): add_action('wp', [$this, 'guard_pagination']); add_action('wp_head', [$this, 'maybe_noindex'], 1); add_action('admin_menu', [$this, 'add_menu']); add_action('admin_init', [$this, 'register_settings']); public guard_pagination(): if (!is_paged()) return; $page = (int) get_query_var('paged'); if ($page < 2) return; global $wp_query; $max = (int) $wp_query->max_num_pages; if ($max < 1) $max = 1; if ($page > $max) { $wp_query->set_404(); status_header(404); nocache_headers(); include get_404_template(); exit; } public maybe_noindex(): $opts = get_option('wp_pagination_guard_options', []); if (!empty($opts['noindex_paged']) && is_paged()) { echo '' . "\n"; } public add_menu(): add_options_page('Pagination Guard', 'Pagination Guard', 'manage_options', 'wp-pagination-guard', [$this, 'render_page']); public register_settings(): register_setting('wp_pagination_guard', 'wp_pagination_guard_options', ['sanitize_callback' => [$this, 'sanitize_options']]); public sanitize_options($input): return ['noindex_paged' => !empty($input['noindex_paged']) ? '1' : '0']; public render_page(): $opts = wp_parse_args(get_option('wp_pagination_guard_options', []), ['noindex_paged' => '0']); Output a div.wrap with h1 'Pagination Guard'. Show a paragraph: 'Pages beyond your actual archive count already return 404 automatically. The setting below controls how in-range paginated pages appear to search engines.' Then a form with method post, settings_fields('wp_pagination_guard'), a table.form-table with one row: label 'Noindex paginated pages', containing a label with a checkbox input[name=wp_pagination_guard_options[noindex_paged] value=1, checked if $opts['noindex_paged'] is '1'] and description text 'Add a noindex meta tag to all paginated pages (page 2 and beyond). Use this when paginated archives should be accessible but should not appear in search results.' submit_button('Save Settings'). No database table, no JavaScript, no front-end output besides the optional meta tag. ``` ## Why WordPress does not return 404 for /page/999/ WordPress registers rewrite rules for /page/N/ without validating the page number first. When a request arrives, the main query runs, finds zero posts for that page number, and then hands off to template loading. What happens next depends on the theme. A well-written theme calls have_posts() and falls through to the 404 template when the loop is empty. A simpler theme renders the archive template regardless. In neither case does WordPress automatically send the correct HTTP 404 status code, which is what crawlers check to decide whether to keep the URL in their index. The front page is a particular problem. When the front page is set to show a blog loop, WordPress sometimes falls back to showing the first page of posts for any /page/N/ request rather than detecting that the requested page number exceeds the actual total. A crawler hitting /page/200/ may receive the same first ten posts as the homepage, with a 200 status code. That is duplicate content with the wrong signal. ## What the plugin does The plugin hooks into the wp action, which fires after the main query runs but before any template is loaded. At that point $wp_query->max_num_pages already holds the correct page count for the current archive. If the requested page number (from get_query_var('paged')) exceeds max_num_pages, the plugin calls $wp_query->set_404(), sends a 404 status header with nocache_headers(), and loads your theme's 404 template. From the crawler's perspective it is indistinguishable from a native WordPress 404: same status code, same response body, same cache headers. There is one optional setting beyond the core behaviour: a checkbox that adds a noindex meta tag to all paginated pages beyond page 1. This is for sites where paginated pages are valid and should be accessible to visitors, but you do not want them appearing in search results. The 404 guard for out-of-range pages applies regardless of whether the noindex option is on. ## The noindex option: when 404 is the wrong choice If your archive has 20 real pages of posts, returning 404 for pages 2 through 20 would be wrong. Those pages have content. The noindex option handles this case: paginated pages beyond page 1 are still served to visitors and crawlers can still follow links on them, but a meta robots tag tells search engines not to index the page itself. Internal link equity flows through; the paginated URLs just do not appear as separate search results. The noindex approach works best for archives where paginated pages contain posts that also appear on earlier pages or on category and tag archives. A site with 80 posts at 40 per page has only two real pages, and page 2 is a near-duplicate of the indexed content. Noindex on page 2 makes sense there. A site with 500 posts at 10 per page has unique content on page 12 that may not surface any other way. Noindex on page 12 loses indexed content for no benefit, and the 404 guard only fires for actual out-of-range requests. ## When a plugin is not enough A Cloudflare WAF rule matching the /page/* path pattern blocks requests before they reach your server. That is the right tool when your site is receiving hundreds or thousands of requests per minute to pagination URLs: no PHP execution, no database query, no WordPress bootstrap at all. The plugin described here still requires a full WordPress load before the 404 fires. Use the plugin when you want correct HTTP semantics without touching server configuration, when you want Search Console to stop reporting crawl errors on these URLs, or when your host does not give you access to nginx or CDN rules. Use the WAF or server rule when you are under active bot traffic and want to reduce server load. Both can run at the same time. ## Questions ### Does this work on category, tag, and custom post type archive pages? Yes. The plugin uses is_paged() and $wp_query->max_num_pages, which reflect the actual query running on any archive: category, tag, author, date, custom post type, or the front page loop. ### I use the Query Loop block, which paginates with ?query-7-page=2 instead of /page/2/. Does this plugin help? No. The plugin only handles rewrite-based pagination, which uses the paged query variable. Query Loop block pagination uses a separate variable and is not covered by this build. ### My theme has a custom 404 template with a search form and related posts. Will the plugin use it? Yes. The plugin calls get_404_template(), which returns the 404.php file from your active theme, falling back to the parent theme or the WordPress default. Your normal 404 page loads exactly as it would for any other missing URL. ### This runs inside PHP. Does it reduce server load from bot attacks? Not significantly. WordPress boots, loads active plugins, and runs the main query before this check fires. For high-volume bot traffic, a Cloudflare WAF rule or nginx directive is the right place to block. This plugin is for correct HTTP semantics and crawl hygiene, not for eliminating server load. ### Can I redirect out-of-range requests to the last real page instead of showing 404? Not in this build. To add it, replace the set_404 block with wp_redirect(get_pagenum_link($max), 301) followed by exit. That sends a permanent redirect to the last real page. A 404 is usually the cleaner signal for crawlers, but a redirect avoids showing an error page to the rare human visitor who has bookmarked a deep URL. --- Source: https://steem.dev/blog/wordpress-pagination-404-plugin Build this plugin: https://steem.dev/app