Redirects, rewrites and variables

NGINX · Practical · 12 min

Site migrations, canonical domains and clean URLs all need redirects, and return is simpler and safer than rewrite.

return stops processing and sends a response:

  • return 301 https://example.com$request_uri;: permanent redirect (308 also preserves the method and body).
  • return 302 /maintenance.html;: temporary redirect.
  • return 403; or return 200 "ok"; for a direct response.

rewrite regex replacement [flag]; changes the URI using regex captures:

  • With no flag, NGINX continues with the next rewrite and then re-searches locations for the new URI.
  • last: stop rewrites here and search locations again with the new URI.
  • break: stop rewrites and keep processing in the current location.
  • redirect (302) and permanent (301): send a redirect to the client. A replacement starting with http:// or https:// also redirects.

Prefer return whenever the target doesn't need a regex; it is clearer and cheaper.

Common variables: $host (Host header or server name), $request_uri (original URI with query string), $uri (normalized, possibly rewritten, without query string), $args and $arg_name for the query string, $scheme, $remote_addr and $http_<header> for any request header.

The map directive computes one variable from another and is the clean way to handle many redirects: keep a table of old to new paths instead of dozens of rewrites.

Example

map $uri $new_uri {                       # in http context
    /old-pricing   /pricing;
    /blog/2023     /blog/archive/2023;
}

server {
    listen 80;
    server_name www.example.com;
    return 301 https://example.com$request_uri;    # canonical host
}

server {
    listen 80;
    server_name example.com;
    if ($new_uri) { return 301 $new_uri; }         # table-driven redirects
    rewrite ^/users/(\d+)$ /profile?id=$1 last;    # internal rewrite
    location = /profile { return 200 "profile $arg_id\n"; }
}
# GET /users/42 -> profile 42

Remember

  • return for redirects and direct responses; rewrite only when you need regexes
  • last re-searches locations; break stays in the current one
  • $request_uri keeps the query string; $uri doesn't

Watch out for

  • Redirecting with $uri instead of $request_uri and losing the query string.

Check yourself

  1. Which variable keeps the original path and query string for a redirect?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nginx.org/en/docs/http/ngx_http_rewrite_module.html