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:
- The browser follows the redirect to the new URL automatically
- 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
.htaccesscauses 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.
.htaccesslives in your web root (typicallypublic_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.htaccesspresent (WordPress needs it for permalink routing). - Edit carefully. Append your redirect rules before the WordPress
# BEGIN WordPressblock. 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
- Open the old URL in a browser
- Open DevTools (F12 or Cmd+Option+I) → Network tab
- Reload the page
- Look at the first request in the waterfall — it should show status 301 with a Location header pointing to the new URL
- 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
.htaccessbecause 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.


