Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
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.
We’ll create our example with the main configuration file of the Nginx server. On Ubuntu, this is the /etc/nginx/nginx.conf file.
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.
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:
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.
The rewrite directive modifies the request URL path. We can set three arguments to rewrite:
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:
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.
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.
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:
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.
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:
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.
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:
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.
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.