Generate .htaccess redirect rules for 301 and 302 redirects
Turn a redirect list into ready-to-paste Apache rules without writing regular expressions by hand.
Run — free
Provide each old path with its destination, choose a permanent 301 or temporary 302 response, and receive exact RewriteRule lines plus a combined .htaccess block. The generator escapes source paths safely, supports local destinations and absolute HTTP or HTTPS URLs, preserves the order you supplied, and reports incomplete pairs before they become broken redirects.
Prepare a clear redirect list
Start with one row for every address that should move. The generate .htaccess redirect rules tool expects a source in the from field and a destination in the to field. A source must be a root-relative path beginning with a slash, such as /old-products/widget. Leave query strings and fragments out of the source because Apache RewriteRule matches the URL path, not those components. A destination can be another root-relative path on the same site or a complete HTTP or HTTPS URL when the visitor must move to another host. Enter percent-encoded characters when a URL needs them instead of literal spaces. Keep the rows in the order you want Apache to evaluate them. Although these generated rules match complete paths and therefore avoid many broad-pattern conflicts, a deliberate order still makes the final configuration easier to inspect, maintain, and compare with a migration spreadsheet. Every row is validated, and an omitted from or to value stops generation with a message identifying the incomplete pair.
Choose permanent or temporary behavior
Select 301 when the move is intended to be permanent and search engines, browsers, and downstream systems should learn the replacement address. Select 302 when the change is temporary, for example during a short campaign, maintenance window, or controlled test. The chosen status applies consistently to every pair in one request, which prevents a mixed spreadsheet from silently producing different behavior than expected. Each output line uses an anchored source pattern, a validated destination, and Apache flags in the form R=301,L or R=302,L. Anchors make the source an exact path match rather than a loose substring match, while L tells the current rewrite pass to stop after that rule succeeds. The generator also escapes regular-expression punctuation in the source, so a dot or pair of parentheses in a literal path does not accidentally match unrelated addresses. Review whether your server is configured to read .htaccess files and whether mod_rewrite is enabled; this tool creates directives but cannot inspect or change the Apache installation.
Install and verify the generated block
Copy the htaccess value into the appropriate .htaccess file inside the site scope where the source paths are handled. The result contains only RewriteRule lines, so an existing configuration may already provide RewriteEngine On; add that directive separately if your file does not enable rewriting. Back up the current configuration before editing it, then test the file in a staging environment when possible. Request every old URL and confirm the response status, Location header, final page, and absence of an unexpected redirect chain. Also test a nearby URL that should not redirect, because exact negative checks catch configuration interactions that a successful destination test cannot reveal. Local Apache rules can be affected by directives above or below the inserted block, virtual-host settings, proxy layers, and content-management-system rewrites. For automation, the API returns both an array of individual rules and a newline-joined block. One request costs $0.002; the same deterministic generator can also run in the browser for quick one-off migrations without sending the list elsewhere.
What you can do with it
Launch a redesigned site
Convert the approved old-to-new path map into exact permanent redirect directives before the new information architecture goes live.
Run a temporary campaign
Create consistent 302 rules for short-lived landing-page moves that should not be treated as permanent replacements.
Move selected pages to another domain
Generate rules whose destinations are absolute HTTPS URLs while keeping each source path explicit and reviewable.
FAQ
What does it cost?
An API request costs $0.002. You can also run the generator in your browser on this page.
Does the output include RewriteEngine On?
No. The output contains only RewriteRule lines because many existing .htaccess files already enable the rewrite engine.
Can one request mix 301 and 302 redirects?
No. The selected redirect type applies to every pair in the request. Use separate requests when permanent and temporary rules must be kept apart.
Can a destination point to another domain?
Yes. A destination may be an absolute HTTP or HTTPS URL without embedded credentials, or a root-relative path on the current site.
What happens when a pair is incomplete?
Generation stops with an invalid input error that identifies the pair and names the missing from or to field.
Are query strings supported in source paths?
No. RewriteRule matches paths rather than query strings, so source query matching requires additional RewriteCond directives outside this generator.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/web/htaccess-redirect-generate \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"pairs":[{"from":"/old-page","to":"/new-page"},{"from":"/docs/v1/","to":"https://docs.example.com/v2/"}],"redirect_type":301}'const res = await fetch("https://api.kit.forhosting.com/web/htaccess-redirect-generate", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"pairs": [
{
"from": "/old-page",
"to": "/new-page"
},
{
"from": "/docs/v1/",
"to": "https://docs.example.com/v2/"
}
],
"redirect_type": 301
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/htaccess-redirect-generate",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"pairs": [
{
"from": "/old-page",
"to": "/new-page"
},
{
"from": "/docs/v1/",
"to": "https://docs.example.com/v2/"
}
],
"redirect_type": 301
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/htaccess-redirect-generate", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"pairs":[{"from":"/old-page","to":"/new-page"},{"from":"/docs/v1/","to":"https://docs.example.com/v2/"}],"redirect_type":301}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"pairs":[{"from":"/old-page","to":"/new-page"},{"from":"/docs/v1/","to":"https://docs.example.com/v2/"}],"redirect_type":301}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/htaccess-redirect-generate", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"pairs": [
{
"from": "/old-page",
"to": "/new-page"
},
{
"from": "/docs/v1/",
"to": "https://docs.example.com/v2/"
}
],
"redirect_type": 301
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.htaccess_redirect_generate",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_items | 500 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |