{"id":13550,"date":"2026-09-02T13:16:52","date_gmt":"2026-09-02T11:16:52","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/redirect-rule-generator-htaccess-nginx\/"},"modified":"2026-09-02T13:16:52","modified_gmt":"2026-09-02T11:16:52","slug":"redirect-rule-generator-htaccess-nginx","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/","title":{"rendered":"301 Redirect Rule Generator (.htaccess, nginx, WordPress)"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">Paste old and new addresses, one pair per line, separated by an arrow, a tab or a comma. Everything runs in the browser. Pairs that share the same folder change collapse into one prefix rule with a wildcard, the way a renamed category needs it. A new address that is itself an old address makes a chain; the generator points the first at the last so that no visitor walks the chain. Loops and contradicting targets are reported and left out.<\/p>\n<div style=\"margin-bottom: 14px;\">\n        <label for=\"rg-paare\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Old address -&gt; new address, one pair per line<\/label><br \/>\n        <textarea id=\"rg-paare\" style=\"width: 100%; height: 190px; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace; font-size: 0.85rem; resize: vertical;\">\/blog\/en\/wordpress-hacks\/ -> \/blog\/en\/wordpress-plugins-tricks\/<br \/>\n\/blog\/en\/wordpress-hacks\/speed-up\/ -> \/blog\/en\/wordpress-plugins-tricks\/speed-up\/<br \/>\n\/alte-seite.html -> \/neue-seite\/<br \/>\n\/produkt\/alt -> \/produkt\/neu<br \/>\n\/produkt\/neu -> \/shop\/produkt-neu\/<br \/>\nhttps:\/\/old.example\/archive\/2019\/ -> https:\/\/www.example.com\/blog\/<\/textarea>\n    <\/div>\n<div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px 18px; margin-bottom: 14px;\">\n<div>\n            <label for=\"rg-format\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Output<\/label><br \/>\n            <select id=\"rg-format\" class=\"form-control\" style=\"width: 100%; padding: 9px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px;\"><option value=\"htaccess\">Apache .htaccess (RewriteRule)<\/option><option value=\"redirect\">Apache .htaccess (Redirect \/ RedirectMatch)<\/option><option value=\"nginx\">nginx (location \/ rewrite)<\/option><option value=\"nginx_map\">nginx (map + return)<\/option><option value=\"wordpress\">WordPress (functions.php, template_redirect)<\/option><\/select>\n        <\/div>\n<div>\n            <label for=\"rg-code\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Status code<\/label><br \/>\n            <select id=\"rg-code\" class=\"form-control\" style=\"width: 100%; padding: 9px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px;\"><option value=\"301\">301<\/option><option value=\"308\">308<\/option><option value=\"302\">302<\/option><option value=\"307\">307<\/option><\/select>\n        <\/div>\n<div style=\"display: flex; flex-direction: column; gap: 6px; justify-content: flex-end;\">\n            <label style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.85rem;\"><input id=\"rg-qsa\" type=\"checkbox\" checked style=\"margin-right: 6px;\">Pass the query string through<\/label><br \/>\n            <label style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.85rem;\"><input id=\"rg-praefix\" type=\"checkbox\" checked style=\"margin-right: 6px;\">Merge folder changes into prefix rules<\/label><br \/>\n            <label style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.85rem;\"><input id=\"rg-glaetten\" type=\"checkbox\" checked style=\"margin-right: 6px;\">Flatten chains to the final target<\/label>\n        <\/div>\n<\/p><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.82rem; margin-bottom: 16px;\">Nothing entered here is sent anywhere. The rules are built in the browser.<\/p>\n<p>    <button id=\"rg-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Generate the rules<\/button><\/p>\n<div id=\"rg-ausgabe\" style=\"margin-top: 22px;\" data-mp-mask><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.8rem; margin: 24px 0 0;\">Limits worth knowing: prefix rules match everything below the old folder, including addresses that were never listed; a query-string pair becomes a RewriteCond on Apache and an if on nginx, which is deliberate but not elegant; and a rule that redirects to a host on which the same rules apply again will loop, which only a trace of the live chain shows.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"Paste old and new addresses, one pair per line, separated by an arrow, a tab or a comma. Everything runs in the browser. Pairs that share the same folder change collapse into one prefix rule with a wildcard, the way a renamed category needs it. A new address that is itself an old address makes a chain; the generator points the first at the last so that no visitor walks the chain. Loops and contradicting targets are reported and left out.\",\"label_paare\":\"Old address -\\u003E new address, one pair per line\",\"label_format\":\"Output\",\"label_code\":\"Status code\",\"label_qsa\":\"Pass the query string through\",\"label_praefix\":\"Merge folder changes into prefix rules\",\"label_glaetten\":\"Flatten chains to the final target\",\"datenschutz\":\"Nothing entered here is sent anywhere. The rules are built in the browser.\",\"knopf\":\"Generate the rules\",\"grenze\":\"Limits worth knowing: prefix rules match everything below the old folder, including addresses that were never listed; a query-string pair becomes a RewriteCond on Apache and an if on nginx, which is deliberate but not elegant; and a rule that redirects to a host on which the same rules apply again will loop, which only a trace of the live chain shows.\",\"zusammenfassung\":\"{gelesen} pair(s) read, {paare} usable, {praefixe} prefix rule(s), {einzeln} single rule(s).\",\"h_fehler\":\"Lines that could not be read\",\"h_hinweise\":\"Checks\",\"h_praefixe\":\"Prefix rules\",\"h_ausgabe\":\"Rules\",\"h_ausgabe_htaccess\":\"Apache .htaccess (mod_rewrite)\",\"h_ausgabe_redirect\":\"Apache .htaccess (mod_alias)\",\"h_ausgabe_nginx\":\"nginx server block\",\"h_ausgabe_nginx_map\":\"nginx map\",\"h_ausgabe_wordpress\":\"WordPress functions.php\",\"kopieren\":\"Copy\",\"kopiert\":\"Copied\",\"kommentar_kopf\":\"Generated by the 301 Redirect Rule Generator, lukaswojcik.com\",\"kommentar_query\":\"Query-string match not possible with mod_alias, use the RewriteRule format for:\",\"fehler_form\":\"Line {zeile}: not a pair of addresses ({roh}).\",\"fehler_host\":\"Line {zeile}: the host part is unusable ({roh}).\",\"hinweis_doppelt\":\"Line {zeile}: {alt} appears twice with the same target, the repeat was dropped.\",\"hinweis_widerspruch\":\"Line {zeile}: {alt} already points to {a}, this line wants {b}. The second target was dropped.\",\"hinweis_selbst\":\"Line {zeile}: {alt} points to itself and was dropped.\",\"hinweis_kette\":\"Line {zeile}: chain {kette}. Enable flattening or point the first address at the last one.\",\"hinweis_geglaettet\":\"Line {zeile}: chain {kette} flattened, {alt} now points at {ziel}.\",\"hinweis_schleife\":\"Line {zeile}: loop {kette}. All addresses in the loop were dropped; the server would answer ERR_TOO_MANY_REDIRECTS.\",\"hinweis_format_htaccess\":\"Belongs above the WordPress block in the .htaccess (everything between BEGIN and END WordPress is overwritten on permalink save). Patterns are matched without the leading slash and relative to the folder of the .htaccess; QSA passes the query string through, NE keeps encoded characters. Test with curl -I before relying on it.\",\"hinweis_format_redirect\":\"mod_alias runs before mod_rewrite and cannot look at the query string. Redirect matches the path and everything below it, which is why single pages use the exact path; RedirectMatch takes a regular expression.\",\"hinweis_format_nginx\":\"Goes into the server block. location = matches one path exactly, location ^~ a prefix. permanent is 301, redirect 302; other codes use return. Reload the configuration after nginx -t.\",\"hinweis_format_nginx_map\":\"The map goes into the http block, the if into the server block. One lookup per request, no chain of location blocks; regular expressions in the map start with ~.\",\"hinweis_format_wordpress\":\"Runs on template_redirect, so only for requests that reach WordPress; a page cache serves cached pages before this code runs. Exact matches come first, prefixes second. wp_redirect needs the exit.\"};<\/p>\n<p>    \/\/ =========================================================================\n    \/\/ Reine Logik: parsen, pruefen, glaetten, Praefixe, erzeugen\n    \/\/ =========================================================================<\/p>\n<p>    function trennen(zeile) {\n        var z = zeile.trim();\n        if (!z || z.charAt(0) === '#') { return null; }\n        var m = z.match(\/^(\\S+)\\s*(?:->|=>|\u2192|\\t|,|;|\\s+)\\s*(\\S+)$\/);\n        if (!m) { return { fehler: 'form', roh: z }; }\n        return { alt: m[1], neu: m[2] };\n    }\n    function zerlegen(adresse) {\n        var m = adresse.match(\/^(https?:\\\/\\\/[^\\\/?#]+)?([^?#]*)(\\?[^#]*)?(#.*)?$\/i);\n        if (!m) { return null; }\n        var pfad = m[2] || '\/';\n        if (pfad.charAt(0) !== '\/') { pfad = '\/' + pfad; }\n        return { host: m[1] ? m[1].toLowerCase() : '', pfad: pfad, query: m[3] || '', voll: (m[1] ? m[1].toLowerCase() : '') + pfad + (m[3] || '') };\n    }\n    function parsen(text) {\n        var paare = [], fehler = [];\n        String(text || '').split(\/\\r?\\n\/).forEach(function (zeile, i) {\n            var t = trennen(zeile);\n            if (t === null) { return; }\n            if (t.fehler) { fehler.push({ zeile: i + 1, art: 'form', roh: t.roh }); return; }\n            var a = zerlegen(t.alt), n = zerlegen(t.neu);\n            if (!a || !n) { fehler.push({ zeile: i + 1, art: 'form', roh: zeile.trim() }); return; }\n            if (a.host && !\/^https?:\\\/\\\/[a-z0-9.-]+(:\\d+)?$\/i.test(a.host)) { fehler.push({ zeile: i + 1, art: 'host', roh: t.alt }); return; }\n            paare.push({ zeile: i + 1, alt: a, neu: n, altRoh: t.alt, neuRoh: t.neu });\n        });\n        return { paare: paare, fehler: fehler };\n    }<\/p>\n<p>    \/** Doppelte, Selbstverweise, Ketten und Schleifen finden; Ketten optional glaetten. *\/\n    function pruefen(paare, glaetten) {\n        var hinweise = [];\n        var nachAlt = {};\n        var raus = [];\n        paare.forEach(function (p) {\n            var k = p.alt.voll;\n            if (nachAlt[k]) {\n                if (nachAlt[k].neu.voll === p.neu.voll) { hinweise.push({ art: 'doppelt', zeile: p.zeile, alt: p.altRoh }); }\n                else { hinweise.push({ art: 'widerspruch', zeile: p.zeile, alt: p.altRoh, a: nachAlt[k].neuRoh, b: p.neuRoh }); }\n                return;\n            }\n            if (p.alt.voll === p.neu.voll) { hinweise.push({ art: 'selbst', zeile: p.zeile, alt: p.altRoh }); return; }\n            nachAlt[k] = p;\n            raus.push(p);\n        });\n        \/\/ Ketten: neu ist wieder ein alt\n        raus.forEach(function (p) {\n            var ziel = p.neu, schritte = 0, gesehen = {};\n            gesehen[p.alt.voll] = true;\n            var kette = [p.altRoh];\n            while (nachAlt[ziel.voll] && schritte < 20) {\n                if (gesehen[ziel.voll]) { hinweise.push({ art: 'schleife', zeile: p.zeile, alt: p.altRoh, kette: kette.concat([ziel.voll]).join(' -> ') }); p.schleife = true; return; }\n                gesehen[ziel.voll] = true;\n                kette.push(ziel.voll);\n                ziel = nachAlt[ziel.voll].neu;\n                schritte++;\n            }\n            if (schritte > 0) {\n                kette.push(ziel.voll);\n                if (glaetten) { hinweise.push({ art: 'geglaettet', zeile: p.zeile, alt: p.altRoh, kette: kette.join(' -> '), ziel: ziel.voll }); p.neu = ziel; p.neuRoh = ziel.voll; }\n                else { hinweise.push({ art: 'kette', zeile: p.zeile, alt: p.altRoh, kette: kette.join(' -> ') }); }\n            }\n        });\n        return { paare: raus.filter(function (p) { return !p.schleife; }), hinweise: hinweise };\n    }<\/p>\n<p>    \/** Paare, die denselben Ordnerwechsel teilen, zu einer Praefixregel zusammenfassen. *\/\n    function praefixe(paare) {\n        var gruppen = {}, einzeln = [];\n        paare.forEach(function (p) {\n            if (p.alt.host !== p.neu.host && p.neu.host && p.alt.host) { einzeln.push(p); return; }\n            var a = p.alt.pfad.split('\/'), n = p.neu.pfad.split('\/');\n            \/\/ gemeinsamer Rest von hinten\n            var rest = 0;\n            while (rest < a.length - 1 &#038;&#038; rest < n.length - 1 &#038;&#038; a[a.length - 1 - rest] === n[n.length - 1 - rest]) { rest++; }\n            if (rest === 0 || p.alt.query || p.neu.query) { einzeln.push(p); return; }\n            var altPraefix = a.slice(0, a.length - rest).join('\/') + '\/';\n            var neuPraefix = n.slice(0, n.length - rest).join('\/') + '\/';\n            if (altPraefix === '\/' || altPraefix === neuPraefix) { einzeln.push(p); return; }\n            var k = (p.alt.host || '') + altPraefix + ' => ' + (p.neu.host || '') + neuPraefix;\n            if (!gruppen[k]) { gruppen[k] = { altHost: p.alt.host, alt: altPraefix, neuHost: p.neu.host, neu: neuPraefix, paare: [] }; }\n            gruppen[k].paare.push(p);\n        });\n        var regeln = [];\n        Object.keys(gruppen).forEach(function (k) {\n            var g = gruppen[k];\n            if (g.paare.length >= 2) { regeln.push(g); } else { einzeln.push(g.paare[0]); }\n        });\n        einzeln.sort(function (x, y) { return x.zeile - y.zeile; });\n        return { praefixe: regeln, einzeln: einzeln };\n    }<\/p>\n<p>    function reEsc(s) { return s.replace(\/[.*+?^${}()|[\\]\\\\]\/g, '\\\\$&'); }\n    function ohneSlash(p) { return p.replace(\/^\\\/\/, ''); }<\/p>\n<p>    function erzeugen(paare, praefixRegeln, format, code, qsa) {\n        var zeilen = [];\n        var flags = function (extra) { var f = ['L', 'R=' + code, 'NE']; if (qsa) { f.push('QSA'); } if (extra) { f = f.concat(extra); } return '[' + f.join(',') + ']'; };\n        var nginxCode = function () { return String(code); };\n        if (format === 'htaccess') {\n            zeilen.push('# ' + T.kommentar_kopf);\n            zeilen.push('RewriteEngine On');\n            praefixRegeln.forEach(function (g) {\n                var ziel = (g.neuHost || '') + g.neu + '$1';\n                zeilen.push('RewriteRule ^' + reEsc(ohneSlash(g.alt)) + '(.*)$ ' + ziel + ' ' + flags());\n            });\n            paare.forEach(function (p) {\n                var ziel = (p.neu.host || '') + p.neu.pfad + (p.neu.query || '');\n                if (p.alt.query) {\n                    zeilen.push('RewriteCond %{QUERY_STRING} ^' + reEsc(p.alt.query.slice(1)) + '$');\n                    zeilen.push('RewriteRule ^' + reEsc(ohneSlash(p.alt.pfad)) + '\/?$ ' + ziel + (p.neu.query ? '' : '?') + ' ' + flags().replace(',QSA', ''));\n                } else {\n                    zeilen.push('RewriteRule ^' + reEsc(ohneSlash(p.alt.pfad)) + '\/?$ ' + ziel + ' ' + flags());\n                }\n            });\n        } else if (format === 'redirect') {\n            zeilen.push('# ' + T.kommentar_kopf);\n            praefixRegeln.forEach(function (g) {\n                zeilen.push('RedirectMatch ' + code + ' ^' + reEsc(g.alt) + '(.*)$ ' + (g.neuHost || '') + g.neu + '$1');\n            });\n            paare.forEach(function (p) {\n                if (p.alt.query) { zeilen.push('# ' + T.kommentar_query + ' ' + p.altRoh); return; }\n                zeilen.push('Redirect ' + code + ' ' + p.alt.pfad + ' ' + (p.neu.host || '') + p.neu.pfad + (p.neu.query || ''));\n            });\n        } else if (format === 'nginx') {\n            zeilen.push('# ' + T.kommentar_kopf);\n            praefixRegeln.forEach(function (g) {\n                zeilen.push('location ^~ ' + g.alt + ' {');\n                zeilen.push('    rewrite ^' + reEsc(g.alt) + '(.*)$ ' + (g.neuHost || '$scheme:\/\/$host') + g.neu + '$1 ' + (code === '301' ? 'permanent' : 'redirect') + ';');\n                zeilen.push('}');\n            });\n            paare.forEach(function (p) {\n                var ziel = (p.neu.host || '$scheme:\/\/$host') + p.neu.pfad + (p.neu.query ? p.neu.query : (qsa ? '$is_args$args' : ''));\n                if (p.alt.query) {\n                    zeilen.push('if ($request_uri = \"' + p.alt.pfad + p.alt.query + '\") { return ' + nginxCode() + ' ' + (p.neu.host || '$scheme:\/\/$host') + p.neu.pfad + (p.neu.query || '') + '; }');\n                } else {\n                    zeilen.push('location = ' + p.alt.pfad + ' { return ' + nginxCode() + ' ' + ziel + '; }');\n                }\n            });\n        } else if (format === 'nginx_map') {\n            zeilen.push('# ' + T.kommentar_kopf);\n            zeilen.push('# http { ... }');\n            zeilen.push('map $request_uri $lw_redirect {');\n            zeilen.push('    default \"\";');\n            praefixRegeln.forEach(function (g) {\n                zeilen.push('    ~^' + reEsc(g.alt) + '(?<rest>.*)$ ' + (g.neuHost || '') + g.neu + '$rest;');\n            });\n            paare.forEach(function (p) {\n                zeilen.push('    ' + (p.alt.query ? '' : '') + p.alt.pfad + (p.alt.query || '') + ' ' + (p.neu.host || '') + p.neu.pfad + (p.neu.query || '') + ';');\n            });\n            zeilen.push('}');\n            zeilen.push('# server { ... }');\n            zeilen.push('if ($lw_redirect) { return ' + nginxCode() + ' $lw_redirect; }');\n        } else if (format === 'wordpress') {\n            zeilen.push('<?php');\n            zeilen.push('\/\/ ' + T.kommentar_kopf);\n            zeilen.push(\"add_action( 'template_redirect', function () {\");\n            zeilen.push(\"    $pfad = rtrim( (string) parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ), '\/' );\");\n            zeilen.push(\"    $query = (string) parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY );\");\n            zeilen.push('    $praefixe = array(');\n            praefixRegeln.forEach(function (g) { zeilen.push(\"        '\" + g.alt + \"' => '\" + (g.neuHost || '') + g.neu + \"',\"); });\n            zeilen.push('    );');\n            zeilen.push('    $einzeln = array(');\n            paare.forEach(function (p) { zeilen.push(\"        '\" + p.alt.pfad.replace(\/\\\/$\/, '') + (p.alt.query || '') + \"' => '\" + (p.neu.host || '') + p.neu.pfad + (p.neu.query || '') + \"',\"); });\n            zeilen.push('    );');\n            zeilen.push(\"    $schluessel = $pfad === '' ? '\/' : $pfad;\");\n            zeilen.push(\"    if ( isset( $einzeln[ $schluessel . ( $query !== '' ? '?' . $query : '' ) ] ) ) { $ziel = $einzeln[ $schluessel . ( $query !== '' ? '?' . $query : '' ) ]; }\");\n            zeilen.push(\"    elseif ( isset( $einzeln[ $schluessel ] ) ) { $ziel = $einzeln[ $schluessel ]\" + (qsa ? \" . ( $query !== '' ? '?' . $query : '' )\" : '') + '; }');\n            zeilen.push('    else {');\n            zeilen.push('        foreach ( $praefixe as $alt => $neu ) {');\n            zeilen.push(\"            if ( strpos( $pfad . '\/', $alt ) === 0 ) { $ziel = $neu . substr( $pfad . '\/', strlen( $alt ) )\" + (qsa ? \" . ( $query !== '' ? '?' . $query : '' )\" : '') + '; break; }');\n            zeilen.push('        }');\n            zeilen.push('    }');\n            zeilen.push('    if ( ! empty( $ziel ) ) { wp_redirect( $ziel, ' + code + \" ); exit; }\");\n            zeilen.push('} );');\n        }\n        return zeilen.join('\\n');\n    }<\/p>\n<p>    function bauen(text, optionen) {\n        var o = optionen || {};\n        var p = parsen(text);\n        var g = pruefen(p.paare, o.glaetten !== false);\n        var pr = o.praefix !== false ? praefixe(g.paare) : { praefixe: [], einzeln: g.paare.slice() };\n        var ausgabe = erzeugen(pr.einzeln, pr.praefixe, o.format || 'htaccess', String(o.code || '301'), o.qsa !== false);\n        return { fehler: p.fehler, hinweise: g.hinweise, paare: g.paare.length, praefixe: pr.praefixe, einzeln: pr.einzeln.length, ausgabe: ausgabe, format: o.format || 'htaccess', gelesen: p.paare.length };\n    }<\/p>\n<p>    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.RG = { parsen: parsen, pruefen: pruefen, praefixe: praefixe, erzeugen: erzeugen, bauen: bauen, darstellen: null, T: T };<\/p>\n<p>    \/\/ =========================================================================\n    \/\/ Oberflaeche\n    \/\/ =========================================================================<\/p>\n<p>    var btn = document.getElementById('rg-btn');\n    var out = document.getElementById('rg-ausgabe');\n    if (!btn || !out) { return; }\n    function el(tag, stil, txt) { var e = document.createElement(tag); if (stil) { e.setAttribute('style', stil); } if (txt !== undefined && txt !== null) { e.textContent = txt; } return e; }\n    function wert(id) { var e = document.getElementById(id); return e ? e.value : ''; }\n    function haken(id) { var e = document.getElementById(id); return e ? e.checked : true; }\n    var UEBERSCHRIFT = 'font-family: \"Nunito Sans\", sans-serif; font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 22px 0 8px;';\n    var ZELLE = 'padding: 5px 12px 5px 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem;';\n    var VORFORM = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 6px; padding: 12px; color: var(--text-primary, #e8e8ee); font-family: monospace; font-size: 0.8rem; white-space: pre; overflow-x: auto; margin: 0;';\n    var GUT = ' color: #7ee787;', WARN = ' color: #ffa94d;', ROT = ' color: #ff7b72;', GRAU = ' color: #8a8a99;';\n    function text(k, d) { var t = T[k]; if (typeof t !== 'string') { return k; } return t.replace(\/\\{([a-z_]+)\\}\/g, function (m, x) { return d && d[x] !== undefined ? String(d[x]) : m; }); }<\/p>\n<p>    function darstellen(b, ziel) {\n        ziel.innerHTML = '';\n        ziel.appendChild(el('div', ZELLE + ' margin: 0 0 6px;' + (b.paare ? GRAU : WARN), text('zusammenfassung', { gelesen: b.gelesen, paare: b.paare, praefixe: b.praefixe.length, einzeln: b.einzeln })));\n        if (b.fehler.length) {\n            ziel.appendChild(el('div', UEBERSCHRIFT, T.h_fehler));\n            var lf = el('ul', 'margin: 0 0 0 18px; padding: 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem; line-height: 1.6;');\n            b.fehler.forEach(function (f) { lf.appendChild(el('li', ROT, text('fehler_' + f.art, { zeile: f.zeile, roh: f.roh }))); });\n            ziel.appendChild(lf);\n        }\n        if (b.hinweise.length) {\n            ziel.appendChild(el('div', UEBERSCHRIFT, T.h_hinweise));\n            var lh = el('ul', 'margin: 0 0 0 18px; padding: 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem; line-height: 1.6;');\n            b.hinweise.forEach(function (h) {\n                var stil = (h.art === 'schleife' || h.art === 'widerspruch') ? ROT : (h.art === 'geglaettet' ? GUT : WARN);\n                lh.appendChild(el('li', stil, text('hinweis_' + h.art, h)));\n            });\n            ziel.appendChild(lh);\n        }\n        if (b.praefixe.length) {\n            ziel.appendChild(el('div', UEBERSCHRIFT, T.h_praefixe));\n            var lp = el('ul', 'margin: 0 0 0 18px; padding: 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem; font-family: monospace; line-height: 1.6;');\n            b.praefixe.forEach(function (g) { lp.appendChild(el('li', '', (g.altHost || '') + g.alt + ' -> ' + (g.neuHost || '') + g.neu + '  (' + g.paare.length + ')')); });\n            ziel.appendChild(lp);\n        }\n        ziel.appendChild(el('div', UEBERSCHRIFT, T['h_ausgabe_' + b.format] || T.h_ausgabe));\n        var pre = el('pre', VORFORM, b.ausgabe);\n        ziel.appendChild(pre);\n        var kopieren = el('button', 'margin-top: 8px; background: transparent; color: var(--accent, #7ee787); border: 1px solid var(--accent, #7ee787); padding: 7px 14px; border-radius: 6px; font-weight: 700; cursor: pointer; font-size: 0.82rem;', T.kopieren);\n        kopieren.className = 'button rg-kopieren';\n        kopieren.addEventListener('click', function () {\n            if (navigator.clipboard) { navigator.clipboard.writeText(b.ausgabe).then(function () { kopieren.textContent = T.kopiert; setTimeout(function () { kopieren.textContent = T.kopieren; }, 1500); }); }\n        });\n        ziel.appendChild(kopieren);\n        ziel.appendChild(el('p', ZELLE + GRAU + ' margin: 12px 0 0; line-height: 1.55;', T['hinweis_format_' + b.format] || ''));\n    }\n    window.LW_TEST.RG.darstellen = darstellen;<\/p>\n<p>    btn.addEventListener('click', function () {\n        darstellen(bauen(wert('rg-paare'), { format: wert('rg-format'), code: wert('rg-code'), qsa: haken('rg-qsa'), praefix: haken('rg-praefix'), glaetten: haken('rg-glaetten') }), out);\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Turns a list of old and new addresses into redirect rules for Apache .htaccess, nginx or a WordPress hook. Merges pairs that share a folder change into one prefix rule, flattens chains so every old address points at its final target, and refuses loops and contradictions before they reach the server.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[91144,91094,91099,91096],"class_list":["post-13550","page","type-page","status-publish","hentry","tag-devops","tag-seo","tag-server-administration","tag-wordpress"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>301 Redirect Rule Generator (.htaccess, nginx, WordPress) - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"301 Redirect Rule Generator (.htaccess, nginx, WordPress) - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Turns a list of old and new addresses into redirect rules for Apache .htaccess, nginx or a WordPress hook. Merges pairs that share a folder change into one prefix rule, flattens chains so every old address points at its final target, and refuses loops and contradictions before they reach the server.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/redirect-rule-generator-htaccess-nginx\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/redirect-rule-generator-htaccess-nginx\\\/\",\"name\":\"301 Redirect Rule Generator (.htaccess, nginx, WordPress) - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-02T11:16:52+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/redirect-rule-generator-htaccess-nginx\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/redirect-rule-generator-htaccess-nginx\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/redirect-rule-generator-htaccess-nginx\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"301 Redirect Rule Generator (.htaccess, nginx, WordPress)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"301 Redirect Rule Generator (.htaccess, nginx, WordPress) - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/","og_locale":"en_US","og_type":"article","og_title":"301 Redirect Rule Generator (.htaccess, nginx, WordPress) - Lukas Wojcik - Blog","og_description":"Turns a list of old and new addresses into redirect rules for Apache .htaccess, nginx or a WordPress hook. Merges pairs that share a folder change into one prefix rule, flattens chains so every old address points at its final target, and refuses loops and contradictions before they reach the server.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/","og_site_name":"Lukas Wojcik - Blog","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/","name":"301 Redirect Rule Generator (.htaccess, nginx, WordPress) - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-02T11:16:52+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/redirect-rule-generator-htaccess-nginx\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"301 Redirect Rule Generator (.htaccess, nginx, WordPress)"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/13550","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=13550"}],"version-history":[{"count":0,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/13550\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=13550"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=13550"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}