Rewrite before regex location

I am trying to migrate to Nginx + PHP-FPM from Apache + mod_php. I am
currently using mod_rewrite to rewrite some virtual URIs ending in .php
to
actual PHP scripts. This works perfectly when using mod_php. But with
with Nginx

  • FPM, since we have to use proxy_pass, this is not working. When I add
    a
    regex location block to match .php extension, it gets higher precedence,
    and my rewrite rules are not applied.

How can I resolve this?

location /test/ {
rewrite “^/test/([a-z]+).php$” test.php?q=$1 last; }

location ~ [^/].php(/|$) {
fastcgi_split_path_info ^(.+?.php)(/.*)$;

set $fastcgi_script_name_custom $fastcgi_script_name;
if (!-f $document_root$fastcgi_script_name) {
    set $fastcgi_script_name_custom "/cms/index.php";
}

fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;}

Thank,
Joyce B.

NB: This is a sample configuration. There are several such rewrite
rules,
hence I cannot use a exact match or regular expression location block to
override the php proxy location block.

On Thu, May 05, 2016 at 02:56:27AM +0530, Joyce B. wrote:

Hi there,

When I add a
regex location block to match .php extension, it gets higher precedence,
and my rewrite rules are not applied.

How can I resolve this?

location /test/ {
rewrite “^/test/([a-z]+).php$” test.php?q=$1 last; }

Possibly using “location ^~ /test/” would work?

http://nginx.org/r/location

You may want to rewrite to /test.php (with the leading slash), though.

Although, better might be to just fastcgi_pass directly, rather than
rewriting.

Something like (untested)

location ^~ /test/ {
fastcgi_param SCRIPT_FILENAME $document_root/test.php;
fastcgi_param QUERY_STRING q=$uri;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
}

although you would want to change the QUERY_STRING line to match what
you need; and you may need to switch the position of the “include”,
depending on how your fastcgi server handles repeated params.

location ~ [^/].php(/|$) {
fastcgi_split_path_info ^(.+?.php)(/.*)$;

set $fastcgi_script_name_custom $fastcgi_script_name;
if (!-f $document_root$fastcgi_script_name) {
    set $fastcgi_script_name_custom "/cms/index.php";
}

I suspect that it should be possible to do what you want to do there,
with a “try_files”. But I do not know the details.

Good luck with it,

f

Francis D. [email protected]

Hi Francis,

Thank you for the response.

Possibly using “location ^~ /test/” would work?

location ^~ /test/ {
fastcgi_param SCRIPT_FILENAME $document_root/test.php;
fastcgi_param QUERY_STRING q=$uri;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
}

although you would want to change the QUERY_STRING line to match what
you need; and you may need to switch the position of the “include”,
depending on how your fastcgi server handles repeated params.

There are over 300 rewrites under 54 location blocks.

I tried using ^~ as you suggested. Now the rewrite is working correctly,
but the files are not executed. The request is returning the actual PHP
source file, not the HTML generated by executing the script.

There is a CMS engine which will intercept all unmatched requests and
check
the database to see if there is an article with that URI. Some times it
has
to match existing directories without index.php. If I use try_files, it
will either lead to a 403 error (if no index is specified), or would
internally redirect the request to the index file (if it is specified),
leading to 404 error. The if condition correctly handles all the
non-existing files.

On Thu, May 05, 2016 at 04:22:11AM +0530, Joyce B. wrote:

Hi there,

Possibly using “location ^~ /test/” would work?

There are over 300 rewrites under 54 location blocks.

If you’ve got a messy config with no common patterns, you’ve got a messy
config with no common patterns, and there’s not much you can do about
it.

If you can find common patterns, maybe you can make the config more
maintainable (read: no top-level regex locations); but you don’t want
to break previously-working urls.

I tried using ^~ as you suggested. Now the rewrite is working correctly,
but the files are not executed. The request is returning the actual PHP
source file, not the HTML generated by executing the script.

Can you show one configuration that leads to the php content being
returned?

If you rewrite /test/x.php to /test.php, /test.php should be handled in
the “~ php” location.

An alternative possibility could be to put these rewrites at server
level rather than inside location blocks. That is unlikely to be great
for efficiency; but only you can judge whether it could be adequate.

There is a CMS engine which will intercept all unmatched requests and check
the database to see if there is an article with that URI. Some times it has
to match existing directories without index.php. If I use try_files, it
will either lead to a 403 error (if no index is specified), or would
internally redirect the request to the index file (if it is specified),
leading to 404 error. The if condition correctly handles all the
non-existing files.

There is more than one possible try_files configuration; but that does
not
matter: if you have a system that works for you, you can keep using it.

Good luck with it,

f

Francis D. [email protected]

If you’ve got a messy config with no common patterns, you’ve got a messy
config with no common patterns, and there’s not much you can do about it.

If you can find common patterns, maybe you can make the config more
maintainable (read: no top-level regex locations); but you don’t want
to break previously-working urls.

The site was initially using Apache + mod_php. Hence these ere not an
issue. It was only when
I tried to migrate to PHP-FPM, I realized the mistakes. Now the urls
cannot
be chanced due to
SEO implications.

I am sorry, I did not rewrite it to a location outside /test/, which was
why the file content was being returned.

Is it possible to do something like this?

location /test/ {
rewrite “^/test/([a-z]+).php$” /php-fpm/test/test.php?q=$1 last;
}

location ~ ^/php-fpm/ {
location ~ [^/].php(/|$) {
fastcgi_split_path_info ^/php-fpm(.+?.php)(/.*)$;

    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    include fastcgi_params;
}

}

What I have tried to do here is rewrite to add a special prefix
(/php-fpm)
to the rewritten urls. and nest the php location block within it. Then
use
fastcgi_split_path_info to create new $fastcgi_script_name without the
special prefix. I tried the above code, but it is not working.
fastcgi_split_path_info is not generating $fastcgi_script_name without
the
/php-fpm prefix.

On Thu, May 05, 2016 at 05:13:29AM +0530, Joyce B. wrote:

Hi there,

    fastcgi_pass 127.0.0.1:9000;

fastcgi_split_path_info is not generating $fastcgi_script_name without the
/php-fpm prefix.

That’s because your fastcgi_split_path_info pattern does not match -
.php is not followed by / in your rewritten url.

Because of the location{} you are in, it is probably simplest to just
replace the second capture part of that pattern with (.*)$.

Cheers,

f

Francis D. [email protected]

Hi ,

Can you try putting the rewrite in server{} block outside of all
location{} blocks like

rewrite “^/test/([a-z]+).php$” /test/test.php?q=$1 last;

On Thu, May 5, 2016 at 5:13 AM, Joyce B. [email protected] wrote:

I tried to migrate to PHP-FPM, I realized the mistakes. Now the urls cannot

returned?
location /test/ {
}

    set $fastcgi_script_name_custom "/cms/index.php";

will either lead to a 403 error (if no index is specified), or would

nginx mailing list
[email protected]
nginx Info Page


Anoop P Alias

Thank you for the suggestion, Anoop. I did not want to do that since it
would be evaluated for every request.

As a sidenote, why using
location ~ ^/php-fpm/
and not
location /php-fpm/
?

Although being distant to your case (I see others are helping you nicely
:o) ), I can but particularly note & enjoy the use of a non-greedy
modifier
in the regex part of fastcgi_split_path_info intended to result in
$fastcgi_script_name. That detail means a lot. Keep up the good work :o)
​​

B. R.

That’s because your fastcgi_split_path_info pattern does not match -
.php is not followed by / in your rewritten url.

Because of the location{} you are in, it is probably simplest to just
replace the second capture part of that pattern with (.*)$.

Thank you Francis. It worked after following your suggestions.

But I have found a different approach, that is working for all the cases
that I tested. As per your suggestions, I have removed the if statement
with try_files and have used the ~^ modifier to ensure that the location
blocks get higher precedence than the regex block.

  1. index index.php;
  2. location ~ /.ht {
  3. deny all;
    
  4. }
  5. .php catchall regex block

  6. location ~ [^/].php$ {
  7. # Handle .php uris, except when they are overridden below
    
  8. include inc/php-fpm.conf;
    
  9. }
  10. location / {
  11. if ($request_uri ~ "^[^?]*?//") {
    
  12.     # Remove double slashes from url
    
  13.     rewrite "^" $scheme://$host$uri permanent;
    
  14. }
    
  15. # Handle non .php files and directory indexes
    
  16. try_files $uri $uri/index.php;
    
  17. }
  18. location /generated/imgs/ {
  19. # Serve file if it exists, else use rewriting (for 
    

dynamically
generated and cached files)
25. # This ensures that rewrite rules are not applied to existing
files
26. try_files $uri @rewrite_generated_imgs;
27. }
28.
29. location @rewrite_generated_imgs {
30. # Since extension is changed from .jpg to .php, use last.
31. # This will pass the rewritten uri to the .php regex block
for
execution
32.
33. rewrite “^/generated/imgs/([0-9]+)/(.).jpg$"
/generated/imgs/pic.php?width=$1&height=$1&pic=$2&mode=proportionate
last;
34. rewrite "^/generated/imgs/([0-9]+)x([0-9]+)/(.
).jpg$”
/generated/imgs/pic.php?width=$1&height=$2&pic=$3&mode=exact last;
35.
36. try_files $uri $uri/index.php;
37. }
38.
39. # The ^~ modifier ensures that .php uris are not caught by the
.php
catchall regex block
40. location ^~ /banking/ {
41. rewrite “^/banking/sitemap-(\d+).xml.gz$”
/banking/sitemap-generator.php?id=$1 last;
42.
43. try_files $uri $uri/index.php;
44.
45. location ~ [^/].php$ {
46. rewrite “^/banking/([A-Za-z0-9-]+)/branches.php$”
/banking/branches.php?name=$1 break;
47. rewrite “^/banking/([A-Za-z0-9-]+)/atm.php$”
/banking/atm.php?name=$1 break;
48.
49. include inc/php-fpm.conf;
50. }
51. }
52.
53. location ^~ /news/ {
54. # Check for file before applying rewrite rules. If not,
rewrite
55. # This ensures that rewrite rules are not applied to existing
files
56. try_files $uri $uri/index.php @rewrite_news;
57. }
58.
59. location @rewrite_news {
60. # Rewrite non PHP urls in outer location block.
61. # Use last so that it will be passed to the .php regex block
after rewrite.
62. # This ensures that the php code is executed and not
downloaded
as is
63.
64. rewrite “^/news/articles/?$” /news/ permanent;
65. rewrite “^/news/articles/a([0-9]+).html$”
/news/article.php?id=
$1&mode=article last;
66. rewrite “^/news/page-([0-9]+).html$” /news/index.php?cat=
home&page=$1 last;
67. rewrite “^/news/([a-z-]+)/rss.xml$” /news/rss/$1.xml break;
68.
69. try_files $uri $uri/index.php;
70.
71. location ~ [^/].php$ {
72. # Rewrite PHP urls
73. # Use break so that the rewrite rules are not applied
again
74.
75. rewrite “^/news/print.php$” /news/article.php?mode=print
break;
76.
77. include inc/php-fpm.conf;
78. }
79. }
80.
81. ================
82. # inc/php-fpm.conf
83.
84. # /cms/cms.php is the fallback script that will handle all
missing
uris
85. try_files $uri /cms/cms.php =404;
86.
87. fastcgi_pass 127.0.0.1:9000;
88. fastcgi_index index.php;
89. include fastcgi_params;

Does this look fine? Is it a bad idea to use named locations like this?

It is working fine on my mac. But when I try to use it on my Cent OS
server, it is failing with error

nginx: [emerg] location “[^/].php$” cannot be inside the named location
@rewrite_news

Both are running version 1.10.0 :frowning:

Oops! The local configuration was location ^~ @rewrite_news {…}

See Module ngx_http_core_module

‘The “@” prefix defines a named location. Such a location is not used
for a
regular request processing, but instead used for request redirection.
They
cannot be nested, and cannot contain nested locations.’