DEV Community

Cover image for Bulk 301 Redirects in .htaccess: A Step-by-Step Guide
Huzaifa Alam
Huzaifa Alam

Posted on Originally published at matrixinnsolutions.com

Bulk 301 Redirects in .htaccess: A Step-by-Step Guide

Originally published on the MatrixInn Solutions blog.

When you change URLs — a redesign, a new CMS, merging blog posts, moving to a new domain — every old URL that isn't redirected turns into a 404. Visitors hit a dead end, and the ranking signals that page earned stay stuck on a URL that no longer exists. A 301 redirect tells browsers and search engines the page has moved permanently and passes those signals to the new address.

Redirecting one page is easy. Redirecting 200 is where people make mistakes. Here's a process that works for any size of migration.

Step 1: Build your redirect map in a spreadsheet

Make two columns: old path and new URL. One row per page.

Old path New URL
/services/web-design.html https://example.com/services/web-design
/blog/2019/05/my-post https://example.com/blog/my-post
/shop/blue-jacket https://example.com/products/blue-jacket

Where to get the old URLs:

  • Your old sitemap.xml (the best source)
  • Google Search Console → Pages, and the Performance report sorted by clicks. Prioritize URLs that actually get traffic
  • A crawl of the old site with Screaming Frog or a similar crawler

Map every old URL to its closest equivalent, not to the homepage. Google treats mass redirects to the homepage as soft 404s, and the ranking signals are lost anyway.

Step 2: Generate the rules

For simple one-to-one moves, Apache's mod_alias is the cleanest option:

Redirect 301 /services/web-design.html https://example.com/services/web-design
Redirect 301 /blog/2019/05/my-post https://example.com/blog/my-post
Redirect 301 /shop/blue-jacket https://example.com/products/blue-jacket
Enter fullscreen mode Exit fullscreen mode

If your .htaccess already uses RewriteRule (WordPress, most PHP frameworks), keep everything in mod_rewrite so the two modules don't fight over the same request:

RewriteEngine On
RewriteRule ^services/web-design\.html$ https://example.com/services/web-design [R=301,L]
RewriteRule ^blog/2019/05/my-post$ https://example.com/blog/my-post [R=301,L]
RewriteRule ^shop/blue-jacket$ https://example.com/products/blue-jacket [R=301,L]
Enter fullscreen mode Exit fullscreen mode

Notice the escaped dot (\.html) and the ^…$ anchors. Without them, /shop/blue-jacket would also match /shop/blue-jacket-xl.

Writing hundreds of these by hand is slow and error-prone, and one stray space can return a 500 error for the whole site. Instead, copy your two spreadsheet columns into our free .htaccess redirect generator, open Bulk paste, and click Import & generate. It trims full URLs down to paths, escapes regex characters, and outputs either Redirect or RewriteRule lines. It all runs in your browser, so your URL list is never uploaded.

Step 3: Put the rules in the right place

  1. Back up your current .htaccess.
  2. Paste the redirects near the top, after RewriteEngine On but before any catch-all rules (like WordPress's # BEGIN WordPress block). Apache processes rules top to bottom, and a catch-all placed earlier will swallow your redirects.
  3. Put more specific paths above broader patterns.

Step 4: Avoid the three classic mistakes

Redirect chains. If /a already redirects to /b, and you now move /b to /c, update the first rule to go straight to /c. Every extra hop slows the page down and loses a little signal. Keep every redirect to a single hop.

Redirect loops. A rule whose destination matches its own pattern (or another rule that points back) loops forever and the browser shows "too many redirects". A common version of this: a rule that strips trailing slashes fighting Apache's own DirectorySlash, which adds them back to real folders. Test folder URLs specifically.

Using 302 by accident. R on its own in a RewriteRule means 302 (temporary). For permanent moves always write R=301.

Step 5: Test before you trust it

Check a sample of old URLs from the command line:

curl -I https://example.com/blog/2019/05/my-post
Enter fullscreen mode Exit fullscreen mode

You want HTTP/1.1 301 Moved Permanently and a Location: header with the new URL, and that new URL should return 200, not another redirect. For a big list, loop through your old paths in a script, or run a crawler in list mode over the old URLs.

Then in Google Search Console:

  • Submit the new sitemap.
  • Watch Pages → Not found (404) over the next few weeks for anything you missed, and add a redirect for each one.

How long should redirects stay?

Keep them for at least a year, and ideally permanently. Other websites, bookmarks and old social posts keep linking to your old URLs long after Google has updated its index. Removing redirects early throws that traffic away.

Quick checklist

  • [ ] Every old URL with traffic or backlinks is in the map
  • [ ] Each maps to its closest new equivalent (not the homepage)
  • [ ] One module style (Redirect or RewriteRule), placed before catch-all rules
  • [ ] R=301, anchored patterns, escaped dots
  • [ ] No chains, no loops, and folder URLs tested
  • [ ] New sitemap submitted; 404 report checked weekly

Need to generate the rules now? Paste your list into the free bulk .htaccess redirect generator. Planning a bigger migration or rebuild? Talk to us.

Top comments (0)