1

Recently we have moved to Nginx from Apache, Under apache it used to be very easy just put some stuff on .htaccess and done.

RewriteEngine on
RewriteBase /

# only rewrite if the requested file doesn't exist
RewriteCond %{REQUEST_FILENAME} !-s

# pass the rest of the request into index.php to handle
RewriteRule ^(.*)$ /index.php/$1 [L]

The above served great to clean URL and let index.php handle all requests. but in Nginx we needed to rewrite every unique URL in location block. However this is not 'automatic' like apache.

Few examples of our rewrite location block

location / {
try_files $uri $uri/ /index.php;
}

location /p {
rewrite ^/p(?:/([a-z_]+))?$ /index.php?p=$1 last;
rewrite ^/p/all_articles/user/(.*)?$ /index.php?p=all_articles&user=$1 last;
try_files $uri $uri/ /index.php;
}

location /about_us {
rewrite ^/about_us /index.php?about_us last;
try_files $uri $uri/ /index.php;
}

location /search {
rewrite ^/search/(.*) /index.php?search=$1;
rewrite ^/search/(.*)/page/(.*)?$ /index.php?search=$1&page=$2 last;
try_files $uri $uri/ /index.php;
}

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

the above does good job in clean URL, but when we need to get pages for example

/p/all_articles/user/ABC/page/2

/index.php?p=all_articles&user=ABC&page=2

we have tried

rewrite ^/p/all_articles/user/(.*)/pg(?:/([0-9]+))?$ /index.php?p=all_articles&user=$1&pg=$2 last;

this only works when we place in separate location block

location /page/all_articles {
rewrite ^/p/all_articles/user/(.*)/pg(?:/([0-9]+))?$ /index.php?p=all_articles&user=$1&pg=$2 last;
try_files $uri $uri/ /index.php;
}

and when done so, it would not let

/p/all_articles/user/ABC

to load.

also, search result pages would not work at all.


another issue we came across is on folder .htaccess

Order deny,allow
Deny from all
Options -Indexes

Under apache this would prevent any access to that folder and files except for php script. We tried,

location /(data|img)/ {
   deny all;
   return 404;
}

It does block the access to folder but, if you specify the filename, it will still serve, without denying access for example;

/data/backup_01012020.zip under apache .htaccess, only certain users were allowed to access this, while logged. and outside of it, apache will deny any access. But under nginx eventhough it gives 404 when trying to access /data/. Even when you are not logged, it would serve backup_01012020.zip file right away.

Now we can not figure out what we can do, which used to be piece of cake with apache. Our application is based on PHP and index.php is capable of handling all clean URL requests. It could have been great if Nginx simply pass all requests to index and let it handle instead of plenty of rewrites and location blocks. Any help would be great.

HelenSaepp
  • 11
  • 4

2 Answers2

2

You might be interested in the questions with the rewrite tag, since it contains many variations of your problem.

Your Apache rewrite rule:

RewriteRule ^(.*)$ /index.php/$1 [L]

appends the entire request URI to /index.php. In nginx the path of the URI (normalized) is available in the $uri variable. If you need the query arguments too, you can use the $request_uri instead.

A strict translation of your rewrite rules would therefore be:

location / {
    # Size zero static files are served.
    # I don't believe that is an issue.
    try_files $uri /index.php$request_uri;
}
# If no other .php files are accessible a prefix location of '/index.php/'
# is safer.
location /index.php/ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
    # Probably duplicates the contents of fastcgi-php.conf
    # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    # include fastcgi_params;
}

Your deny directive on the /(data|img)/ location does not work, since you are using a prefix match, instead of a regex match:

location ~ ^/(data|img)/ {
   # Only one is required
   deny all;
   # return 404;
}
Piotr P. Karwasz
  • 5,292
  • 2
  • 9
  • 20
  • Thank you for the answer, Tested your solution, It is giving me 500 Internal error. and restricting access to data folder still the same, when I try to access .zip file, I still am able to access without being logged. I agree with using exact location of index.php is a safer option. trying `try_files $uri $uri/ /index.php$uri;` will work only for those rewrite locations, rest of the things will end up in 500 internal error instead of index.php. – HelenSaepp Jan 15 '20 at 15:12
  • You should run `nginx -T` to check the configuration and look into the error log: `/var/log/nginx/error.log` – Piotr P. Karwasz Jan 15 '20 at 16:25
  • testing under lab condition now. this is the error: _rewrite or internal redirection cycle while internally redirecting to "/index.php/index.php/index.php/index.php/index.php/index.php/index.php/index.php/index.php/index.php/index.php/"_ ... And with, `location ~ ^/(data|img)/ ` is same output as `location /(data|img)/ ` or `location ^~ /(data|img)/ ` they all block the folder access but deliver the file when requested. `/data/backup.zip` – HelenSaepp Jan 15 '20 at 17:19
  • Sorry, nothing replaces testing the solution. I went to far with `location = /index.php`, `location /index.php/` in the edited solution works. You should comment all your own `location` blocks before testing. Something is overriding the `deny all` on the `/data` path. – Piotr P. Karwasz Jan 15 '20 at 17:58
  • Yes, agreed. I just did a clean server block based on your updated solution. and wow, I can see a lot of success with this now.rewrite location blocks are not necessary anymore, it does forward to index.php /p/all_articles/user/ABC or /p/all_articles/user/ABC/page/2 loads perfectly fine. Most of the links work as it suppose to be with clean URL except for search function, about_us and more importantly home or index page ends with 404, access log has access to `/index.php/ 404` and no errors. And, /data path is fixed. It does deny everything from the folder. – HelenSaepp Jan 15 '20 at 18:42
  • That probably means that your scripts do not encoded everything in the [path URI part](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Examples), but use the query string too. Replace `/index.php$uri` with `/index.php$request_uri` in the `try_files` directive. – Piotr P. Karwasz Jan 15 '20 at 18:56
  • I sincerely appreciate your help. I tried $request_uri, it seems to solve few more links, however index page stays 404 and search function as well, also I have noticed random redirect issues, captcha would not load either. tried many other possibilities and combinations, but now, I believe it has to do with my PHP script. Unfortunately I do not wanna edit the script in order to maintain flexibility to switch back to apache. And hence, I am left with only option, that is to rewrite location block for multiple query parameters. – HelenSaepp Jan 15 '20 at 21:11
  • Probably you don't have an `index index.php;` in your server block, so **nginx** is looking for something else. – Piotr P. Karwasz Jan 15 '20 at 21:48
  • It does have. After many trail and error, I zero down to `location /index.php/ {` and `try_files $uri /index.php$request_uri;` these 2 lines are associated in connection with 404 on index, search, captcha. /home works when specifically mentioned, so I am guessing index.php script has to do something with it. Also tried many combinations, with no luck to resolve it, yet. – HelenSaepp Jan 15 '20 at 22:06
  • Insert some `error_log` statements (e.g. `error_log(print_r($_SERVER), TRUE)` in your PHP script and check the nginx error log – Piotr P. Karwasz Jan 15 '20 at 22:11
  • I tried `error_log(print_r($_SERVER), TRUE)` and there was nothing which lead me to solve the issue I was having. I found an answer for rewrite, It was actually very simple will update it here. Your solution is very interesting and could have been great by avoiding 100s of rewrite lines, but my script seems like not compatible with the above approach so I had to stick with rewrite for now. I wish to mark both answers/approach as right answer. I appreciate your help. – HelenSaepp Jan 16 '20 at 16:35
0

Solution for rewrite

location /search {
rewrite ^/search/(.*)/page/(.*)?$ /index.php?search=$1&page=$2 last;
rewrite ^/search/(.*) /index.php?search=$1 last;
try_files $uri $uri/ /index.php;
}

location /p/all_articles {
rewrite ^/p/all_articles/user/(.*)/page(?:/([0-9]+))?$ /index.php?p=all_articles&user=$1&page=$2 last;
rewrite ^/p/all_articles/user/(.*)?$ /index.php?p=all_articles&user=$1 last;
try_files $uri $uri/ /index.php;
}

Notice, all I did was to interchange lines. Credits to Richard Smith


Thanks to Piotr P. Karwasz, for the the other solution, it might help someone whose script is 100% compatible to handle clean URL on it's own.

location / {
    # Size zero static files are served.
    # I don't believe that is an issue.
    try_files $uri /index.php$request_uri;
}
# If no other .php files are accessible a prefix location of '/index.php/'
# is safer.
location /index.php/ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
    # Probably duplicates the contents of fastcgi-php.conf
    # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    # include fastcgi_params;
}

the above solution is a way to go as long as your script work with clean URL 100%. Here, you do not need to put 100s of rewrite location blocks, and nginx will append the entire request URI to /index.php which is very interesting and helpful, probably this is the real solution, but in my case my script was not 100% compatible with this. Still this is a good, cleaver solution.


Soution for preventing folder, files access

location ~ ^/(data|img)/ {
   # Only one is required
   deny all;
   # return 404;
}

Credits to Piotr P. Karwasz pointed out, the deny all was being overridden by something, upon clean server block it did solve the issue. Also make sure to use either deny all; or return 404; but not together.

HelenSaepp
  • 11
  • 4