1. Overview

Nginx is a popular web server that we can also use as a forward proxy, reverse proxy, or load balancer. A notable strength of Nginx is that it offers many configuration options. URL rewriting with the return and rewrite directives is an example of this.

In this tutorial, we’ll examine the rewrite directive. In particular, we’ll learn about the break and last flags. Both flags change the way the server handles a request.

2. Creating an Example Configuration

We’ll create our example with the main configuration file of the Nginx server. On Ubuntu, this is the /etc/nginx/nginx.conf file.

2.1. Creating a New Virtual Server

We’ll start by adding a new server config:

server {
   listen 8000;
   rewrite_log on;
}

As we can see, our server listens on port 8000. Moreover, the rewrite_log directive enables the output of URL rewrites to the error log. This means that the server will output the rewritten URLs to the error log. The logging level of these records is notice.

2.2. Creating the Locations

Now that we have a virtual server, we’ll add locations inside it:

location  /api {
   root /data;
}
 location /service2 {
    alias /data/service2;
 }

As can be seen, we created two locations:

  • the first matches URLs starting with /api and gets files from the /data directory
  • the second matches URLs starting with /service2 and gets files from the /data/service2 directory

Furthermore, we used the root and alias directives. Both of them append the request URL path to the local filesystem path. The difference is that root keeps the location prefix string, while alias doesn’t.

2.3. Adding the Rewrite Rules

The rewrite directive modifies the request URL path. We can set three arguments to rewrite:

  • regular expression to match the request URL path, where no match means no rewriting
  • replacement expression to transform the request URL path
  • optional flags

Next, we create some rewrite rules for the /api location:

rewrite ^/api/(.*) /service1/$1;
rewrite ^/service1/(.*) /service2/$1;
return 400;

Here we created two rewrite directives:

  • the first matches URLs starting with the /api string and replaces it with the /service1 string
  • the second matches URLs starting with /service1 and replaces it with the /service2 string

Notably, we can capture parts of the request URL in the regular expression. To do this, we enclose the desired part in parentheses. After that, we can refer to it in the replacement expression as a variable. For example, the content matched in the first set of parentheses can be referred to as $1. Similarly, the content in the second set of parentheses is $2, and so on.

Furthermore, we added a return directive that will return an HTTP status code 400. This stands for bad request.

2.4. Testing the Configuration

Let’s see the complete server configuration:

server {
   listen 8000;
   rewrite_log on;

   location  /api {
      root /data;
      rewrite ^/api/(.*) /service1/$1;
      rewrite ^/service1/(.*) /service2/$1;
      return 400;
}

   location /service2 {
      alias /data/service2;
   }
}

The URL that we’ll use for testing is http:/127.0.0.1:8000/api/echo.json and we expect it to match the /api location.

Before testing, we should create the directories /data/service1 and /data/service2:

$ sudo mkdir /data/service1
$ sudo mkdir /data/service2
$ echo "{ 'message' : 'Hello from service1' }" | sudo tee /data/service1/echo.json
{ 'message' : 'Hello from service1' }
$ echo "{ 'message' : 'Hello from service2' }" | sudo tee /data/service2/echo.json
{ 'message' : 'Hello from service2' }

Here, we created two sample echo.json files and wrote a greeting in both using the echo and tee commands.

3. Rewrite With No Flags

Firstly, let’s examine how our test request is processed without rewrite flags:

$ curl 127.0.0.1:8000/api/echo.json
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center> nginx/1.18.0 (Ubuntu) </center>
</body>
</html>

The server responded to our curl request with an HTTP status code 400. This means that Nginx executed the directives of the /api location one by one until the return directive.

Let’s examine the error log to verify rewrite_log worked:

$ tail /var/log/nginx/error.log
2022/11/28 11:55:31 [notice] 45579#45579: *432 "^/service1/(.*)" matches "/service1/echo.json", client: 127.0.0.1, server: , request: "GET /api/echo.json HTTP/1.1", host: "127.0.0.1:8000"
2022/11/28 11:55:31 [notice] 45579#45579: *432 rewritten data: "/service2/echo.json", args: "", client: 127.0.0.1, server: , request: "GET /api/echo.json HTTP/1.1", host: "127.0.0.1:8000"

Indeed, we can see two URL rewrites in the error log.

So, here are the execution steps:

  1. /api location matches
  2. First rewrite converts the request URL to /service1/echo.json
  3. Second rewrite converts /service1/echo.json to /service2/echo.json
  4. Nginx returns the HTTP status code 400

Notably, both return and rewrite belong to the ngx_http_rewrite module. When used together, the directives of this module create a set of instructions.

The last and break flags stop the execution of the current set. Otherwise, all the directives in the set execute sequentially.

4. The last Flag

The last flag stops the processing of the current ngx_http_rewrite set. Moreover, it will start a new search for a location that matches the rewritten URL. Next, we’ll add the last flag to the second rewrite directive of the /api location:

location  /api {
   root /data;
   rewrite ^/api/(.*) /service1/$1;
   rewrite ^/service1/(.*) /service2/$1 last;
   return 400;
}

location /service2 {
   alias /data/service2;
}

Let’s reload our configuration and test:

$ sudo nginx -s reload
$ curl 127.0.0.1:8000/api/echo.json
{ 'message' : 'Hello from service2' }

As we expected, the request processing flow changes because of the added last flag:

  1. /api location matches
  2. First rewrite converts the request URL to /service1/echo.json
  3. Second rewrite converts /service1/echo.json to /service2/echo.json
  4. Because of the last flag in the second rewrite, the set execution stops and return isn’t executed
  5. New location search starts for the /service2/echo.json URL
  6. /service2 location matches
  7. Nginx returns the /data/service2/echo.json file

Let’s also check the error log:

$ tail /var/log/nginx/error.log
2022/11/28 12:20:49 [notice] 45608#45608: *433 "^/api/(.*)" matches "/api/echo.json", client: 127.0.0.1, server: , request: "GET /api/echo.json HTTP/1.1", host: "127.0.0.1:8000"
2022/11/28 12:20:49 [notice] 45608#45608: *433 rewritten data: "/service1/echo.json", args: "", client: 127.0.0.1, server: , request: "GET /api/echo.json HTTP/1.1", host: "127.0.0.1:8000"
2022/11/28 12:20:49 [notice] 45608#45608: *433 "^/service1/(.*)" matches "/service1/echo.json", client: 127.0.0.1, server: , request: "GET /api/echo.json HTTP/1.1", host: "127.0.0.1:8000"
2022/11/28 12:20:49 [notice] 45608#45608: *433 rewritten data: "/service2/echo.json", args: "", client: 127.0.0.1, server: , request: "GET /api/echo.json HTTP/1.1", host: "127.0.0.1:8000"

Indeed, we can see the two rewrites that were performed.

5. The break Flag

The break flag stops the execution of the current ngx_http_rewrite directive set. After break, the server continues to run directives outside the current set.

To see the use of the break flag, we’ll add it to the first rewrite directive:

location  /api {
   root /data;
   rewrite ^/api/(.*) /service1/$1 break;
   rewrite ^/service1/(.*) /service2/$1;
   return 400;
}

location /service2 {
   alias /data/service2;
}

In our case, we expect that the execution will stop at the first rewrite directive. As a result, the second rewrite and return won’t run.

Let’s reload and test:

$ sudo nginx -s reload
$ curl 127.0.0.1:8000/api/echo.json
{ 'message' : 'Hello from service1' }

Here, service1 responded.

Let’s review the execution flow:

  1. /api location matches
  2. First rewrite converts the request URL to /service1/echo.json
  3. Due to the break flag, the set execution stops
  4. The request is served from the root directory of the current location directive

Let’s also review the error log:

$ tail /var/log/nginx/error.log
2022/11/28 12:41:09 [notice] 45625#45625: *434 "^/api/(.*)" matches "/api/echo.json", client: 127.0.0.1, server: , request: "GET /api/echo.json HTTP/1.1", host: "127.0.0.1:8000"
2022/11/28 12:41:09 [notice] 45625#45625: *434 rewritten data: "/service1/echo.json", args: "", client: 127.0.0.1, server: , request: "GET /api/echo.json HTTP/1.1", host: "127.0.0.1:8000"

Indeed, only the first rewrite was performed.

6. Conclusion

In this article, we examined the last and break flags. We saw how they affect request handling with examples. Both of them stop the execution of the current ngx_http_rewrite directive set. However, while the last flag initiates a new location search in the configuration file using the rewritten URL, break continues the execution after the end of the current directive set.