Contact Us

Redirects in Angie: return, rewrite and When to Use Each

Angie offers a broad set of request redirection capabilities, and most web applications need them. This article covers the main redirect methods, from simple to complex. Start with the general rule for how directives are processed in the rewrite module.

Video version

A video version of this article is available on YouTube.

Directive processing order

As the rewrite module documentation says, redirect directives (if, set, break, return, rewrite) are processed in sequence within the chosen server block first. After that a location is matched against the request URI and the directives in that location run. If the directives inside a location change the URI and cause another location to be searched for, the search-and-execute cycle repeats up to 10 times.

Redirect behavior can be controlled from here — for example, redirect processing can be stopped after a given rewrite.

The return directive

The simplest way to send a response to the client is the return directive. The first option is a stub: a fixed response code and text. For example:

return 200 'OK text, test location 1';

The directive can be placed in a server, location or if block. In this form return lets you define test server or location blocks and answer bots under certain conditions (using an if block). An example with a variable-based condition:

http {
map $http_user_agent $limit_bots {
    default 0;
    ~*(google|bing|yandex|msnbot) 1;
}
server {
  if ($limit_bots = 1) {
            return 404;
  }
}

This defines the $limit_bots variable from the value of $http_user_agent. The variable is then used in the server block to conditionally return 404. If a bot arrives with certain strings in its User-Agent header, Angie returns 404 to every request. Angie also has a special 444 response code that closes the connection immediately without sending a response (useful when dealing with bots).

The second use is redirects. Here you specify a 3xx code (such as 301 or 302) and the URL the user should be sent to. A typical example: redirecting the user from the HTTP version to HTTPS on the wanted domain while preserving the URI:

server {
  listen 80;
  server_name example.com www.example.com;
  return 301 https://example.com$request_uri;
}
server {
  listen 443 ssl;
  server_name example.com;
}

The difference between the 301 and 302 response codes deserves a closer look. A 301 has the text description Moved Permanently, which means a permanent redirect. A client that receives this code may cache it for a long time and stop sending requests to the original URL. That is what regular browsers do, and search bots treat a 301 response as a sign that the resource has a new permanent address.

A 302 (Found) is less absolute: it indicates a temporary redirect, implying that the destination address may change later. It is the safer way to redirect when you need to test a configuration but are not sure it works correctly.

The return directive covers a wide range of tasks, but more flexible redirects use the rewrite directive, which is based on regular expressions.

The rewrite directive

Full-featured redirects are implemented with the rewrite directive. Unlike return, rewrite requires a regular expression matched against the request URI (GET parameters of the request are ignored), and besides sending 3xx codes it can perform internal redirects. The basic syntax of this directive includes a regular expression (regex), a target URL, and an optional flag modifier. Regular expressions use PCRE syntax (Perl compatible regular expressions).

Internal redirects are needed to convert addresses into the form the application requires. Say you want human-readable URLs for catalog sections — /catalog/cat1/, /catalog/cat2/ and so on — while the application requires addresses like /catalog?cat=1. Write a rewrite directive to convert the addresses:

server {
    rewrite ^/catalog/cat(\d+)/$ /catalog?cat=$1 last;
}

This example uses a regex that matches the URI against a pattern, with start (^) and end ($) anchors and a capturing group (\d+) that puts that part into the $1 variable, which is then used in the replacement URI. Regexes can also be wrapped in quotes to avoid configuration parsing problems (for example, when they contain a } or ; character). The last flag orders an internal redirect, ends rewrite-module processing at this level, and finds a location to handle the request. The client then gets a full response to the request right away instead of a 301 or 302 redirect. If internal redirects are used inside a location, the break flag can help: it stops redirect processing and leaves the request being handled in the current location. For the example above it looks like this:

server {
    location /catalog/ {
        rewrite ^/catalog/cat(\d+)/$ /catalog?cat=$1 break;
    }
}

Ordinary redirects can also be implemented with the rewrite directive:

server {
  rewrite ^/result/([a-zA-Z0-9_]+)$ /result/$1/ permanent;
}

Here the client gets a 301 response with a Location header containing the new address. That header can be inspected in the browser's developer tools (the Network tab), or with curl ‑-head against the original URL. The redirect as a whole can be checked in the browser or with curl ‑L. With the redirect flag the response code is 302.

All the examples above redirected the request based on the URI without taking GET parameters into account. If that task does come up, there are several non-trivial solutions, because a plain rewrite is not enough. The first is based on an if block (a reminder: if blocks are recommended only together with directives from the rewrite module).

location /index/ {
  if ($args ~ "^t1=(\d+)&t2=(\d+)") {
    set $key_t1 $1;
    set $key_t2 $2;
    rewrite ^ /loc/$key_t1/$key_t2 last;
  }
}

A shorter form with named captures in the regex:

location /index/ {
  if ($args ~ "^t1=(?<key_t1>\d+)&t2=(?<key_t2>\d+)") {
    rewrite ^ /loc/$key_t1/$key_t2 last;
  }
}

Here the parameters are checked by comparing the $args variable (all GET parameters of the request) with a regex and saving the values of parameters t1 and t2 into the $key_t1 and $key_t2 variables respectively. A new URI is then built from these values to find the location and finish processing.

The second way to solve the parameter task uses proxying. For example:

location /index/ {
  proxy_pass http://127.0.0.1:8080/loc/$arg_t1/$arg_t2;
}

For debugging, redirect processing and rewrite module directives can be logged to error_log (level notice); the directive can be set in the http, server, location and if contexts:

rewrite_log on;

The try_files directive may also be worth recalling, even though it does not belong to the rewrite module. With try_files you can check for files in order and use an internal redirect to a named location or return an error. The classic example looks like this:

location / {
  try_files $uri $uri/ @namedloc;
}
location @namedloc {
  proxy_pass http://127.0.0.1:8080;
}
location /static/ {
  try_files $uri $uri/ =404;
}

In this example, for location / the request is first handled as $uri; if there is no such file, a slash is added ($uri/) and the request is tried as a directory; if that fails too, an internal redirect goes to location @namedloc. For location /static, the first attempt uses the URI unchanged, the second uses the directory, and on failure a 404 is returned. One interesting feature: if a file is not found in try_files, no record about the file search error is added to error_log.

So the main capabilities of the rewrite module have been covered, starting from basic uses of the return directive and finishing with more complex constructions that use regular expressions and if blocks.