Every 404 error on your WordPress site is a missed opportunity — a visitor arrived, found nothing, and likely left. From an SEO perspective, a large number of 404 pages can waste your crawl budget and signal poor site health to search engines. Tracking these errors and redirecting the relevant ones to the correct pages recovers lost traffic, preserves link equity, and improves the user experience. WordPress fires the template_redirect action on every page load before the template is selected, which is the ideal hook to detect a 404 and log it or send a header redirect. For simple, static redirects you already know about, .htaccess Redirect directives are faster because they are handled by Apache before PHP even loads. For dynamic 404 logging where you want to analyse patterns over time, a custom database table or a transient-based counter works well. Pair this knowledge with SEO performance improvements and canonical URL tags for a comprehensive on-page optimisation strategy. The snippet below logs the last 100 unique 404 URLs to a transient so you can review them in the admin, with an optional redirect table you maintain in a filter. This avoids the overhead of a third-party plugin for sites that have only occasional 404 issues.
Problem: You want to automatically log 404 errors on your WordPress site and redirect specific broken URLs to the correct pages without installing a redirect plugin.
Solution: Add the following code to your functions.php file:
/**
* Log 404 URLs and optionally redirect known broken URLs.
*/
add_action( 'template_redirect', 'helloadmin_handle_404' );
function helloadmin_handle_404() {
if ( ! is_404() ) {
return;
}
$current_url = home_url( add_query_arg( null, null ) );
// 1. Log the 404 URL
$log = get_transient( 'helloadmin_404_log' ) ?: [];
if ( ! in_array( $current_url, $log, true ) ) {
$log[] = $current_url;
$log = array_slice( $log, -100 ); // keep last 100 unique URLs
set_transient( 'helloadmin_404_log', $log, WEEK_IN_SECONDS );
}
// 2. Redirect table (slug => destination)
$redirects = apply_filters( 'helloadmin_404_redirects', [
'/old-page-slug/' => '/new-page-slug/',
'/blog/removed-post/' => '/',
'/products/old-item/' => '/shop/',
] );
$request_path = wp_parse_url( $current_url, PHP_URL_PATH );
foreach ( $redirects as $old => $new ) {
if ( $request_path === $old ) {
wp_redirect( home_url( $new ), 301 );
exit;
}
}
}
/**
* Display logged 404 URLs in a simple admin notice (admin only).
*/
add_action( 'admin_notices', 'helloadmin_show_404_log' );
function helloadmin_show_404_log() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$log = get_transient( 'helloadmin_404_log' ) ?: [];
if ( empty( $log ) ) {
return;
}
echo '<div class="notice notice-warning"><p><strong>404 URLs logged (' . count( $log ) . '):</strong><br>'
. implode( '<br>', array_map( 'esc_html', $log ) )
. '</p></div>';
}
NOTE: The wp_redirect( ..., 301 ) call issues a permanent redirect that browsers and search engines cache aggressively. Only use 301 when you are certain the destination URL will never change. Use 302 for temporary redirects (maintenance periods, A/B tests). Also, avoid creating redirect chains — if URL A redirects to B and B redirects to C, consolidate to a single A → C redirect to preserve crawl efficiency.