Updated: 22 October 2026 · Applies to: Nginx on a self-managed Linux VPS; commands tested against nginx 1.31 (Alpine)
Short answer: use return when you want the browser to see a redirect (a new HTTP status and a new URL in the address bar). Use rewrite when you want Nginx to serve different content for a URL without the browser ever knowing the URL changed. Both directives come from Nginx's core ngx_http_rewrite_module, so no extra module needs installing.
What you need
- Root or sudo access to a server running Nginx, and the ability to edit its config and reload the service.
- A way to test a rule before it goes live. The examples below were tested in a throwaway nginx:alpine container; you can repeat the same test with Docker or Podman on any machine, or in a staging VPS, before touching a production config.
- A basic regular-expression tester such as regex101.com helps when writing the capture-group patterns used below.
Return: send the browser a redirect
return is the simpler of the two directives. It does not use regular expressions on its own, but it can reuse a group captured by the location block it sits in. Place it inside a server or location block.
Redirect everything under a path to another domain, permanently:
location /old-blog/ {
return 301 https://example.com;
}
Keep the original path and query string when redirecting to the new domain, using $request_uri:
location /old-blog/ {
return 301 https://example.com$request_uri;
}
A request to /old-blog/post1?x=1 becomes https://example.com/old-blog/post1?x=1. Use $host instead of a hard-coded domain if you want the rule to work the same way on every domain the server answers for.
Redirect a request based on a pattern captured in the location block, for example sending any .txt file to a /files/ directory:
location ~* ^/([^/]+\.txt)$ {
return 301 /files/$1;
}
A request for /report.txt redirects to /files/report.txt. (Escape the dot as \. in the pattern, an unescaped dot matches any character, not just a literal one.)
Rewrite: change the URL internally
Syntax: rewrite regex replacement [flag]; The regex is matched against the request URI; when it matches, the URI is replaced and, depending on the flag, Nginx either keeps processing in the current location or looks for a new location that matches the new URI. The flags, per the official module documentation:
- last: stop processing rewrite rules in this location and start a fresh location search on the new URI.
- break: stop processing rewrite rules, but stay in the current location; the rewritten URI is served from there, it is not matched against other locations.
- redirect: send the browser a temporary 302 redirect (used automatically when the replacement is not a local path).
- permanent: send the browser a permanent 301 redirect.
Official documentation: nginx.org, ngx_http_rewrite_module.
last vs break, tested side by side
The difference between last and break only shows up once a rewritten URL would also match a different location block. Copy this into a test container to see it for yourself:
location /browser/ {
rewrite ^/browser/(.*)$ /chrome/$1 last;
}
location /chrome/ {
try_files $uri $uri/ =404;
}
location /break-test/ {
rewrite ^/break-test/(.*)$ /served/$1 break;
try_files $uri $uri/ =404;
}
location /served/ {
return 200 "this text is never reached by the break example below\n";
}
With that config loaded and a file called sample.txt placed in both the chrome and served directories under the document root:
nginx -t && nginx -s reload curl http://127.0.0.1/browser/sample.txt curl http://127.0.0.1/break-test/sample.txt
Both requests return the content of sample.txt, which looks identical from the outside, but the two flags got there differently. The last request was rewritten to /chrome/sample.txt and then re-matched against the /chrome/ location, which served it with try_files. The break request was rewritten to /served/sample.txt but never re-entered the /served/ location at all. try_files, still running inside the original /break-test/ location, served the file straight from the document root. That is why the return 200 text inside /served/ never appears: with break, that location block is never reached, no matter what it contains.
Test and apply a rule safely
- Edit the config in a
serverorlocationblock, in a separate file first if possible. - Check the syntax before touching the live service:
nginx -t
- Reload once the test passes, so existing connections are not dropped:
systemctl reload nginx
(orservice nginx reloadon older init systems; on a cPanel/WHM server running EA-Nginx, reload it from WHM's Service Manager so the panel's own state stays in sync). - Confirm the result with a direct request, following redirects where relevant:
curl -I http://your-domain.example/old-blog/post1 curl -IL http://your-domain.example/old-blog/post1
Common mistakes to avoid
- Redirecting a domain to itself with
returnorrewrite, which creates a redirect loop the browser eventually refuses to follow. - Using
lastinside alocationblock when the rewritten URI matches the same location again. Nginx stops after 10 internal rewrites and returns a 500 error rather than loop forever. - Leaving an unescaped dot in a file-extension pattern, for example
.txtinstead of\.txt, which matches one character followed by "txt" rather than a literal dot. - Forgetting that a query string on the original request is appended again to a
rewritereplacement that already has its own arguments, unless the replacement ends with a bare?.