Redirects keep old links and search rankings working when URLs change; rewrites map pretty URLs to internal paths. Nginx has two tools for this — return and rewrite — and choosing the right one keeps configs fast and readable.

Prefer return for redirects

return sends a response immediately — no regex processing, no further rules:

# Whole domain moved
server {
    server_name old-example.com www.old-example.com;
    return 301 https://example.com$request_uri;
}

# One page moved
location = /old-page/ {
    return 301 /new-page/;
}

# Section moved, keep the rest of the path
location ^~ /blog/2019/ {
    return 301 /archive$request_uri;
}
  • 301 — permanent (search engines transfer ranking to the new URL).
  • 302 / 307 — temporary.
  • 308 — permanent and keeps the method (POST stays POST).

See 301 vs 302 redirects.

rewrite: when you need regex captures

rewrite regex replacement [flag];
# /products/123 → /shop/item.php?id=123 (internal, URL in the browser stays the same)
rewrite ^/products/(\d+)/?$ /shop/item.php?id=$1 last;

# Old category structure → new, as a redirect
rewrite ^/category/(.+)$ /topics/$1 permanent;

Flags:

Flag Behaviour
last Stop processing rewrites; search locations again with the new URI
break Stop processing rewrites; continue in the current location
redirect 302 redirect
permanent 301 redirect
(none) Continue with the next rewrite rule

If the replacement starts with http:// or https://, Nginx always sends a redirect.

Query strings: rewrite appends the original query string automatically. To drop it, end the replacement with ?:

rewrite ^/search/(.*)$ /find?q=$1? permanent;

Many redirects: use a map

Hundreds of redirects as individual location or rewrite lines are slow to evaluate and hard to maintain. A map is a fast hash lookup:

# http {} context
map $request_uri $redirect_to {
    default "";
    /old-page/            /new-page/;
    /about-us/            /about/;
    /2020/06/hello/       /blog/hello/;
    ~^/docs/v1/(.*)$      /docs/v2/$1;
}
server {
    if ($redirect_to) {
        return 301 $redirect_to;
    }
    # …
}

You can keep the map in a separate file (include /etc/nginx/redirects.map;) and generate it from a spreadsheet or your CMS. This is exactly how large site migrations handle thousands of old URLs.

Common patterns

# Force trailing slash on directory-style URLs (not files)
rewrite ^([^.]*[^/])$ $1/ permanent;

# Remove trailing slash
rewrite ^/(.*)/$ /$1 permanent;

# Remove .php extension from URLs
location / {
    try_files $uri $uri/ $uri.php?$args;
}
location ~ \.php$ {
    if ($request_uri ~ ^/([^?]*)\.php($|\?)) { return 301 /$1$is_args$args; }
    # … fastcgi config …
}

# Lowercase URLs (needs Perl or Lua module) — usually better handled in the app

For HTTP→HTTPS and www handling, see Nginx HTTPS redirects.

Converting .htaccess rules

Apache’s RewriteRule maps to Nginx’s rewrite fairly directly, but:

  • Apache rules in .htaccess match paths without the leading slash; Nginx matches with it (^/products/).
  • RewriteCond %{REQUEST_FILENAME} !-f / !-d patterns become try_files.
  • [R=301,L] → permanent; [L] → last; [QSA] is Nginx’s default behaviour.

Example:

RewriteRule ^old/(.*)$ /new/$1 [R=301,L]
rewrite ^/old/(.*)$ /new/$1 permanent;

Online converters help, but always review the output.

Avoid “if is evil” problems

Inside location, if only safely supports return and rewrite … last. Don’t put proxy_pass, try_files or other directives inside if blocks — they behave unexpectedly. Use map, separate locations or try_files instead.

Test before going live

sudo nginx -t && sudo systemctl reload nginx
curl -sI https://example.com/old-page/ | grep -iE '^(HTTP|location)'
curl -sIL https://example.com/about-us/ | grep -iE '^(HTTP|location)'   # follow the chain

Watch for chains (A→B→C) and loops. Each hop costs a round trip and dilutes SEO signals — redirect directly to the final URL. Enable rewrite_log on; with error_log … notice; on a test server to see how rules are applied.

Related: Nginx try_files, website migration SEO and Nginx location blocks.

  • Nginx
  • Redirects
  • SEO