A 301 redirect is a server response telling the browser and the search crawler that a page has moved for good, and here is its new address. What sets it apart from other ways of pushing a visitor along is that it carries the accumulated link weight into the index as well. Below are ready-made .htaccess rules for the usual tasks, their nginx equivalents, and the mistakes that cost a site traffic during a move.
How it works
The server answers the request with code 301 and a Location header holding the new address. The browser goes there immediately, usually without the visitor noticing, and caches the hop. A search engine does the same: it replaces the old URL in the index with the new one and passes the link weight along.
The difference between the codes matters and costs money:
| Code | Meaning | When to use |
|---|---|---|
| 301 | Moved permanently | Domain change, page address change, merging mirrors, moving to HTTPS |
| 302 | Temporarily | A two-week promo, maintenance, an A/B test |
| 307 | Temporary, request method preserved | Same, but for POST requests |
| 308 | Permanent, method preserved | A rare case: moving an API where POST matters |
| 410 | Gone for good | The page no longer exists and has no replacement |
Confusing 301 with 302 is the most expensive mistake on this list: with a 302 the engine keeps the old address in the index and passes no weight to the new one.
Rules for .htaccess
Every example assumes Apache with mod_rewrite enabled. The block goes at the top of the .htaccess file in the site root — above the CMS rules, otherwise WordPress intercepts the request first.
RewriteEngine On
RewriteBase /
One page to another
# the simple way
Redirect 301 /old-page/ https://site.com/new-page/
# the same result through mod_rewrite, if your other rules use it too
RewriteRule ^old-page/?$ https://site.com/new-page/ [R=301,L]
Merging mirrors: www to non-www
RewriteCond %{HTTP_HOST} ^www.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
The other way round, when the www domain is the main mirror:
RewriteCond %{HTTP_HOST} !^www. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]
Moving to HTTPS
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
Behind Cloudflare or another proxy the %{HTTPS} condition will not fire — the request reaches the server over plain HTTP. There you check a header instead:
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
HTTPS and non-www in one rule
The key trick of this section: do not stack two rules, or you end up with a chain of two hops.
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} ^www.(.+)$ [NC]
RewriteRule ^(.*)$ https://site.com/$1 [R=301,L]
Trailing slash
# add the slash
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !.[a-zA-Z0-9]{1,5}$
RewriteCond %{REQUEST_URI} !/$
RewriteRule ^(.*)$ /$1/ [R=301,L]
# strip the slash
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [R=301,L]
The !-f and !-d conditions are not decoration: without them the rule starts rewriting image URLs and existing directories.
Moving to a new domain
# keeping page addresses — this is the way to do it
RewriteCond %{HTTP_HOST} ^(www.)?old-site.com$ [NC]
RewriteRule ^(.*)$ https://new-site.com/$1 [R=301,L]
# everything to the home page — only if the new site has a different structure
RewriteRule ^(.*)$ https://new-site.com/ [R=301,L]
Search engines often treat the second variant as a soft 404 and pass no weight. Map address to address wherever you can, and send to the home page only what truly has no counterpart.
Section to section, and addresses with parameters
# /blog/article/ → /news/article/
RewriteRule ^blog/(.*)$ /news/$1 [R=301,L]
# ?id=12 → /catalog/sofa/
RewriteCond %{QUERY_STRING} (^|&)id=12(&|$)
RewriteRule ^index.php$ /catalog/sofa/? [R=301,L]
The question mark at the end of the target drops the original query string — without it, the string sticks to the new address.
The same on nginx
nginx has no .htaccess: rules live in the server config and take effect after nginx -t and a reload.
server {
listen 80;
server_name www.site.com site.com;
return 301 https://site.com$request_uri;
}
# one page to another
location = /old-page/ {
return 301 https://site.com/new-page/;
}
# section to section
rewrite ^/blog/(.*)$ /news/$1 permanent;
return 301 is faster than rewrite and reads unambiguously, which is why simple cases use exactly that.
Common mistakes
- A redirect chain. www → non-www → https → slash: four requests instead of one. Collapse the conditions into a single rule.
- A loop. A rule sends the page to itself and the browser shows
ERR_TOO_MANY_REDIRECTS. The usual cause is an HTTPS redirect behind a proxy written against%{HTTPS}. - 302 instead of 301. The move is done, but the weight stays on the old address.
- Everything to the home page. Mass-redirecting hundreds of pages to the root reads to a search engine as a 404 in disguise.
- Rules placed after the CMS block. In
.htaccessWordPress grabs the request first and your rule never fires: put your block above it. - A redirect where canonical belongs. If people need both pages and only the content is duplicated, use
rel="canonical", not a 301. - Forgotten internal links. After the move, links inside the site must point straight at the new address, otherwise every visitor hop goes through the redirect.
How to check
The terminal is the fastest way. -I asks for headers only, -L follows the chain to the end:
curl -sIL https://site.com/old-page/ | grep -E "HTTP/|Location"
A correct answer looks like this: one HTTP/1.1 301, one Location, then HTTP/2 200. Two or more 301s in a row mean a chain that needs collapsing. Search Console shows the same thing in the Page indexing report, and the browser shows it in the Network tab with “Preserve log” switched on.
After a large move, test a sample of two or three dozen addresses: the home page, categories, product pages, URLs with parameters and the addresses that rank in search.
The short version
301 is for a permanent move, 302 for a temporary one, 410 for what is gone. Write one rule that lands on the final address with no intermediate steps. And always verify — curl -sIL answers the question in a second.
If the move is a big one — a new domain, merging sites, switching to HTTPS with a rebuilt URL structure — write to us: we will draw up the mapping and move the site so the traffic stays where it is.




