10.1.x breaks Nginx + PHP-FPM with too many redirects even when nginx config is changed

Created on 22 June 2023, over 1 year ago
Updated 11 April 2024, 6 months ago

Problem/Motivation

After updating Drupal 10.0.x to Drupal 10.1.x on Nginx with PHP-FPM, CSS/JS stops working entirely with the following errors in the browser:

/sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css?q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&q=sites/default/files/css/css_fJbzVNElaoIiLehgnxKy0d76upZOKPJlrn6jSuE13B4.css&delta=0&language=en&theme=gin&include=eJx9UltywyAMvJAbn6En8QihOJpgYCSc1jl9ZWycup32g0FaLasHwiTUe5kzhEuWNAqpdphioc8yQ2ihF_IWON6100ULTb0DpQ4DSOoRxDcY_MTRZEw7Jpkg8LPRxpCcqWhZTGjsRo59AafV8AwhbZidoYqvTqARcBlQtcU27b04wMIPqoV1VuR6t9DuHpKASLFUF9P1Spt-TJ6GLPRg-qiATSGnqKZ6gl9TGLYprKATAo8yT666SqVYYzoUgeU3MpDncpRzS8JPEzXBklJwIEeIo_G-d-lZcwDcKtbCeF_auHfG5p0eXW36JwBTCJBtrnu-vuWdlaSRVvvC1q2e3to_OWtkgggjyf9b0mTrJrTm6q0tZc1wJhyhIvY1f4cut_QgYctmn_mTQIqQ6b2uoONxyJypb8YX6_sj-w:1 Failed to load resource: net::ERR_TOO_MANY_REDIRECTS

Steps to reproduce

Use Drupal 10.0.x setup that worked previously on Nginx and update to 10.1.x. Changes noted in https://www.drupal.org/node/2888767#nginx-php-fpm were applied.

If JS and CSS aggregation is disabled, the website operates normally.

Configuration based on https://www.nginx.com/resources/wiki/start/topics/recipes/drupal/

🐛 Bug report
Status

Closed: works as designed

Version

10.1

Component
Asset library 

Last updated about 22 hours ago

No maintainer
Created by

🇸🇮Slovenia KlemenDEV

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @KlemenDEV
  • 🇬🇧United Kingdom catch
  • 🇬🇧United Kingdom catch

    @KlemenDEV can you confirm whether you're running advagg or not?

    Also could you try the ddev version of the aggregate config:

    https://github.com/ddev/ddev/blob/master/pkg/ddevapp/webserver_config_as...

    # js and css always loaded
        location ~* \.(js|css)$ {
            try_files $uri @rewrite;
            expires -1;
            log_not_found off;
        }
    

    (you'll want to remove the expires -1; line on production once it's confirmed as working I think.

  • 🇸🇮Slovenia KlemenDEV

    The website was initially running advagg but I uninstalled the module prior to updating to Drupal 10.1.x.

    I will spin another dev instance tomorrow and try it out

  • 🇸🇮Slovenia KlemenDEV

    But seems something happens on the Drupal HTML generation level already as those long URLs are also present in the website HTML source.

  • 🇷🇴Romania idflorin

    This also happened to me and I changed in the https://www.nginx.com/resources/wiki/start/topics/recipes/drupal/
    to

        location / {
            try_files $uri $uri/ /index.php?$query_string; # For Drupal >= 7
        }
        location @rewrite {
            # For D7 and above:
            # Clean URLs are handled in drupal_environment_initialize().
            rewrite ^ /index.php;
        }

    and now it works with Nginx 1.24.0.
    I don't use advagg.

  • 🇬🇧United Kingdom catch

    @idflorin are you able to post a diff for the change you made?

  • 🇷🇴Romania idflorin

    @catch
    before

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    add_header X-Permitted-Cross-Domain-Policies "none";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
    # Allow "Well-Known URIs" as per RFC 5785
    location ~* ^/.well-known/ {
        allow all;
    }
    #remove # for ssl renew
    if ($host = carthermostats.com) {return 301 https://www.carthermostats.com$request_uri;}
    
    #https://www.nginx.com/resources/wiki/start/topics/recipes/drupal/
    location = /12e3o302=op302cache/ {allow all;log_not_found off;access_log off;}
    location = /ads.txt {allow all; log_not_found off; access_log off;}
    location = /robots.txt {allow all; log_not_found off; access_log off;}
    location = /favicon.ico {allow all; expires max; log_not_found off; access_log off;}
    # Very rarely should these ever be accessed outside of your lan
    location ~* \.(txt|log)$ { allow 192.168.0.0/16; deny all; }
    
    location ~ \..*/.*\.php$ {
        return 403;
    }
    
    location ~ ^/sites/.*/private/ {
    	return 403;
    }
    
    # Block access to scripts in site files directory
    location ~ ^/sites/[^/]+/files/.*\.php$ {
    	deny all;
    }
    
    # Allow "Well-Known URIs" as per RFC 5785
    location ~* ^/.well-known/ {
        allow all;
    }
    
    # Block access to "hidden" files and directories whose names begin with a
    # period. This includes directories used by version control systems such
    # as Subversion or Git to store control files.
    location ~ (^|/)\. {
        return 403;
    }
    
    location / {
        # try_files $uri @rewrite; # For Drupal <= 6
        try_files $uri /index.php?$query_string; # For Drupal >= 7
    }
    
    location @rewrite {
        rewrite ^/(.*)$ /index.php?q=$1;
    }
    
    # Don't allow direct access to PHP files in the vendor directory.
    location ~ /vendor/.*\.php$ {
    	deny all;
    	return 404;
    }
    
    # Protect files and directories from prying eyes.
    location ~* \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|composer\.(lock|json)$|web\.config$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ {
    	deny all;
    	return 404;
    }
    
        # In Drupal 8, we must also match new paths where the '.php' appears in
        # the middle, such as update.php/selection. The rule we use is strict,
        # and only allows this pattern with the update.php front controller.
        # This allows legacy path aliases in the form of
        # blog/index.php/legacy-path to continue to route to Drupal nodes. If
        # you do not have any paths like that, then you might prefer to use a
        # laxer rule, such as:
        #   location ~ \.php(/|$) {
        # The laxer rule will continue to work if Drupal uses this new URL
        # pattern with front controllers other than update.php in a future
        # release.
        location ~ '\.php$|^/update.php' {
            fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
            # Ensure the php file exists. Mitigates CVE-2019-11043
            try_files $fastcgi_script_name =404;
            # Security note: If you're running a version of PHP older than the
            # latest 5.3, you should have "cgi.fix_pathinfo = 0;" in php.ini.
            # See http://serverfault.com/q/627903/94922 for details.
            include fastcgi_params;
            # Block httpoxy attacks. See https://httpoxy.org/.
            fastcgi_param HTTP_PROXY "";
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param QUERY_STRING $query_string;
            fastcgi_intercept_errors on;
            # PHP 5 socket location.
            #fastcgi_pass unix:/var/run/php5-fpm.sock;
            # PHP 7 socket location.
            #fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        }
    
    location ~* \.(js|css|webp|png|jpg|jpeg|gif|ico|svg|eot|ttf|woff|woff2)$ {
    	try_files $uri @rewrite;
    	etag on;
    	expires 365d;
    	add_header Pragma "public";
    	add_header Cache-Control "public, max-age=31536000, immutable";
    	#log_not_found off;
    }
    #location ~ ^/sites/.*/files/(css|js)/optimized { add_header Cache-Control 'public, max-age=31536000, immutable'; }
    
    # Fighting with Styles? This little gem is amazing.
    location ~ ^/sites/.*/files/styles/ {
    	try_files $uri @rewrite;
    }
    
    # Handle private files through Drupal. Private file's path can come
    # with a language prefix.
    location ~ ^(/[a-z\-]+)?/system/files/ { # For Drupal >= 7
    	try_files $uri /index.php?$query_string;
    }
    
    # Enforce clean URLs
    # Removes index.php from urls like www.example.com/index.php/my-page --> www.example.com/my-page
    # Could be done with 301 (was 307) for permanent or other redirect codes.
    if ($request_uri ~* "^(.*/)index\.php(.*)") {
    	return 301 $1$2;
    }

    after

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    add_header X-Permitted-Cross-Domain-Policies "none";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
    # Allow "Well-Known URIs" as per RFC 5785
    location ~* ^/.well-known/ {
        allow all;
    }
    #remove # for ssl renew
    if ($host = carthermostats.com) {return 301 https://www.carthermostats.com$request_uri;}
    
    #https://www.nginx.com/resources/wiki/start/topics/recipes/drupal/
    location = /12e3o302=op302cache/ {allow all;log_not_found off;access_log off;}
    location = /ads.txt {allow all; log_not_found off; access_log off;}
    location = /robots.txt {allow all; log_not_found off; access_log off;}
    location = /favicon.ico {allow all; expires max; log_not_found off; access_log off;}
    # Very rarely should these ever be accessed outside of your lan
    location ~* \.(txt|log)$ { allow 192.168.0.0/16; deny all; }
    
    location ~ \..*/.*\.php$ {
        return 403;
    }
    
    location ~ ^/sites/.*/private/ {
    	return 403;
    }
    
    # Block access to scripts in site files directory
    location ~ ^/sites/[^/]+/files/.*\.php$ {
    	deny all;
    }
    
    # Allow "Well-Known URIs" as per RFC 5785
    location ~* ^/.well-known/ {
        allow all;
    }
    
    # Block access to "hidden" files and directories whose names begin with a
    # period. This includes directories used by version control systems such
    # as Subversion or Git to store control files.
    location ~ (^|/)\. {
        return 403;
    }
    
    location / {
    	try_files $uri $uri/ /index.php?$query_string; # For Drupal >= 7
    }
    
    location @rewrite {
    	# For D7 and above:
    	# Clean URLs are handled in drupal_environment_initialize().
    	rewrite ^ /index.php;
    }
    
    # Don't allow direct access to PHP files in the vendor directory.
    location ~ /vendor/.*\.php$ {
    	deny all;
    	return 404;
    }
    
    # Protect files and directories from prying eyes.
    location ~* \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|composer\.(lock|json)$|web\.config$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ {
    	deny all;
    	return 404;
    }
    
        # In Drupal 8, we must also match new paths where the '.php' appears in
        # the middle, such as update.php/selection. The rule we use is strict,
        # and only allows this pattern with the update.php front controller.
        # This allows legacy path aliases in the form of
        # blog/index.php/legacy-path to continue to route to Drupal nodes. If
        # you do not have any paths like that, then you might prefer to use a
        # laxer rule, such as:
        #   location ~ \.php(/|$) {
        # The laxer rule will continue to work if Drupal uses this new URL
        # pattern with front controllers other than update.php in a future
        # release.
        location ~ '\.php$|^/update.php' {
            fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
            # Ensure the php file exists. Mitigates CVE-2019-11043
            try_files $fastcgi_script_name =404;
            # Security note: If you're running a version of PHP older than the
            # latest 5.3, you should have "cgi.fix_pathinfo = 0;" in php.ini.
            # See http://serverfault.com/q/627903/94922 for details.
            include fastcgi_params;
            # Block httpoxy attacks. See https://httpoxy.org/.
            fastcgi_param HTTP_PROXY "";
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param QUERY_STRING $query_string;
            fastcgi_intercept_errors on;
            # PHP 5 socket location.
            #fastcgi_pass unix:/var/run/php5-fpm.sock;
            # PHP 7 socket location.
            #fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        }
    
    location ~* \.(js|css|webp|png|jpg|jpeg|gif|ico|svg|eot|ttf|woff|woff2)$ {
    	try_files $uri @rewrite;
    	etag on;
    	expires 365d;
    	add_header Pragma "public";
    	add_header Cache-Control "public, max-age=31536000, immutable";
    	#log_not_found off;
    }
    #location ~ ^/sites/.*/files/(css|js)/optimized { add_header Cache-Control 'public, max-age=31536000, immutable'; }
    
    # Fighting with Styles? This little gem is amazing.
    location ~ ^/sites/.*/files/styles/ {
    	try_files $uri @rewrite;
    }
    
    # Handle private files through Drupal. Private file's path can come
    # with a language prefix.
    location ~ ^(/[a-z\-]+)?/system/files/ { # For Drupal >= 7
    	try_files $uri /index.php?$query_string;
    }
    
    # Enforce clean URLs
    # Removes index.php from urls like www.example.com/index.php/my-page --> www.example.com/my-page
    # Could be done with 301 (was 307) for permanent or other redirect codes.
    if ($request_uri ~* "^(.*/)index\.php(.*)") {
    	return 301 $1$2;
    }

    it's a bit messy but it works

  • 🇸🇮Slovenia KlemenDEV

    So we had

    rewrite ^/(.*)$ /index.php?q=$1;

    and changing it to

    rewrite ^ /index.php;

    fixed the problem.

  • 🇬🇧United Kingdom catch

    OK that is in the Drupal 6 section of https://www.nginx.com/resources/wiki/start/topics/recipes/drupal/ commented out. Are you aware of documentation that actually recommends rewrite ^/(.*)$ /index.php?q=$1;?

  • 🇸🇮Slovenia KlemenDEV

    At https://www.nginx.com/resources/wiki/start/topics/recipes/drupal/,

    for Drupal 6 they recommend:

    rewrite ^/(.*)$ /index.php?q=$1;

    and is what we have been accidentally using so far (code with explicit q query).

    rewrite ^ /index.php

    is recommended for Drupal 7+ and worked (code without explicit q query).

    Are you aware of documentation that actually recommends rewrite ^/(.*)$ /index.php?q=$1;?

    According to nginx.com, they don't recommend that (explicit q query) for D7+. What documentation are you referring to?

    What are the differences and implications between these two?

  • Status changed to Closed: works as designed over 1 year ago
  • 🇮🇹Italy FiNeX

    Thanks to #11 I've solved my problem, until 10.0.x it worked flawlessly with the previous nginx settings. Please update the docs.

  • 🇺🇸United States bwoods

    Thanks for this - #11 also worked for me using a custom conf file with Lando.

  • 🇺🇸United States areynolds

    Thanks! #11 works for me and big thanks to @ericgsmith for submitting this as a PR to the Lando Drupal recipe, should be rolled out shortly in Lando's next release.

  • 🇳🇱Netherlands gopisathya

    #11 solved the problem for us. We use css/js aggregation together with advagg.

  • 🇩🇪Germany kreatIL

    Yes, #11 did the trick.

  • 🇹🇷Turkey Umit Turkey

    #11 worked but I'm not sure it's the correct method. Thanks.

  • Yes, #11 worked for me also. Thanks !

  • 🇺🇾Uruguay rpayanm

    Could you check if this solution fixes this issue?

    https://www.drupal.org/project/redirect/issues/3373123#comment-15300077 🐛 Setting 'Enforce clean and canonical URLs.' breaks CSS aggregation on multilingual Drupal 10.1.x with browser caching enabled RTBC

  • 🇫🇷France Nicolas Ebiz

    Hello @rpayanm, your solution worked for me!

Production build 0.71.5 2024