SEO & web Site files

htaccess redirect generator

Built in your browser · nothing is uploaded

Local · a mistaken 301 is cached indefinitely

Turns a list of old and new paths into Apache RewriteRule lines. A pair written as /old-page | /new-page comes out as RewriteRule ^old-page$ /new-page [R=301,L], under a single RewriteEngine On. The leading slash is dropped on purpose, because a per-directory pattern never sees one.

How to use the htaccess redirect generator

1 Write one pair per line, old path and new target separated by a pipe. The old side is a path; the new side can be a path or a full URL.
2 Choose the status code. 301 and 308 say permanent and are cached; 302 and 307 say temporary and are not.
3 Copy the block into a .htaccess file at the document root, or merge the RewriteRule lines into an existing one and keep a single RewriteEngine On.
4 Test each old URL with curl -I and read the status line and the Location header rather than following the redirect in a browser.
5 Add [NC] for case-insensitive matching or [QSD] to drop the incoming query string, neither of which this tool writes for you.

The generated pattern has no leading slash, and the reason is a rule of Apache’s rather than a style choice. In per-directory context the documentation is explicit: "the directory-path to which the rule applies is stripped from the currently mapped filesystem path before comparison", "the removed prefix always ends with a slash", and consequently "a Pattern with ^/ never matches in per-directory context". A rule written as ^/old-page$ in a .htaccess file matches nothing at all, silently. This tool strips the slash so the rules work; the corollary is that they assume the file sits at the document root, because a .htaccess inside /shop/ has /shop/ removed before matching, and ^old-page$ there means /shop/old-page.

The pattern never sees the query string. Apache says to use a RewriteCond on %{QUERY_STRING} if you want to match against it. Two consequences follow, one convenient and one destructive. The convenient one: /old-page?ref=newsletter still matches ^old-page$, so tracking parameters do not defeat your redirects, and by default the query string is carried across to the new URL untouched. Add [QSD] if you want it dropped, or [QSA] to merge it with parameters you are adding.

The destructive one is a redirect loop, and it catches people who are being careful. Write a rule sending /a to /a?utm_source=x and the browser requests /a?utm_source=x, Apache strips the query before matching, the path is still a, the rule fires again, and the loop runs until the browser gives up. Any rule whose target differs from its source only in the query string will do this. The fix is a RewriteCond %{QUERY_STRING} !utm_source guard above it.

The $ anchors the end of the path, so matching is exact. /old-page matches; /old-page/ does not, and neither does /old-page/child. If both slash forms were in circulation, write both pairs, or drop the $ and accept that the rule then also catches anything beginning with that path. Matching is case-sensitive too, so /Old-Page needs its own line or an [NC] flag added by hand.

On the status codes, RFC 9110 is precise where SEO advice usually is not. A 301 response "is heuristically cacheable", and so is a 308: with no explicit cache headers, clients may keep them for as long as their own heuristics allow, which in practice means a mistaken permanent redirect keeps sending returning visitors to the wrong place long after the server is fixed. You cannot recall one already cached. You can bound the next one, by sending an explicit Cache-Control with the redirect response, and using 302 or 307 whenever reverting is even slightly possible.

The 307 and 308 pair exists for a different reason again: the method. RFC 9110 notes that for 301 and 302 "a user agent MAY change the request method from POST to GET", while a 307 recipient "MUST NOT change the request method". Redirecting a form endpoint with a 301 can therefore turn a POST into a GET and lose the body. Use 308 for a permanent move of anything that receives POSTs, keeping in mind the RFC’s own caution that 308 "is much younger (June 2014) than its sibling codes and thus might not be recognized everywhere".

Two operational notes. Chains cost real time: A to B to C is two round trips before anything renders, and each hop is another chance for a client to stop following. When you retire B, rewrite the A rule to point at C rather than leaving the chain in place. And .htaccess itself is not free. Apache’s own documentation is blunt that enabling it "causes a performance hit, whether or not you actually even use them", because httpd looks for the file in the requested directory and every directory above it, on every request. Redirects in the virtual host config cost nothing per request; the trade you are making is that .htaccess needs no restart and no root, which is often the reason it exists.

What people use it for

  • Moving a set of old URLs after a site restructure
  • Writing the RewriteRule lines for a batch of retired pages
  • Choosing 301 or 302 for a page that will come back
  • Redirecting a form endpoint without turning its POST into a GET
  • Collapsing an old redirect chain into single hops
  • Producing rules that survive tracking parameters on the incoming URL

Questions

Because Apache strips the directory path, trailing slash included, before matching in per-directory context. Its documentation states that a pattern with ^/ never matches there, so the slash would break every rule.

Apache, mod_rewriteApache — when (not) to use .htaccess filesRFC 9110 §15.4 — redirection status codes
Was this tool any good?
Internal signal only · I use it to find the tools worth rebuilding