Htaccess Redirect Generator
Build .htaccess redirect rules — bulk 301/302 maps plus HTTPS, www and trailing-slash canonicals — with paths escaped and rule order checked.
# Apache redirect rules — review and test them on your own server before deploying. # Requires mod_rewrite, plus .htaccess enabled for this directory (AllowOverride FileInfo) and FollowSymLinks. # Patterns carry no leading slash: this file is assumed to sit in your document root. RewriteEngine On # Redirect map — 2 rules. RewriteRule ^old/page\.html$ /new/page [R=301,L] RewriteRule ^blog/2019/post$ /blog/post [R=301,L]
2 rules · 429 B
Rules are written for an .htaccess file in the directory they apply to. Apache strips that directory prefix before a RewriteRule pattern sees the path, which is why patterns have no leading slash.
mod_rewrite must be loaded and AllowOverride must permit FileInfo in this directory. This page writes no <IfModule> wrapper: a missing module should fail loudly, not redirect nobody in silence.
The download is the block above, byte for byte. Save it as .htaccess (with the leading dot) in the directory it applies to.
Press ? for shortcuts
Do it offline:$ apachectl configtest
How to use it
- Paste the map — one row per redirect: old path, then a comma, a tab or a space, then the new path. Blank and hash rows are skipped.
- Pick the status yourself: 301 and 308 say permanent, 302 and 307 temporary, and the two newer codes also keep the request method.
- Choose the mechanism. RewriteRule matches one exact path; Redirect matches a prefix and appends whatever followed it.
- Switch on the canonical rules you need, and type your bare domain when asked.
- Copy the block into the .htaccess file in your document root, then request a moved address yourself before announcing anything.
Paste your old and new paths and this page writes the Apache directives for them: one rule per row, literal paths escaped so a dot stays a dot, plus canonical toggles for HTTPS, www and trailing slashes. It runs in the browser and uploads nothing. The block still has to be tried on your own server before you deploy it.
How it works
A .htaccess file is read per directory, and Apache strips that directory prefix before matching, so in the document root the leading slash is gone: the source lands as ^old/page\.html$. The pattern is a regular expression, so a dot or a bracket is escaped, and a path holding a space is quoted because Apache splits arguments on whitespace. It sees the path alone, never the query. The target is an expanded string, not an expression, and there a dollar or a percent already means something — hence the NE flag.
Order is the other half of the job. RewriteEngine On leads; then scheme and host, merged into one hop; then index.php removal matched against THE_REQUEST so a front controller cannot loop; then your rows as pasted; and the trailing-slash rule last. The block is read back for rows that point at themselves, hop through a second row, or sit behind a rule that already matches them.
Examples
/old/page.html,/new/page
/blog/2019/summer-sale,/sale# Apache redirect rules — review and test them on your own server before deploying.
# Requires mod_rewrite, plus .htaccess enabled for this directory (AllowOverride FileInfo) and FollowSymLinks.
# Patterns carry no leading slash: this file is assumed to sit in your document root.
RewriteEngine On
# Redirect map — 2 rules.
RewriteRule ^old/page\.html$ /new/page [R=301,L]
RewriteRule ^blog/2019/summer-sale$ /sale [R=301,L]
Two rows, two rules, comments on. The dot is escaped so it stays a dot, and neither pattern keeps its leading slash.
/about-us.php,/about# Apache redirect rules — review and test them on your own server before deploying.
# Requires mod_rewrite, plus .htaccess enabled for this directory (AllowOverride FileInfo) and FollowSymLinks.
# Patterns carry no leading slash: this file is assumed to sit in your document root.
RewriteEngine On
# Force HTTPS and the canonical host in a single redirect, so a visitor never pays two round trips.
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]
# Redirect map — 1 rule.
RewriteRule ^about-us\.php$ /about [R=301,L]
HTTPS and the www host are one rule, not two, so an insecure bare-domain request is fixed in a single hop. NC is there because host names are case-insensitive.
/search.php?q=shoes,/collections/shoes
/legacy/list.php?id=42,/products/42RewriteEngine On
RewriteCond %{QUERY_STRING} ^q=shoes$
RewriteRule ^search\.php$ /collections/shoes [R=301,QSD,L]
RewriteCond %{QUERY_STRING} ^id=42$
RewriteRule ^legacy/list\.php$ /products/42 [R=301,QSD,L]
Edge case — a query string in the source. It cannot stay in the pattern, so each becomes a condition above its rule, and QSD drops it.
/docs/old,/docs/new
/docs/old/deeper,/docs/newestRedirect 301 /docs/old /docs/new
Redirect 301 /docs/old/deeper /docs/newest
Edge case — the mod_alias footgun. Both lines are valid; the second never runs, because the first matches by prefix and sends the deeper address to /docs/new/deeper.
/old prices (2019).html,/pricing
/caf%C3%A9,/cafeRewriteEngine On
RewriteRule "^old prices \(2019\)\.html$" /pricing [R=302,L]
RewriteRule ^caf%C3%A9$ /cafe [R=302,L]
Edge case — escaping. Brackets and the dot are escaped, the space forces quotes, and the encoded row is left byte for byte.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
A fragment cannot be redirected
Everything from the hash onwards stays in the browser, so no rule can act on it. A row carrying one is refused rather than turned into a rule that quietly does nothing.
RewriteBase does not move these rules
It only applies where a substitution is written relative, and every target here is absolute; the directive is emitted for hand-editing and changes nothing above it.
Trailing-slash rules and directory requests
The add-a-slash rule skips real files and anything shaped like one, so it cannot fight the slash Apache adds for a directory.
Two modules, one order
mod_rewrite runs before mod_alias, so a RewriteRule beats a Redirect over the same address whichever line comes first — which is why mixing the two is called out.
Common errors
Row 1: the old path "old/page.html" does not start with "/". Apache matches URL-paths from the site root. Write it as "/old/page.html".- The URL-path begins at the site root, so a bare word is refused rather than guessed at.
Row 1: the new path "new-page" is neither a root-relative path nor an http(s) URL. Apache accepts only those two as a redirect target. Write "/new/page" or "https://example.com/new/page".- A target must be something a browser can follow: a root-relative path, or a full address including the scheme.
Row 2: the old path "/old/page" was already mapped on row 1. Apache stops at the first rule that matches, so this row could never run. Delete one of the two rows.- The commonest fault in a hand-merged map. Only the first rule can fire, so the second is refused.
Row 1: the old path carries a fragment ("#…"). Browsers never send the fragment to the server, so no rule can match one. Remove everything from "#" onwards.- Usually arrives by copying an address out of the browser bar. Strip the hash; if it mattered, the landing page must handle it.
The non-www host rule needs your domain. Apache has to be told which host name to send visitors to — deriving it from %{HTTP_HOST} would double the prefix once the condition no longer holds. Enter a bare domain such as "example.com".- A host rule must name the host it sends people to; deriving it from the incoming host breaks once the condition stops matching.
Row 1: "/old/page" redirects to itself. A browser follows that until it gives up with "too many redirects". Point the row somewhere else, or delete it.- The loop everyone writes once. The rule is still generated, since you may be mid-edit, but the warning stays.
FAQ
Is the generated block checked against a real Apache?
Its shapes are. The forms emitted here ship as golden vectors run through httpd -t on Apache 2.4.66, a real syntax check: it rejects an unterminated flag list, an uncompilable expression and an invalid R code. That is all it proves. It does not read .htaccess files at all. Test the block on your own server.
Should a site migration use 301 or 302?
Use 302 or 307 while addresses are still moving, 301 or 308 once they have settled. A permanent code carries ranking signals across, but browsers cache it hard, so publishing one early is expensive to undo. RFC 9110 defines all four.
Can I redirect an address with a query string such as ?id=42?
Yes, in two lines rather than one. The pattern matches the path only, so the query becomes a RewriteCond above the rule, and that condition matches exactly — reorder the parameters and it stops firing. Decide what reaches the target too.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Generate the block again from the current form.
- ⌘/Ctrl + S
- Download the block, rather than saving the page.
- ⌘/Ctrl + C
- Copy the whole block when nothing is selected.
- ⌘/Ctrl + Shift + C
- Force-copy the block, selection or not.
- t
- Next theme (single-key; idle in text fields).
- ? or ⌘/Ctrl + /
- Open the shortcut help.
- Esc
- Close the topmost overlay.