The ProblemYou go to check your site, but instead of the homepage, your browser hits you with a frustrating ERR_TOO_MANY_REDIRECTS message. When you dig into your Nginx error logs (usually at /var/log/nginx/error.log), you will likely see a specific entry like this:
2023/10/24 10:15:30 [error] 1234#0: *1 rewrite or internal redirection cycle while internally redirecting to "/index.php", client: 192.168.1.1, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
Nginx has a safety limit of 10 internal redirects. If a request gets stuck in a loop and never finds a final file to serve, Nginx kills the process to prevent your server's CPU from Maxing out.
Step 1: Pinpoint the Loop with CurlA quick curl test reveals whether the loop lives in the browser's logic or deep inside Nginx. Run this command from your terminal:
curl -I http://example.com
Look closely at the results. If you see dozens of HTTP/1.1 301 Moved Permanently headers jumping between HTTP and HTTPS, you have a logical redirect loop. However, if you see a 500 Internal Server Error, you are dealing with the internal rewrite cycle mentioned in your logs.
Step 2: Audit the try_files DirectiveMost PHP applications—like Laravel, WordPress, or Symfony—rely on the try_files directive to handle URLs. A typical, healthy configuration looks like this:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
The cycle starts when Nginx cannot find index.php in your root folder. Because the file is missing, Nginx falls back to the last parameter, which triggers the location ~ \.php$ block, fails again, and restarts the search. Double-check your root path. If your app lives in /var/www/my-app/public but Nginx is looking in /var/www/my-app, it will never find the entry file.
server {
listen 80;
server_name example.com;
root /var/www/html/public; # Ensure this path contains index.php
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
}
}
Step 3: Resolve SSL Termination LoopsAre you using Cloudflare or an AWS Load Balancer? You might be caught in an SSL "ping-pong" match. This happens when your proxy talks to Nginx via port 80 (HTTP), but Nginx insists on redirecting everything to HTTPS. The proxy sends a request, Nginx says "Go to HTTPS," and the proxy sends it back as HTTP again.
To stop this, use the X-Forwarded-Proto header to detect the original protocol. Update your config to only redirect if the request wasn't already secure:
# Add this inside your server block
if ($http_x_forwarded_proto != "https") {
return 301 https://$host$request_uri;
}
Step 4: Use a 'Circuit Breaker' for PHPSometimes Nginx gets confused about which script to execute if the SCRIPT_FILENAME is missing. Adding a circuit breaker is the best way to stop a loop cold. Add try_files $fastcgi_script_name =404; inside your PHP block. This tells Nginx to stop and throw a 404 error if the file isn't found, rather than attempting another internal redirect.
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# This line breaks the loop if the file is missing
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/var/run/php/php-fpm.sock;
}
Step 5: Verify and ReloadTest your changes before you apply them to avoid taking your site offline. Run the Nginx syntax check:
sudo nginx -t
If the test passes, reload the service to put the changes into effect:
sudo systemctl reload nginx
Finally, run curl -I again. You should now see a clean HTTP/1.1 200 OK or a single 301 redirect followed by a 200 status code.

