100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
DevOps

Rewrites and Redirects

The difference between client-visible redirects (return) and internal URI rewrites (rewrite) in Nginx, plus the flags and pitfalls that cause loops and lost query strings.

ConfigurationIntermediate9 min readJul 10, 2026
Analogies

Redirects vs Rewrites: A Critical Distinction

A redirect tells the client's browser to make a brand new request to a different URL, using the return directive with a 301 (permanent) or 302 (temporary) status code, for example return 301 https://$host$request_uri;. A rewrite, done with the rewrite directive, changes the URI internally on the server without the client ever knowing, and Nginx continues processing the request against the new URI. Confusing the two is a common mistake: using rewrite when you meant a return forces Nginx to run regex processing on every request unnecessarily, and can create loops if the rewritten URI matches the same rewrite rule again.

🏏

Cricket analogy: It's like the difference between an umpire sending a batsman back to the pavilion to walk in fresh through a different gate (redirect, visible to everyone) versus a scorer quietly correcting the scorecard internally without announcing anything (rewrite, invisible to the crowd).

Writing Safe rewrite Rules

The rewrite directive syntax is rewrite regex replacement [flag];, evaluated within a server or location context. The last flag stops processing the current rewrite set and re-searches location blocks with the new URI, while break stops rewrite processing but stays within the current location. A frequent production incident is a rewrite rule whose replacement can match its own regex, creating an infinite internal loop that Nginx eventually kills after 10 iterations with a 500 error — always test rewrite rules against edge cases like trailing slashes and query strings before deploying.

🏏

Cricket analogy: It's like a captain's fielding instruction that says 'if the ball goes to deep cover, immediately re-assess the whole field' (last, full re-evaluation) versus 'just shift this one fielder slightly and carry on with the same plan' (break, stay in current context).

Preserving Method, Query Strings, and Loops

A return 301 preserves the original HTTP method poorly — browsers convert POST to GET on a 301 in older implementations, so a 307 or 308 status should be used when the method must be preserved across a redirect. Query strings are appended automatically by return 301 https://$host$request_uri; since $request_uri includes them, but a manually constructed rewrite target must explicitly append $args or the query string is silently dropped, breaking anything relying on tracking parameters or pagination.

🏏

Cricket analogy: It's like a rain-delay rule that resets the match format entirely (dropping context, like a 301 losing POST data) versus a Duckworth-Lewis recalculation that carefully preserves the original match context (like a 308 preserving method and body).

nginx
server {
    listen 80;
    server_name example.com www.example.com;

    # Permanent redirect to HTTPS canonical host, preserving path + query string
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    # Internal rewrite: old blog paths to new structure, without a client-visible redirect
    rewrite ^/blog/([0-9]+)/(.*)$ /articles/$2?id=$1 last;

    # Explicit redirect for a deprecated marketing path, preserving query args
    location /promo {
        return 302 /offers?$args;
    }
}

Prefer return over rewrite whenever you're just issuing a redirect and not changing internal routing — return is evaluated immediately and skips Nginx's regex engine, making it both clearer to read and measurably cheaper on high-traffic servers.

A rewrite rule whose output can match its own input regex creates an internal loop. Nginx caps internal redirects at 10 and then returns a 500 Internal Server Error — always trace a new rewrite rule with rewrite_log on; in a staging environment before shipping it to production.

  • return issues a client-visible HTTP redirect (301/302/307/308); rewrite changes the URI internally on the server.
  • 301 is permanent and cacheable by browsers; 302 is temporary and should be revalidated on every request.
  • 307 and 308 preserve the original HTTP method and body, unlike 301/302 in some legacy clients.
  • The last flag re-searches location blocks with the new URI; break stays within the current location.
  • $request_uri includes the query string automatically; manual rewrite targets must append $args explicitly.
  • A rewrite rule that can match its own output creates an internal loop, capped at 10 iterations before a 500 error.
  • Prefer return for simple redirects — it skips regex evaluation and is both faster and easier to audit.

Practice what you learned

Was this page helpful?

Topics covered

#DevOps#NginxStudyNotes#RewritesAndRedirects#Rewrites#Redirects#Critical#Distinction#StudyNotes#SkillVeris#ExamPrep