How-To Guide

How to do a 301 redirect:
Apache, Nginx, PHP,
and WordPress.

By Farman Rind | | 9 min read

A 301 redirect is a server-level instruction that permanently forwards one URL to another. The 301 status code tells browsers and search engines that the page has moved permanently – not temporarily – which means search engines transfer the original page's link equity to the destination URL and update their index to reflect the new address. It is the correct method for URL changes during site migrations, slug corrections, and domain consolidations where you want SEO value preserved.

Apache .htaccess redirects

For shared hosting, cPanel, and most traditional web servers

Apache is the most common web server for shared hosting. If you are on cPanel, Plesk, or similar, your redirects go in a file called .htaccess in your site's document root. Make sure the mod_rewrite module is enabled – it is on virtually all shared hosts by default.

Redirect a single page:

Redirect 301 /old-page/ https://yoursite.com/new-page/

Redirect an entire directory:

RedirectMatch 301 ^/old-section/(.*)$ https://yoursite.com/new-section/$1

Redirect a full domain (www to non-www):

RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.yoursite\.com [NC]
RewriteRule ^(.*)$ https://yoursite.com/$1 [R=301,L]

The [R=301,L] flags mean: send a 301 response (R=301) and stop processing further rewrite rules (L). Always include L to prevent the request from matching additional rules below it.

Nginx server block redirects

For VPS, cloud servers, and dedicated servers running Nginx

Nginx does not use .htaccess. Redirects go in the server block configuration, typically in /etc/nginx/sites-available/yoursite.conf or the main nginx.conf. After editing, run nginx -t to test syntax before reloading.

Redirect a single page:

server {
    listen 80;
    server_name yoursite.com;

    location = /old-page/ {
        return 301 https://yoursite.com/new-page/;
    }
}

Redirect www to non-www:

server {
    listen 80;
    server_name www.yoursite.com;
    return 301 https://yoursite.com$request_uri;
}

server {
    listen 80;
    server_name yoursite.com;
    # ... rest of config
}

The return 301 directive is cleaner and faster than using rewrite for simple redirects. Use $request_uri to preserve the path and query string in the destination URL. After editing your config, run sudo systemctl reload nginx for the changes to take effect.

PHP header() redirect

For application-level redirects within PHP scripts

PHP redirects are handled at the application layer using header(). This is the right approach when the redirect decision depends on application logic – for example, redirecting based on a URL slug pattern, a database lookup, or request parameters. The critical rule: header() must be called before any output is sent to the browser. Any echo, print, whitespace, or byte-order mark before header() will silently break the redirect on most PHP configurations.

Basic 301 redirect in PHP:

<?php
header('Location: https://yoursite.com/new-page/', true, 301);
exit;

Conditional redirect based on URL:

<?php
$redirects = [
    '/old-page/'    => '/new-page/',
    '/old-service/' => '/services/new-service/',
];

$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

if (isset($redirects[$request_uri])) {
    header('Location: https://yoursite.com' . $redirects[$request_uri], true, 301);
    exit;
}

The third parameter to header() is the HTTP status code. Without it, PHP sends a 302 by default. Always specify 301 explicitly for permanent redirects and always call exit; immediately after to stop script execution.

WordPress 301 redirects without a plugin

Clean method using functions.php or custom code

WordPress handles many redirects automatically (old slugs, category pages, attachment URLs), but for specific custom redirects you have two code-based options that do not require a plugin: WordPress's built-in wp_redirect() function inside a hook, or editing the site's .htaccess file directly (since WordPress sites run on Apache in most shared hosting environments).

Using wp_redirect() in functions.php (child theme only):

add_action('template_redirect', function () {
    $redirects = [
        '/old-page/'    => '/new-page/',
        '/old-post/'    => '/category/new-post/',
    ];

    $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

    if (isset($redirects[$path])) {
        wp_redirect(home_url($redirects[$path]), 301);
        exit;
    }
});

Or add directly to .htaccess above the WordPress block:

# Custom 301 redirects
Redirect 301 /old-page/ https://yoursite.com/new-page/

# BEGIN WordPress
# ... WordPress .htaccess block below this line

Add custom redirects above the # BEGIN WordPress line in .htaccess. WordPress regenerates its section of .htaccess occasionally (on permalink setting changes), so anything inside the WordPress block will be overwritten. Custom rules above it are safe from that.

How to test a 301 redirect

Never trust your browser to test a redirect correctly. Browsers cache redirects aggressively, so a cached 301 will look correct even if the server configuration is broken. Use one of these methods instead.

curl from a terminal (most reliable):

curl -I https://yoursite.com/old-page/

The -I flag fetches headers only without following redirects. A working 301 returns HTTP/1.1 301 Moved Permanently and a location: header with the destination URL.

curl following the full redirect chain:

curl -IL https://yoursite.com/old-page/

Adding -L makes curl follow redirects and print headers for each hop. This is useful for checking redirect chains: you should see the 301 headers and then a final 200 at the destination. If you see multiple 301s, you have a chain to fix.

After deploying a redirect, also check it in Google Search Console under Coverage to confirm Google has picked up the canonical URL change. This can take several days to weeks depending on crawl frequency for your site.

Common 301 redirect mistakes

Using 302 instead of 301

A 302 tells search engines the move is temporary. They keep the old URL indexed and do not transfer link equity. If the move is permanent, always use 301.

Redirect chains longer than two hops

Each additional redirect hop adds latency and risks crawlers stopping short. Always redirect directly from the origin to the final destination. If you have A → B → C, update A to go directly to C.

Redirect loops

A redirect from A to B where B also redirects to A creates an infinite loop. Browsers and crawlers will error out. Always test both the old and new URLs after setting up a redirect to confirm neither loops back.

Not preserving trailing slashes

If your site is consistent about trailing slashes (e.g., /page/ not /page), make sure your redirect destination matches that convention. Inconsistency creates duplicate URL problems.

Output before header() in PHP

In PHP, any echo, whitespace, or UTF-8 byte-order mark printed before header() silently prevents the redirect from firing. If a PHP redirect is not working, check for output before the header() call, including in included files.

FAQ

Common 301 redirect questions.

Does a 301 redirect pass link equity to the new URL?

Yes. A 301 redirect passes the majority of link equity (also called PageRank) from the old URL to the new one. Google has confirmed that 301 redirects are the correct method for permanent URL changes and that link equity passes through them. The transfer is not instant and can take weeks to fully reflect in rankings. Use 301 for any permanent change where you want SEO value preserved.

What is the difference between a 301 and a 302 redirect?

A 301 redirect signals a permanent move. It tells browsers and search engines to update their records and stop using the old URL. A 302 redirect signals a temporary move, meaning search engines keep the old URL indexed and do not pass link equity to the new one. Use 301 for permanent URL changes, site migrations, and consolidating duplicate content. Use 302 only when you genuinely plan to restore the original URL, such as during A/B testing or temporary maintenance pages.

How do I test whether my 301 redirect is working?

The most reliable method is curl with the -I flag from a terminal: curl -I https://olddomain.com/old-page/ — this returns the raw HTTP response headers. A working 301 redirect shows HTTP/1.1 301 Moved Permanently with a Location header containing the new URL. You can also use online tools like the Redirect Checker or Redirect Path browser extension to test and visualize the redirect chain. Always test with the actual URL in incognito mode rather than relying on browser cache.

Should I redirect www to non-www with a 301?

Yes, and you should pick one canonical version and redirect consistently to it. If your site is non-www (example.com), redirect www.example.com to example.com with a 301. If your site is www (www.example.com), redirect example.com to www.example.com. Both are equally valid for SEO — what matters is consistency. Also set your preferred domain version in Google Search Console. The redirect and the Search Console setting should agree.

How many 301 redirect hops are too many?

Keep redirect chains to a maximum of two hops. A chain is when URL A redirects to URL B which redirects to URL C. Each hop adds latency, and search engine crawlers may not follow long chains reliably — link equity drops with each additional redirect. If you have existing chains from a previous migration, resolve them: update the origin to point directly to the final destination so A redirects straight to C, bypassing B entirely.

About the author

Farman Rind

SEO and GEO consultant with 7+ years running search visibility campaigns across 50+ websites. Farman handles technical SEO including URL structure, redirect audits, and site migrations as part of ongoing client work. Full background →

Technical SEO

Need more than
a single redirect?

Site migrations, redirect audits, and technical SEO fixes. If you are moving a site, consolidating URLs, or cleaning up a broken redirect structure, I can run the full audit and implementation.