Nginx Rewrite and Return Directives Explained

29 May 2026, 20:51:03
Nginx (pronounced “engine-x”) is a modern web server and so much more. In fact, it powers around 35% of all websites on the internet, making it the most widely used web server today.
In this article, we’ll take a closer look at two directives commonly found in the configuration of almost every modern Nginx-based website: rewrite and return.
Their purpose is to process requests, redirect users, rewrite file paths internally, and modify URLs. We’ll examine how these directives work, their syntax, common use cases, and when to use each one.

Request Processing Phases in Nginx

To understand how return and rewrite interact with requests, it’s useful to know how Nginx processes a request internally.
The process is divided into 11 sequential phases:
  • NGX_HTTP_POST_READ_PHASE
  • NGX_HTTP_SERVER_REWRITE_PHASE
  • NGX_HTTP_FIND_CONFIG_PHASE
  • NGX_HTTP_REWRITE_PHASE
  • NGX_HTTP_POST_REWRITE_PHASE
  • NGX_HTTP_PREACCESS_PHASE
  • NGX_HTTP_ACCESS_PHASE
  • NGX_HTTP_POST_ACCESS_PHASE
  • NGX_HTTP_PRECONTENT_PHASE
  • NGX_HTTP_CONTENT_PHASE
  • NGX_HTTP_LOG_PHASE
We won't cover all of them in this article. For our purposes, the following four phases are the most important:
  • NGX_HTTP_SERVER_REWRITE_PHASE - processes rewrite directives that are defined directly inside the server block (outside of any location);
  • NGX_HTTP_FIND_CONFIG_PHASE - Nginx searches for the appropriate location block based on the request URI;
  • NGX_HTTP_REWRITE_PHASE - executes rewrite directives inside the matched location;
  • NGX_HTTP_POST_REWRITE_PHASE - if the URI was modified during the previous phase, the request will be sent back to find for a new matching location.
The rewrite directive is the mechanism that can modify a URI and send the request through a second round of processing to find a new location. return, as in many programming languages, acts as an “exit point”, so it terminates further processing of the request and returns a response to the user.

return: Fast and Simple

Syntax

return code [text];
return code URL;
return URL;
  • code - HTTP status code (200, 301, 302, 403, 444, etc.);
  • [text] - text in quotation marks. It will be included in the body of the reply;
  • URL - Redirect URL (30x codes). Will be sent in the Location header.

Context

server, location, and inside if blocks.

When to Use Return

return is used when you need to perform a simple redirect or return a response without further processing the current request. It does not support regular expressions, which makes its syntax simple and easy to understand, and it is faster than rewrite. The URL value can contain variables (such as $request_uri).

Redirect WWW to Non-WWW

20260529_QydTh2GI
if ($host = www.domain.com) {
return 301 https://domain.com$request_uri;
}
  • 301 - HTTP status code (“Moved Permanently”);
  • domain.com - your domain;
  • $request_uri - requested URI including query parameters (for example /article.php?id=99).

Redirect HTTP to HTTPS

This redirect is commonly configured with return inside the server block of http version of the website:
20260529_4m8kPx5S
return 301 https://domain.com$request_uri;It can also be implemented using if:
20260529_L7Fx63hy
if ($scheme = http) {
return 301 https://domain.com$request_uri;
}

Redirect an Old Domain to a New One

The syntax is very similar to the HTTP -> HTTPS redirect:
20260529_wkABK6AG
server {
server_name old-domain.com;
listen 80;

return 301 https://new-domain.com$request_uri;
}

Disable Access to a Page or File Without Deleting It

Create a dedicated location block and return the desired HTTP status code:
location = /photos.html {
return 404 "No photos :( \n";
}
  • /photos.html - URI that triggers the rule;
  • 404 - HTTP response code (“Not Found”);
  • "No photos :(" - response body text. This could also be a file path, URL, or omitted entirely.
As a result, the client receives a 404 Not Found response, while the actual file still physically exists on the server:
20260529_VTAoWzmf

Blocking Bots and Unwanted Traffic

You can block unwanted bots by checking the client’s User-Agent header:
if ($http_user_agent ~* (bot|crawler|spider|curl|wget|python|scrapy||libwww)) {
return 444;
}
The User-Agent string is compared against the listed patterns. If a match is found, Nginx returns status code 444.
444 is a non-standard Nginx-specific response code. Instead of sending an HTTP response, Nginx immediately closes the TCP connection without returning any data, which helps reduce unnecessary resource usage.
A user whose UA is on the list receives a connection termination error:
20260529_FN2kMGAB
This is very useful for blocking junk traffic, bots, scrapers, and basic malicious requests.

Rewrite: More Powerful and Flexible

Syntax

rewrite regex replacement [flag];
  • regex - regular expression used to match the request URI;
  • replacement - new URI;
  • flag - optional parameter defining further request processing behavior:
    • last - stop processing the current block and search for a new location using the rewritten URI;
    • break - stop processing rewrite rules but continue handling the request inside the current location;
    • redirect - send a temporary redirect (302);
    • permanent - send a permanent redirect (301).

If no flag is specified, Nginx continues executing subsequent rewrite directives in the same block. This can sometimes lead to confusing behavior, so explicitly specifying a flag is generally considered good practice.

Context

server, location, if.

How Rewrite Works

The rewrite directive operates on the normalized URI and does not directly process query parameters. Query arguments are preserved automatically by default:
location / {
rewrite ^/user$ /user.php last;
}
You can also pass arguments explicitly using $args:
location / {
rewrite ^/user$ /user.php?$args last;
}
Both examples behave identically:
20260529_1ZhIEye2
Adding a trailing ? disables automatic query params string forwarding:
location / {
rewrite ^/user$ /user.php? last;
}
This is exactly what happens when you manually append $args: first you disable automatic argument forwarding with ?, then add them manually.
20260529_Cr5ipio8

Last vs Break

The last flag tells Nginx to stop processing rewrite rules in the current context and perform a new location lookup using the rewritten URI. Let’s look at a few classic examples.

Human-Friendly URLs

location / {
rewrite ^/article/(\d+)- /article.php?id=$1 last;
rewrite ^/user/(\w+)$ /user.php?name=$1 last;
}
  • ^ - beginning of string (regex syntax);
  • $ - end of string;
  • /article/ and /user/ - text from a regular expression;
  • (\d+) - one or more digits;
  • (\w+) -one or more alphanumeric characters;
  • - - literal dash.
In all of our previous examples, we used the last flag. This caused the Nginx to take the replaced URI (in the example, this is /article.php or /user.php) and look for a new location for it (one that handles .php). The result is human-friendly URLs:
20260529_lbQTMViB

WWW to Non-WWW Redirect Using Rewrite

The same redirect from earlier can also be implemented with rewrite:
if ($host = www.domain.com) {
rewrite ^(.*)$ https://domain.com$1 permanent;
}
  • ^ - beginning of string;
  • $ - end of string;
  • (.*) - captures the entire URI;
  • $1 - inserts the captured value;
  • permanent -  sends HTTP 301 (“Moved Permanently”).
The result is identical to using return, although return is usually preferred for simple redirects because it is cleaner and more efficient.
20260529_SHjOfPrU

The break flag also rewrites the URI, but unlike last, it does not trigger a new location search. Instead, Nginx continues processing inside the current location.
This is useful, for example, when changing file access paths:
location /iso/ {
rewrite ^/iso/(.*)$ /files/$1 break;
}
With this configuration: /iso/Windows10.iso internally becomes: /files/Windows10.iso:
20260529_Aiq4eCa7
Nginx serves the file using the rewritten path while remaining inside the same location block.

Try_files as an Alternative to Rewrite

In modern frameworks such as Laravel or Symfony, the try_files directive is often a more practical alternative for checking whether files or directories exist and routing requests.
A common configuration looks like this:
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
In this example, Nginx checks whether the requested URI exists as:
  1. a file ($uri);
  2. a directory ($uri/).
If neither exists, the request is internally redirected to /index.php together with any query parameters. Just like rewrite with the last flag, this causes Nginx to perform another location lookup: in this case forwarding the request to a PHP handler.
In simple terms, this allows Nginx to serve static files directly while passing all other requests to the application’s front controller.
20260529_aw3AF5AM

Conclusion

Modern web applications increasingly rely on application-level routing combined with try_files. However, classic Nginx directives such as rewrite and return remain extremely important and useful for understanding how request processing works. Use return for simple redirects and immediate HTTP responses. Use rewrite when you need more advanced URI transformations. Understanding the difference between last and break will also help you migrate .htaccess rules from Apache to Nginx and move from Apache-based PHP handling to the faster and more efficient PHP-FPM.