</>
DevToolHub
Computer monitor displaying code in a dark development environment

.htaccess Redirect Guide: 301s, 302s, and Regex Patterns

·10 min read
Quick answer: A 301 redirect in .htaccess looks like Redirect 301 /old-page https://example.com/new-page. Use 301 for permanent moves (page renamed, domain changed). Use 302 for temporary moves (A/B tests, maintenance). For pattern-based redirects, use RewriteRule with regex. Always place HTTPS and domain rules before page-level redirects.

I once migrated a 400-page WordPress site to a new domain and did the redirects wrong. Used 302 instead of 301. Google treated every page as "temporarily moved" and kept the old domain indexed for four months. Four months of split authority between two domains, tanking rankings for both. The fix took 10 seconds --- change 302 to 301 --- but the recovery took another two months.

Redirects are the most common .htaccess task and the easiest to screw up. This guide covers every redirect type you'll actually need, with code you can copy directly.

Redirect Types: When to Use Each

There are four redirect types you'll encounter. Two matter, two are rare.

CodeNameTransfer SEO?When to UseHow Long Browsers Cache
301Moved PermanentlyYes (90-99% of link equity)Page permanently moved, domain change, URL restructureCached indefinitely by most browsers
302Found (Temporary)No (or minimal)A/B tests, seasonal pages, maintenance modeNot cached, re-checked each visit
307Temporary RedirectNoSame as 302 but preserves HTTP method (POST stays POST)Not cached
308Permanent RedirectYesSame as 301 but preserves HTTP methodCached indefinitely
The rule is simple: if the move is permanent, use 301. If it's temporary, use 302. You'll use 301 for 95% of redirect cases. I've built dozens of sites and used 307/308 exactly twice --- both times for API endpoints that needed to preserve POST data during a migration.

One critical detail: Google says 301 redirects pass "most" link equity to the new URL. In 2016, Gary Illyes from Google stated that 301, 302, and 307 all pass PageRank. But their behavior differs in practice. A 301 tells Google to replace the old URL with the new one in the index. A 302 tells Google to keep the old URL indexed. For SEO purposes, that difference is what matters.

Simple Page-Level Redirects

The most basic redirect. One old URL points to one new URL.

Using the Redirect Directive

# Single page redirect
Redirect 301 /old-page https://example.com/new-page

# Redirect with query string in destination
Redirect 301 /old-blog https://example.com/blog

# Redirect a file to a new location
Redirect 301 /docs/old-guide.pdf https://example.com/docs/new-guide.pdf

The Redirect directive is the simplest approach. The first argument is the old path (relative to the document root), the second is the full destination URL. No regex, no conditions, no RewriteEngine needed.

Watch out for trailing slashes. Redirect 301 /blog will also catch /blog/post-1, /blog/post-2, and everything under /blog/. If you only want to redirect the exact path /blog and not its children, use RedirectMatch with an anchor:

RedirectMatch 301 ^/blog$ https://example.com/new-blog

The ^ and $ mean "match from the start to the end of the string" --- so only the exact path /blog matches, not /blog/anything-else.

Using RewriteRule

RewriteEngine On

# Single page redirect
RewriteRule ^old-page$ https://example.com/new-page [R=301,L]

# Redirect with captured path
RewriteRule ^blog/(.*)$ https://example.com/articles/$1 [R=301,L]

RewriteRule is more powerful than Redirect because it supports regex and capturing groups. The $1 in the destination captures whatever matched (.<em>) in the pattern. So /blog/my-post redirects to /articles/my-post, /blog/another-post redirects to /articles/another-post, and so on.

The [R=301,L] flags mean: R=301 sets the redirect type, L means "last rule" --- stop processing after this match. Without L, Apache continues evaluating subsequent rules, which can cause unexpected behavior or redirect loops.

Regex Redirect Patterns

Regex redirects handle bulk URL changes with a single rule. They're essential for site migrations, CMS changes, and URL restructuring.

Remove Dates From Blog URLs

RewriteEngine On
# /2024/03/15/post-slug → /blog/post-slug
RewriteRule ^[0-9]{4}/[0-9]{2}/[0-9]{2}/(.*)$ /blog/$1 [R=301,L]

The pattern [0-9]{4}/[0-9]{2}/[0-9]{2}/ matches any date in YYYY/MM/DD format. The (.</em>) captures the slug. This single rule handles every date-based URL on the site.

I used this exact rule when migrating a WordPress site to a flat URL structure. 200+ blog posts, one redirect rule. The alternative --- 200 individual Redirect lines --- is a maintenance nightmare.

Remove File Extensions

RewriteEngine On
# /about.html → /about
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)\.html$ /$1 [R=301,L]

# /product.php?id=42 → /products (needs individual mapping)

The RewriteCond %{REQUEST_FILENAME} !-d check prevents the rule from firing on actual directories. Without it, a directory named /about.html/ (unlikely but possible) would break.

Removing .php extensions with query parameters is trickier. /product.php?id=42 can't be cleanly redirected to /products/widget with a single regex rule --- you need a mapping of IDs to slugs. For small sites (under 50 pages), individual redirects work. For larger sites, handle it in your application code.

Redirect an Entire Directory

RewriteEngine On
# /old-section/anything → /new-section/anything
RewriteRule ^old-section/(.*)$ /new-section/$1 [R=301,L]

This preserves the path structure within the directory. Every page under /old-section/ gets redirected to the same path under /new-section/. Clean, simple, handles hundreds of pages at once.

Query String Redirects

RewriteEngine On
# /page?id=42 → /new-page
RewriteCond %{QUERY_STRING} ^id=42$
RewriteRule ^page$ /new-page? [R=301,L]

The trailing ? in the destination is critical --- it strips the original query string. Without it, the redirect goes to /new-page?id=42, carrying over the parameter you're trying to get rid of.

Query strings aren't part of the RewriteRule pattern. They're matched separately with RewriteCond %{QUERY_STRING}. This trips up a lot of developers who try to match page?id=42 in the rule itself.

Domain-Level Redirects

These handle the big moves: HTTP to HTTPS, WWW normalization, and full domain migrations.

Force HTTPS

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

This should be the first redirect rule in your .htaccess. If it comes after other rules, an HTTP request might match a different redirect first and end up on an HTTP destination --- creating a chain instead of a single hop.

Force WWW (or Non-WWW)

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

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

The [NC] flag means case-insensitive matching. Without it, WWW.Example.com wouldn't match (unlikely, but hostnames can technically have mixed case).

Full Domain Migration

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

This catches both olddomain.com and www.olddomain.com and redirects everything to the new domain, preserving the path. So /blog/my-post on the old domain goes to /blog/my-post on the new domain.

Place this in the .htaccess on the old domain's server. The old domain must still be pointing to a server that processes the redirect. If you cancel the old hosting before Google recrawls, the redirects stop working and you lose all link equity.

The Correct Rule Order

Order matters in .htaccess. Apache processes rules top-to-bottom, and the first matching rule with an [L] flag stops processing. Here's the order I always use:

RewriteEngine On

# 1. Force HTTPS (always first)
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

# 2. Force WWW or non-WWW
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]

# 3. Domain-level redirects (old domain → new domain)
# (only if migrating)

# 4. Directory-level redirects (bulk patterns)
RewriteRule ^old-section/(.*)$ /new-section/$1 [R=301,L]

# 5. Page-level redirects (individual pages)
Redirect 301 /old-page /new-page
Redirect 301 /another-old-page /another-new-page

Why this order? HTTPS and domain rules are the broadest --- they affect every request. If a page-level redirect fires first and sends someone to an HTTP URL, the HTTPS rule fires next, creating a two-hop chain (HTTP old page to HTTP new page to HTTPS new page). Putting HTTPS first makes the redirect a single hop.

Don't want to think about ordering and syntax? The htaccess generator outputs rules in the correct order with proper flags and conditions. Select your redirect type, enter the URLs, and copy the output.

Common Redirect Patterns Reference Table

Here's a cheat sheet for the redirect patterns I use most often:

ScenarioCode
Single page movedRedirect 301 /old /new
Entire folder movedRewriteRule ^old/(.<em>)$ /new/$1 [R=301,L]
Remove trailing slashRewriteRule ^(.+)/$ /$1 [R=301,L]
Add trailing slashRewriteRule ^(.+[^/])$ /$1/ [R=301,L]
Remove .html extensionRewriteRule ^(.+)\.html$ /$1 [R=301,L]
HTTP to HTTPSRewriteCond %{HTTPS} off + RewriteRule ^(.</em>)$ https://%{HTTP_HOST}/$1 [R=301,L]
Old domain to newRewriteCond %{HTTP_HOST} old\.com + RewriteRule ^(.*)$ https://new.com/$1 [R=301,L]
Strip query stringAdd ? at end of destination: /new-page?
Redirect only 404sErrorDocument 404 /custom-404.html (not technically a redirect)
Keep this bookmarked. I reference it every time I set up a new site migration.

How to Test Redirects

Never deploy redirects blind. Here's how to verify they work before (and after) going live.

cURL from the command line. The most reliable method:

curl -I -L https://example.com/old-page

The -I flag fetches headers only (faster). The -L flag follows redirects. You'll see every hop: the response code (301, 302), the Location header (where it redirects to), and the final destination. If you see more than one hop, you have a redirect chain that needs fixing.

Browser DevTools. Open Network tab, navigate to the old URL, and look for a 301/302 response with a Location header. Chrome's Network tab shows redirect chains clearly --- each hop is a separate row.

Screaming Frog. For bulk testing during migrations. Feed it a list of old URLs and it'll report the response code and final destination for each one. Essential for sites with 100+ redirects.

Google Search Console. After deploying, check the Coverage report for redirect errors. Google flags redirect loops, broken redirect chains, and 404s from expected-but-missing redirects. This data appears within a few days of Googlebot recrawling.

Test your redirect regex patterns before deploying them live. The regex tester lets you paste your pattern, test it against sample URLs, and see exactly what matches and what gets captured.

Redirect Chains and Performance

A redirect chain is when URL A redirects to URL B, which redirects to URL C. Each hop adds 50-100ms of latency and dilutes a small amount of SEO value. Google follows up to 5 hops before giving up, but best practice is a single hop: A directly to C.

Common causes of redirect chains:

  • HTTP to HTTPS combined with WWW normalization. http://www.example.com redirects to https://www.example.com, then to https://example.com. Fix: combine both conditions into one rule.
  • Multiple URL migrations over time. In 2020 you moved /page-a to /page-b. In 2023 you moved /page-b to /page-c. Now /page-a chains through /page-b to reach /page-c. Fix: update the first redirect to point directly to /page-c.
  • CMS-level redirects stacked with .htaccess redirects. WordPress's redirect plugin adds a rule, and your .htaccess adds another. The request bounces between both. Fix: use one system for redirects, not two.
I audit redirect chains quarterly. Run Screaming Frog against your sitemap, filter for 3xx responses, and check for anything with more than one hop. It takes 30 minutes and can recover significant crawl budget on sites with hundreds of redirected URLs.

FAQ

How long should I keep 301 redirects active?

Indefinitely, or at least as long as the old URL has backlinks pointing to it. Google recrawls and processes 301s within days to weeks, but other sites may link to the old URL for years. Removing the redirect turns those backlinks into 404 errors --- wasted link equity. Server overhead for redirect rules is negligible; keeping them costs you nothing.

Can I redirect with just HTML or JavaScript?

Technically yes. An HTML meta refresh (<meta http-equiv="refresh" content="0;url=https://example.com/new-page">) or JavaScript window.location redirect works for users. But Google treats these as soft redirects that don't pass full link equity. Server-side 301 redirects are the only type Google fully respects for SEO purposes. Use .htaccess redirects, not client-side workarounds.

What happens if I use 302 instead of 301 by accident?

Google keeps the original URL in its index instead of replacing it with the new one. Both URLs may appear in search results, splitting your click-through rate and link equity. If you catch it early (within a few weeks), switching to 301 and waiting for a recrawl usually fixes it. If it's been months, recovery can take 4-8 weeks because Google has to re-evaluate the redirect signal.

Do redirects affect page speed?

Each redirect adds one additional HTTP round-trip, typically 50-100ms on a fast connection. A single redirect is barely noticeable. A chain of 3-4 redirects adds 200-400ms, which impacts Core Web Vitals (specifically Time to First Byte). Google's PageSpeed Insights flags redirect chains as a performance issue. Keep redirects to a single hop whenever possible.

Next Steps

  • Generate .htaccess redirect rules (and other directives) without memorizing syntax using the htaccess generator.
  • Learn what .htaccess can do beyond redirects --- caching, compression, security --- in the .htaccess file explained guide.
  • Test your redirect regex patterns before deploying with the regex tester.
  • Make sure your new URLs follow best practices with the slug generator.