14+ years building on WordPress / Replies in under 5 hours
WordPress 8 min read · Updated July 2026

Permanent 301 Redirect Using .htaccess: Complete Guide

Photo of Ajay Khandal
Ajay Khandal
WordPress Developer
TL;DR

A 301 redirect permanently moves a URL and transfers its link equity to the new address — use 302 only for genuinely temporary moves. .htaccess redirects are Apache-only (Nginx uses nginx.conf instead). Always back up .htaccess before editing — a syntax error causes a 500 error sitewide. The six patterns covered here: single-file same-domain (Redirect 301), single-file cross-domain, entire domain move (RewriteRule), force www., force non-www. (note: escape the dot as . in RewriteCond), and bulk extension redirect (.php → .html). Test with curl -I to confirm the correct 301 status code before marking the task done. For managing individual page redirects on WordPress, the Redirection plugin is safer than .htaccess — use .htaccess directly for domain-level and bulk pattern redirects.

A 301 redirect permanently forwards one URL to another. It tells browsers, search engines, and other crawlers that the original URL has moved — and that they should update their records and follow the new one from now on. The “301” is the HTTP status code; “permanent” is what it means. This guide covers how to implement 301 redirects using Apache’s .htaccess file, with six common patterns and the steps to verify they’re working.

What a 301 Redirect Actually Does

When a browser or crawler hits a URL that returns a 301 status, two things happen:

  1. The browser follows the redirect to the new URL automatically
  2. The browser (and search engine) caches the redirect — meaning future requests for the old URL go straight to the new one without asking your server again

From an SEO perspective, a 301 tells Google to transfer the link equity (ranking signals, backlinks) from the old URL to the new one. A 302 redirect (temporary) does not reliably transfer link equity, because search engines hold the old URL in their index on the assumption it’s coming back. Use 301 for permanent moves; use 302 for genuinely temporary redirects (A/B tests, maintenance pages, regional variations).

If you’ve recently changed your site’s URL structure, moved to a new domain, or migrated from HTTP to HTTPS, 301 redirects in .htaccess are how you tell search engines where everything went — before Google drops the old URLs from its index. Check the WordPress post-launch checklist for when to set these up relative to other launch-day tasks.

Before You Start: Apache Only

These rules only work on Apache web servers. If your hosting runs Nginx (common on cloud VPS setups, DigitalOcean Droplets, and some managed WordPress hosts), .htaccess is not read at all. Nginx uses a different configuration file (nginx.conf or a site config in /etc/nginx/sites-available/) with a different redirect syntax. Most shared hosting and cPanel-based hosting uses Apache — if you’re unsure, ask your host.

Before editing .htaccess:

  • Download a backup. A syntax error in .htaccess causes a 500 Internal Server Error sitewide. One missing bracket or misplaced flag takes the whole site offline. Always have the previous working version ready to re-upload.
  • Find the file. .htaccess lives in your web root (typically public_html/ or the folder your domain points to). It’s a hidden file — enable “Show hidden files” in your FTP client or file manager. On WordPress, there’s always an .htaccess present (WordPress needs it for permalink routing).
  • Edit carefully. Append your redirect rules before the WordPress # BEGIN WordPress block. Rules below that block may be overwritten the next time WordPress regenerates the file.

The Six .htaccess Redirect Patterns

1. Redirect a Single File (Same Domain)

The simplest case: one page has moved to a new URL on the same site. Use the Redirect directive with the old path (relative to web root) and the new path:

Redirect 301 /old-page.html /new-page.html

The first argument is the old path starting with /. The second is the new path (also starting with / for same-domain). This directive doesn’t require RewriteEngine to be on — it’s handled by Apache’s mod_alias, which is almost always available.

2. Redirect a Single File to a Different Domain

Same directive, but the destination is an absolute URL on another domain:

Redirect 301 /old-page.html https://newdomain.com/new-page.html

Use this when you’re retiring content from one site and consolidating it on another — for example, merging a satellite blog into your main domain.

3. Redirect an Entire Domain to a New Domain

When you’re moving everything from one domain to another, you need mod_rewrite. This captures every path on the old domain and appends it to the new domain URL:

RewriteEngine on
RewriteCond %{HTTP_HOST} ^olddomain.com [NC,OR]
RewriteCond %{HTTP_HOST} ^www.olddomain.com [NC]
RewriteRule ^(.*)$ https://newdomain.com/$1 [L,R=301,NC]

Breaking down the flags: [L] means stop processing further rules if this one matches. [R=301] sets the redirect status. [NC] makes the match case-insensitive. The $1 captures and replays the request path — so olddomain.com/about/ redirects to newdomain.com/about/ automatically.

The two RewriteCond lines with [NC,OR] and [NC] handle both the bare domain and the www variant. If you only need to redirect one of them (e.g. only www.olddomain.com), drop the second RewriteCond line.

4. Force the www. Version of Your Domain

All requests to the bare domain (example.com) redirect permanently to the www version (www.example.com):

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

Choose www or non-www and stick to it — both are valid, but mixing them means some pages exist on two URLs, which splits link equity and creates duplicate content signals for search engines. Once chosen, set your preferred version in Google Search Console as well.

5. Force the Non-www. Version of Your Domain

The reverse: all requests to www.example.com redirect to the bare domain. The RewriteCond matches the www version (note ^www\.), and the destination is the non-www URL:

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

Note the escaped dot (\.) in the RewriteCond pattern. In regex, an unescaped . matches any character — writing ^www.example.com would match wwwXexample.com. The backslash makes it match a literal dot only.

6. Redirect All Files with a Specific Extension

Useful when migrating from one technology to another — for example, moving from PHP pages to HTML pages, or from a legacy CMS with .aspx URLs to a new system:

RewriteEngine On
RewriteCond %{REQUEST_URI} \.php$
RewriteRule ^(.*).php$ /$1.html [R=301,L]

%{REQUEST_URI} is the requested path. The \.php$ condition matches any request ending in .php. The RewriteRule then strips the .php extension and adds .html in its place. The [R=301,L] flags make it a permanent redirect and stop further rule processing.

If you’re migrating from PHP to HTML on a WordPress site, be careful — WordPress itself uses PHP extensively. Apply this pattern only if you know specifically which PHP files need redirecting, not as a blanket rule.

Testing Your Redirects

Never assume a redirect is working without verifying it. The three fastest ways:

curl in the terminal

The most reliable method — shows the exact HTTP status code and the Location header the server returns:

curl -I https://olddomain.com/old-page.html

The -I flag fetches headers only (no body). A working 301 redirect returns:

HTTP/2 301
location: https://newdomain.com/new-page.html

If you see 200 instead of 301, the redirect isn’t firing. If you see 500, there’s a syntax error in .htaccess — restore your backup immediately.

Browser DevTools

  1. Open the old URL in a browser
  2. Open DevTools (F12 or Cmd+Option+I) → Network tab
  3. Reload the page
  4. Look at the first request in the waterfall — it should show status 301 with a Location header pointing to the new URL
  5. The second request should show 200 (the new page loading successfully)

Online redirect checker

Tools like httpstatus.io or the redirect checker in Screaming Frog will follow the full redirect chain and show you every hop — useful when diagnosing chains that should be single-step redirects but aren’t.

Common Mistakes

Missing RewriteEngine On

Every block using RewriteCond or RewriteRule requires RewriteEngine on as its first line. Forgetting it means the rules are parsed but never executed. The simple Redirect 301 directive (patterns 1 and 2) doesn’t need it — those use mod_alias, not mod_rewrite.

Infinite Redirect Loops

A redirect that sends traffic to a URL that then redirects back to the original creates an infinite loop — the browser stops after a few iterations and shows a “too many redirects” error. This commonly happens when:

  • You redirect the www version to non-www but already have the reverse rule somewhere
  • You redirect HTTP to HTTPS but the HTTPS version also triggers a redirect
  • A WordPress setting (Settings → General → WordPress Address) conflicts with an .htaccess rule

If you hit a redirect loop after editing .htaccess, restore the backup and test one rule at a time. See how to fix the too many redirects error in WordPress for a systematic diagnosis.

Wrong Placement in the File

WordPress regenerates the section between # BEGIN WordPress and # END WordPress whenever you save your permalink settings. Any custom rules in that block get overwritten. Always put custom redirect rules above the # BEGIN WordPress comment.

Unescaped Dots in RewriteCond Patterns

In RewriteCond regex, . matches any character. Writing ^example.com would also match exampleXcom. Use ^example\.com (backslash-escaped dot) to match a literal period. This matters most in www/non-www rules — an unescaped pattern can match hosts you don’t intend.

When .htaccess Isn’t the Right Tool

For most WordPress sites, the Redirection plugin (free, actively maintained) is a better choice than editing .htaccess directly for managing individual URL redirects. It stores redirects in the database, provides a UI to add/edit/delete them, logs 404s so you can see what needs redirecting, and doesn’t risk taking the site offline with a syntax error.

Use .htaccess directly for:

  • Domain-level changes (entire domain move, www/non-www enforcement) — these need to be in .htaccess because the WordPress application isn’t even loaded yet when these rewrites need to fire
  • Extension-level bulk redirects (.php → .html migrations)
  • Situations where the Redirection plugin isn’t available (pre-WordPress, static sites)

If something goes wrong after an .htaccess edit, see how to fix a 500 Internal Server Error in WordPress — a bad .htaccess line is one of the most common causes. And for the broader server security picture on Apache-hosted WordPress sites, the WordPress security guide covers .htaccess hardening rules (blocking access to wp-config.php, XML-RPC, and directory listing) that complement the redirect rules here.

Frequently asked questions

A 301 is an HTTP status code meaning 'moved permanently.' When a browser or search engine crawler hits a URL that returns 301, it follows the Location header to the new URL and updates its records accordingly. For SEO, a 301 transfers link equity (ranking signals, backlinks) from the old URL to the new one — a 302 (temporary redirect) does not reliably do this, because search engines keep the old URL in their index expecting it to return. Use 301 for permanent URL changes: domain moves, slug restructures, HTTP to HTTPS migrations. Use 302 for genuinely temporary situations like maintenance pages or regional A/B tests.

In .htaccess, you specify the status code explicitly: Redirect 301 /old /new for permanent, Redirect 302 /old /new for temporary. With RewriteRule, the flag [R=301] sets permanent and [R=302] sets temporary (just [R] defaults to 302 if no code is given — worth knowing to avoid accidental temporary redirects). The practical difference: 301s are cached by browsers (subsequent visits go straight to the new URL without asking your server) and are interpreted by Google as a signal to transfer ranking to the new URL. 302s are not cached and Google continues to index the original URL.

No. .htaccess is an Apache-specific configuration mechanism. Nginx does not read .htaccess files at all — they're simply ignored. If your hosting uses Nginx (common on cloud VPS providers, DigitalOcean Droplets, and some managed WordPress hosts), redirects go in the Nginx server block config (typically in /etc/nginx/sites-available/), using 'return 301 https://newurl$request_uri;' syntax. If you're unsure which server your host uses, ask their support or check the Server header in a curl -I request — it will say Apache or nginx.

The fastest way: run curl -I https://yourdomain.com/old-url in a terminal. A working 301 returns 'HTTP/2 301' and a 'location:' header pointing to the new URL. In a browser, open DevTools → Network tab, load the old URL, and check the first request in the waterfall — it should show status 301 with the Location header, followed by a 200 for the new URL. If you see 500, there's a syntax error in .htaccess — restore your backup immediately. If you see 200 on the old URL, the redirect rule isn't matching — check that RewriteEngine is on and the path patterns are correct.

Use mod_rewrite with two RewriteCond lines to catch both the bare domain and the www variant, then a RewriteRule to forward everything to the new domain while preserving the path: RewriteEngine on | RewriteCond %{HTTP_HOST} ^olddomain.com [NC,OR] | RewriteCond %{HTTP_HOST} ^www.olddomain.com [NC] | RewriteRule ^(.*)$ https://newdomain.com/$1 [L,R=301,NC] — The $1 captures and replays the URL path, so olddomain.com/about redirects to newdomain.com/about. This .htaccess file goes on the old domain's server. After migration, keep it live for at least 6 months so Google fully processes the move.

A redirect loop: URL A redirects to URL B, which redirects back to URL A (or through a chain that loops). Common causes: conflicting www/non-www rules (one rule forces www, another forces non-www); an HTTP-to-HTTPS redirect rule that keeps firing even on HTTPS requests; WordPress Settings → General URL conflicting with .htaccess rules; or a CDN/proxy (Cloudflare) doing its own redirect that conflicts with your origin rules. Fastest diagnosis: use curl -IL https://yourdomain.com/ to follow the full redirect chain and see exactly where the loop starts. For a full step-by-step fix, see the guide to resolving WordPress too many redirects errors.

Photo of Ajay Khandal

Written by Ajay Khandal

I'm a freelance WordPress developer with 14+ years of experience building, fixing, and speeding up sites for businesses, agencies, and store owners across the US, UK, Europe, and Australia. I specialize in custom themes, WooCommerce, and performance — the kind of work that shows up as faster load times and fewer support tickets. No account managers, no outsourced tickets — you work directly with me, with replies typically inside 5 hours.

Work with me →