Nginx Redirects: return vs rewrite, Best Practices and Gotchas
Nginx has three ways to send a redirect, and most of the production bugs I see come from picking the wrong one. This guide walks through return, rewrite, and the if directive — when to use each, and the gotchas that bite people.
The three ways Nginx redirects
return— fastest, cleanest, no regex. Use this whenever you can.rewrite ... permanent|redirect— when you need pattern matching or capture groups.try_files+ error pages — not technically a redirect, but worth knowing for fallbacks.
return: the one you usually want
If you're redirecting a static path to a static target, use return. It's faster than rewrite because there's no regex engine involved, and it's easier to read.
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}A few things to notice:
$request_uripreserves the path and query string. Don't use$urihere — it drops the query string.- Put the HTTPS+canonical redirect in its own
serverblock listening on port 80. Don't mix HTTP and HTTPS handling in one block. - Use
301for permanent moves,302for temporary,308if you need to preserve POST (see the 301 vs 308 article).
rewrite: when you need regex
Reach for rewrite when you need capture groups or pattern matching. The syntax is rewrite regex replacement [flag], and the flag is what makes it a redirect:
# 301 permanent redirect
rewrite ^/old-blog/(.*)$ /blog/$1 permanent;
# 302 temporary redirect
rewrite ^/sale/(.*)$ /promo/$1 redirect;
# Internal rewrite (no redirect, no status change)
rewrite ^/api/v1/(.*)$ /api/v2/$1 last;The flags matter a lot:
- permanent — 301
- redirect — 302
- last — internal rewrite, re-evaluates location blocks
- break — internal rewrite, stops rewrite processing
No flag at all? The behavior depends on whether the replacement starts with http:// or https://. If it does, Nginx sends a 302. If it doesn't, it's an internal rewrite. This implicit behavior is a footgun — always specify the flag.
The if directive is evil (mostly)
Nginx's official wiki literally has a page called "If is Evil". The problem: if inside a location block has surprising interactions with other directives and can cause segfaults in edge cases.
What's safe inside if:
# Safe: return and rewrite
if ($host = 'www.example.com') {
return 301 https://example.com$request_uri;
}
if ($request_method = POST) {
return 405;
}What to avoid: setting variables, calling proxy_pass, or anything stateful inside if inside a location. If you can express the logic with map or separate server blocks, do that instead.
Common patterns
Force HTTPS and canonicalize the host
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name www.example.com;
return 301 https://example.com$request_uri;
}Strip trailing slashes
rewrite ^/(.*)/$ /$1 permanent;Redirect an entire directory tree
location /old-docs/ {
return 301 /docs/;
}
# Or preserve the sub-path:
location /old-docs/ {
rewrite ^/old-docs/(.*)$ /docs/$1 permanent;
}Gotchas that waste hours
- Relative redirects.
return 301 /newworks, but some clients and proxies handle relativeLocationheaders poorly. Prefer absolute URLs in production. - Double slashes.
return 301 https://example.com/$request_uriproduceshttps://example.com//pathbecause$request_urialready starts with a slash. - Variable scoping.
$urichanges after a rewrite;$request_uridoesn't. Pick the right one for your case. - Port handling. If you're behind a load balancer on a non-standard port,
$hostmay not include the port. Use$http_hostor$host:$server_portif you need it. - Reload vs restart. After changing redirect rules,
nginx -t && nginx -s reload. No downtime needed.
Testing your redirects
Before deploying, test locally with curl -I:
curl -I http://localhost/old-path
# Look for: HTTP/1.1 301 Moved Permanently
# Location: https://example.com/new-pathIn production, use a tool like Redirect Check to see the full chain, detect loops, and verify each hop's status code.
Bottom line
- Default to
return 301/302. It's faster and clearer. - Use
rewrite ... permanentonly when you need regex capture. - Avoid
ifunless it's a simplereturnorrewrite. - Always specify the flag on
rewrite— implicit behavior changes based on the replacement URL. - Test with
curl -Ibefore deploying, and use a redirect checker after.
Verifica tus redirecciones ahora
No dejes que las malas redirecciones perjudiquen tu SEO. Usa nuestra herramienta gratuita para auditar tus enlaces al instante.
Artículos Relacionados
Ver todos los artículos arrow_forwardHow to Fix ERR_TOO_MANY_REDIRECTS: A No-BS Troubleshooting Guide
Stuck in a redirect loop? Learn how to diagnose and fix the ERR_TOO_MANY_REDIRECTS error with practical solutions for WordPress, Cloudflare, Apache, and Nginx.
Setting Up Redirects in Cloudflare: A No-Nonsense Guide for 2025
Master Cloudflare redirects with this practical guide. Learn Single Redirects, Bulk Redirects, common pitfalls, and real troubleshooting tips that actually work.
Mobile-First Redirects: How to Optimize for Core Web Vitals in 2025
Learn how redirects impact Core Web Vitals and mobile performance. Practical strategies to maintain LCP, INP, and CLS scores during redirects.