nginx

Nginx Redirects: return vs rewrite, Best Practices and Gotchas

RC
Redirect Check Team
9 min read

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_uri preserves the path and query string. Don't use $uri here — it drops the query string.
  • Put the HTTPS+canonical redirect in its own server block listening on port 80. Don't mix HTTP and HTTPS handling in one block.
  • Use 301 for permanent moves, 302 for temporary, 308 if 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 /new works, but some clients and proxies handle relative Location headers poorly. Prefer absolute URLs in production.
  • Double slashes. return 301 https://example.com/$request_uri produces https://example.com//path because $request_uri already starts with a slash.
  • Variable scoping. $uri changes after a rewrite; $request_uri doesn't. Pick the right one for your case.
  • Port handling. If you're behind a load balancer on a non-standard port, $host may not include the port. Use $http_host or $host:$server_port if 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-path

In 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 ... permanent only when you need regex capture.
  • Avoid if unless it's a simple return or rewrite.
  • Always specify the flag on rewrite — implicit behavior changes based on the replacement URL.
  • Test with curl -I before deploying, and use a redirect checker after.

지금 리디렉션을 검사하세요

잘못된 리디렉션이 SEO에 피해를 주지 않도록 하세요. 무료 도구로 링크를 즉시 감사하세요.

#nginx#tutorial#troubleshooting
이 글 공유하기: