🍱 Lunchbox Hands

Nginx Redirect Generator

Build nginx 301/302 redirects, www and HTTPS rules, and rewrite maps

Presets

Used for server_name and the canonical host. With or without www — the www section decides which wins.

Adds a port-80 server block that answers every request with a redirect to https://, keeping the full path and query string via $request_uri.

Status code (HTTPS and www)

Exact matches one URL (location =). Folder moves everything under a prefix and keeps the rest of the path: /old/a/b → /new/a/b.

nginx config
# nginx redirects — generated by lunchboxhands.com/tools/nginx-redirect-generator
# Test before reloading:  sudo nginx -t && sudo systemctl reload nginx

# ---- HTTP -> HTTPS ----
server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

# ---- Your site ----
# Already have this server block? Paste only the lines between the >>> / <<< markers into it.
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;  # update path
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;    # update path
    server_name example.com;

    # >>> redirects
    location = /old-page {
        return 301 /new-page$is_args$args;
    }
    # <<< redirects

    # ... your existing root / location / proxy_pass config ...
}

return vs rewrite

return 301 URL stops immediately and can send any code, but appends nothing — this tool adds $is_args$args to keep the query string. rewrite … permanent matches a regex and keeps the query string automatically, but can only send 301 or 302, so 307/308 rules use return.

$request_uri vs $uri

$request_uri is the original request target — path and query string, still percent-encoded — so host-level redirects that append it lose nothing. $uri is the decoded, normalized path without the query, and is what location blocks match.

Build nginx redirects without guessing at syntax. Force HTTPS, pick www or non-www, move single pages or whole folders, write regex redirects with captures, point an old domain at a new one, and turn a pasted list of hundreds of URLs into a single map block. The generator picks the right construct for each job: location = for exact pages, location ^~ with rewrite for folders, and return with $request_uri for host-level redirects. It flags loops, redirect chains, duplicate locations, and query strings that a location block can never match.

Everything is generated in your browser, and nothing you type is sent anywhere. On Apache, the .htaccess generator builds the equivalent RewriteRule rules. After you reload nginx, run each old URL through the redirect checker to confirm the status code and final destination. Test regex patterns against real paths first in the regex tester.

Frequently asked questions

Should I use return or rewrite for nginx redirects?

Use return whenever you can. "return 301 /new;" ends the request immediately, needs no regular expression, and accepts any status code, including 307 and 308. Its one catch is that it appends nothing, so to keep the query string you add $is_args$args yourself, which this generator does. rewrite is for pattern-based moves where you need captures like $1. With the permanent (301) or redirect (302) flag it sends a redirect and carries the original query string over automatically. Put a trailing "?" in the replacement to drop it. rewrite cannot send 307 or 308, so for those codes the generator uses return with a regex capture.

Why does the HTTPS redirect use $request_uri instead of $uri?

$request_uri is the original request target exactly as the browser sent it: the path plus the query string, still percent-encoded. Appending it to https://$host gives a lossless redirect, so /search?q=a%20b arrives intact. $uri is the path after nginx has decoded and normalized it, with no query string. It is what location blocks match against, but building a redirect from it would drop ?q=… and could produce unencoded characters in the Location header. The rule of thumb is to use $request_uri when you forward a whole URL to another host, and $uri, or regex captures, when you are matching or rewriting paths.

In what order does nginx evaluate these redirects?

Directives at the server level (the bulk-map "if", regex rewrites, and the trailing-slash rule) run first, top to bottom, before nginx picks a location. Then locations are chosen. An exact "location = /path" match wins immediately. Otherwise nginx finds the longest matching prefix, and if that prefix is marked ^~ (which the folder redirects use), regex locations are skipped. Without ^~, nginx checks regex locations in the order they appear, and the first match beats the prefix. So a folder redirect written as "location ^~ /old/" still works even if you have a "location ~ \.php$" further down. Separate server blocks for http, www, and old domains are chosen by port and Host header before any of this happens.

Where does the map block go, and why can’t it go inside server?

nginx only allows map in the http {} context, so pasting it inside server {} makes nginx -t fail with: "map" directive is not allowed here. Put it in nginx.conf inside the http block, or in a file that the http block includes (most distributions include /etc/nginx/conf.d/*.conf there). The "if ($redirect_target) { return 301 $redirect_target; }" part goes inside your server block. Map keys are matched case-insensitively, and a duplicate key is a fatal error, so the generator flags keys that differ only by case. With thousands of long URLs you may also need map_hash_bucket_size or map_hash_max_size, and the output adds a comment when that's likely.

Which status code should I pick: 301, 302, 307, or 308?

For a permanent move, like a new URL structure, HTTPS, or a www change, use 301 or 308. Both tell search engines to move ranking signals to the new URL. For something temporary, like maintenance or an A/B test, use 302 or 307, because search engines keep the old URL indexed. The difference inside each pair is the request method. With 301 and 302, browsers may turn a POST into a GET when they follow the redirect. 307 and 308 require the same method and body to be sent again, which matters for form posts and API endpoints. For ordinary page redirects, 301 is the most widely supported choice.

Why doesn’t my redirect for /page?id=5 work in a location block?

location blocks never see the query string. They match the decoded path only, so "location = /page?id=5" can never match. When old URLs differ only by their query string, use the bulk map set to match $request_uri. Its keys include the query, so /page?id=5 and /page?id=6 can go to different places. Keep in mind that $request_uri matching is exact. /page?id=5&utm_source=x is a different key and falls through. Also, nothing after "#" is ever sent to the server, so fragments cannot be redirected by nginx at all. Handle those with JavaScript on the page.

Get weekly dev tools and tips