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.
지금 리디렉션을 검사하세요
잘못된 리디렉션이 SEO에 피해를 주지 않도록 하세요. 무료 도구로 링크를 즉시 감사하세요.
How 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.