Redirects in Nginx are set up in two ways: with the return directive or the rewrite directive. Both are placed inside a server block or a location, described in the article on location priority in Nginx, but they work differently and can just as easily break a site if configured carelessly.
return versus rewrite: the difference
The return directive immediately stops processing the request and sends the client a response code and, if needed, a redirect address. It is the fastest and most predictable way to do a redirect: one line, minimal CPU load.
The rewrite directive changes the request URI itself using a regular expression and, depending on the flag, either continues processing with the new URI inside Nginx or sends the client an HTTP redirect. Rewrite is more flexible, but that same flexibility more often causes loops and unexpected behavior.
When to use return for a simple redirect
If the task is simply sending the client to another address without modifying the path, return is always preferable to rewrite. The official nginx.org documentation directly recommends return for redirects that do not need regular-expression substitution.
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
Here $request_uri preserves the original path and query parameters, so example.com/page?id=5 stays example.com/page?id=5 instead of turning into a redirect to the homepage.
rewrite syntax: the last, break, redirect, permanent flags
The rewrite directive takes a regular expression, a replacement, and an optional flag:
- last — stops processing the current location and searches again for a location matching the new URI.
- break — stops rewrite processing but stays in the same location.
- redirect — sends the client an HTTP 302 with the new address instead of internal processing.
- permanent — sends the client an HTTP 301 with the new address.
Without the redirect or permanent flag, rewrite creates no browser-visible redirect: the new URI is processed inside Nginx, and the old address stays in the client's address bar.
A common mistake: an infinite rewrite loop
A last flag inside a location that itself matches the same rewrite pattern creates an infinite loop: Nginx rewrites the URI, searches for a location again, lands in the same block, and rewrites the URI again. The result is a rewrite or internal redirection cycle error in error_log and a 500 error on the site.
To avoid the loop, check with an if ($uri !~ ...) condition that the URI has not already been rewritten, or use break instead of last where moving to a new location is not needed. When proxying to a backend through reverse proxy in Nginx, an extra rewrite is often not needed at all — proxy_pass handles it on its own.
Codes 301 and 302: when to use which
| Code | Meaning | When to use it |
|---|---|---|
| 301 Moved Permanently | Permanent redirect | Domain or protocol change, a final URL move |
| 302 Found | Temporary redirect | Maintenance, an A/B test, a temporary landing page |
| 307 Temporary Redirect | Temporary redirect preserving the method | POST requests that must not turn into GET |
Search engines pass page weight through a 301, but cache it aggressively: changing a 301 to a new address later takes time to reindex. Search engines do not remember a 302 for long, so final moves should use a 301.
Practical redirect examples
Three common redirect scenarios on a site, which are also worth checking against the article on SSL and Certbot in Nginx when moving to HTTPS:
# redirect from www to the non-www domain
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
# redirect from http to https
server {
listen 80;
server_name example.com;
return 301 https://example.com$request_uri;
}
# redirect an old URL to a new one, keeping parameters
rewrite ^/old-page$ /new-page permanent;
Checklist before deploying redirects
- Use return instead of rewrite when no regular-expression substitution is needed.
- Include $request_uri so you do not lose the path and query parameters during a redirect.
- Check for an infinite loop: nginx -t does not always catch this error, so test with curl -I.
- Choose 301 for permanent moves and 302 for temporary ones.
Return and rewrite solve the same task — redirecting traffic — but at a different cost: return is faster and more reliable for direct redirects, while rewrite is needed where the request path truly changes by pattern. Test every new redirect with curl -I before deploying it to production.