The Nginx rewrite and redirect cycle error happens when Nginx gets stuck in an infinite loop, repeatedly matching a rewrite rule and then redirecting to a URL that itself matches the same rewrite rule.
Common Causes and Fixes
1. Trailing Slash Mismatch:
- Diagnosis: Check your
rewriterules andlocationblocks. A common culprit is arewriterule that adds or removes a trailing slash, but thelocationblock or anotherrewriterule immediately behind it also tries to manipulate the trailing slash, leading to oscillation. For example, a rule might redirect/footo/foo/, but another rule then sees/foo/and redirects it back to/foo. - Fix: Ensure your
rewriterules andlocationdirectives consistently handle trailing slashes. If arewriterule is intended to add a trailing slash, make sure subsequent rules or thelocationblock expect it.# Example of a problematic rule that might cause a cycle # rewrite ^/old-path$ /new-path/ permanent; # Adds slash # Another rule or location block that might then remove it or re-process # location /new-path/ { ... } # or # rewrite ^/new-path$ /new-path/ permanent; # Tries to add slash again # Corrected approach: location = /old-path { rewrite ^ /new-path/ permanent; # Redirects to /new-path/ } location /new-path/ { # This block correctly handles the trailing slash added by the rewrite. # If you need to enforce a trailing slash, ensure it's handled here. # For example, if you *don't* want trailing slashes on /new-path, # you'd need a different rewrite rule or a location block that handles that. # But for preventing cycles, consistency is key. # If the rewrite adds a slash, the location should expect it. try_files $uri $uri/ =404; } - Why it works: By ensuring that a rewrite adding a slash is followed by a configuration that correctly handles that slash (or vice-versa), you break the cycle of adding/removing the slash repeatedly.
2. Case Sensitivity Mismatch:
- Diagnosis: Nginx’s
rewriteandlocationdirectives can be case-sensitive or case-insensitive depending on configuration. If arewriterule matches a URL in a case-insensitive way (e.g.,rewrite ... "i";), but the subsequentlocationblock or anotherrewriterule is case-sensitive and doesn’t match the rewritten URL’s case, it might trigger another rewrite or redirect. - Fix: Use the
if_modflag with yourrewriterules or ensure yourlocationblocks are consistently case-sensitive or insensitive as intended. Often, explicitly matching case is safer.# Problematic: Might rewrite /Page to /page, then a case-sensitive location /page/ doesn't match # rewrite ^/Page$ /page/ permanent; # location /page/ { ... } # Corrected: Ensure case consistency or use case-insensitive matching appropriately location /page/ { # This location block will match /page/ and /PAGE/ if the server is configured for case-insensitivity # or if the location itself is case-insensitive. # If you need explicit case handling in rewrites: rewrite ^/Page$ /page/ permanent; # Explicitly matches /Page rewrite ^/page$ /page/ permanent; # Explicitly matches /page try_files $uri $uri/ =404; } - Why it works: Explicitly defining case handling in
rewriterules and ensuringlocationblocks match the intended case prevents Nginx from misinterpreting the URL’s casing and re-evaluating rules incorrectly.
3. Incomplete or Overlapping rewrite Rules:
- Diagnosis: You might have multiple
rewriterules that can match the same incoming URL, or arewriterule that doesn’t fully resolve the URL, leaving it in a state that matches another rule. This is particularly common with complex regular expressions. - Fix: Review your
rewriterules from top to bottom. Ensure that eachrewriterule has a clear termination condition (e.g.,lastorbreakif it’s meant to stop processing within the currentlocation, orpermanent/redirectif it’s a client-side redirect). Uselastto stop processingrewritedirectives in the currentlocationand start a new search for alocationmatching the rewritten URI. Usebreakto stop processingrewritedirectives in the currentlocationand continue processing in the samelocation.# Problematic: Two rules could potentially match and restart the cycle # rewrite ^/old/(.*)$ /new/$1 permanent; # rewrite ^/old-path$ /new-path/ permanent; # If /old-path is also matched by the first rule, ambiguity # Corrected: Use 'last' to break the rewrite chain and re-evaluate locations location /old/ { rewrite ^/old/(.*)$ /new/$1 last; # Rewrites and searches for a new location } location /new/ { # This block handles URIs starting with /new/ try_files $uri $uri/ =404; } # If a specific path needs a different rewrite, place it before or after based on priority location / { rewrite ^/specific-old-path$ /specific-new-path/ permanent; # Permanent redirect rewrite ^/another-old/(.*)$ /another-new/$1 last; # Internal rewrite try_files $uri $uri/ =404; } - Why it works: The
lastdirective tells Nginx to stop processingrewriterules in the currentlocationand restart the search for alocationthat matches the new URI. This prevents a cascade ofrewritedirectives within the samelocationblock, which is a common source of cycles.
4. Missing or Incorrect location Block for Rewritten URI:
- Diagnosis: A
rewriterule successfully changes the URI, but there’s no correspondinglocationblock to handle the new URI. Nginx, unable to find alocation, might re-evaluate the originalrewriterules, leading to a cycle. - Fix: Ensure that for every
rewriterule that changes a URI, there is alocationblock defined that will match the resulting URI.# Problematic: rewrite changes URI to /target, but no location /target/ exists # rewrite ^/source/(.*)$ /target/$1 permanent; # Corrected: Add a location for the rewritten URI location /source/ { rewrite ^/source/(.*)$ /target/$1 permanent; } location /target/ { # This block will now handle requests that were rewritten from /source/ try_files $uri $uri/ =404; } - Why it works: By providing a
locationblock that correctly handles the URI after arewrite, Nginx can process the request without needing to re-evaluate therewriterules that led to it, breaking the potential cycle.
5. Using rewrite Inside location ~* or location ~ Without last or break:
- Diagnosis: Regular expression
locationblocks (~for case-sensitive,~*for case-insensitive) can be tricky. If arewriterule within such a block doesn’t uselastorbreak, Nginx might continue processing otherrewritedirectives in the samelocationblock or even re-evaluate the originallocationblock with the modified URI, leading to a loop. - Fix: Always use
lastorbreakwhen usingrewriteinside a regular expressionlocationblock to control how Nginx proceeds after the rewrite.lastis generally preferred as it restarts the location search.# Problematic: rewrite inside ~* without last/break might loop # location ~* \.php$ { # rewrite ^/app/(.*)$ /index.php?q=$1; # No last/break, might re-evaluate itself or other rules # fastcgi_pass unix:/var/run/php-fpm.sock; # ... # } # Corrected: Use 'last' to break the rewrite chain and re-evaluate locations location ~* \.php$ { rewrite ^/app/(.*)$ /index.php?q=$1 last; # Use last to ensure Nginx searches for a new location for /index.php # If /index.php has its own location block, Nginx will find it. # If not, and you want this location to handle it: # try_files $uri =404; # Or use a specific fastcgi_pass for index.php # fastcgi_pass unix:/var/run/php-fpm.sock; # ... } # Example of a location for index.php itself location /index.php { fastcgi_pass unix:/var/run/php-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } - Why it works:
lastensures that therewritedirective terminates its processing within the currentlocationand that Nginx begins a new search for a matchinglocationbased on the rewritten URI. This prevents Nginx from re-evaluating the samelocationblock with the modified URI indefinitely.
6. Incorrectly Using return with a URI that Matches a rewrite Rule:
- Diagnosis: The
returndirective is intended to stop processing and return a specific status code or URL. If you usereturnto redirect to a URL that itself matches an activerewriterule, you’ll create a cycle. - Fix: Ensure that any URL specified in a
returndirective does not trigger anyrewriterules that would then redirect it back to the original URL or a URL that would cause anotherreturnto be evaluated in a loop.# Problematic: return redirects to /old-page, which is then rewritten back # rewrite ^/old-page$ /new-page permanent; # location / { # return 301 /old-page; # This will cause a cycle if /old-page is matched by the rewrite above # } # Corrected: Return a URL that doesn't trigger the rewrite, or perform the rewrite directly. location / { # Option 1: Perform the rewrite directly rewrite ^/old-page$ /new-page permanent; # Option 2: If you *must* use return, ensure the target doesn't trigger a loop # For example, if you wanted to redirect to a *different* page that doesn't have a rewrite rule # return 301 /another-page-without-rewrites; } - Why it works: The
returndirective halts processing immediately. If its target URL is designed not to re-engage the problematicrewriterule, the cycle is broken. Typically, it’s better to userewritefor internal redirects andreturnfor external or final redirects.
The next error you’ll likely encounter after fixing rewrite cycles is a 404 Not Found error if your rewrites lead to URIs that no location block can satisfy.