Nginx rewrite rule results in error 403 - nginx

I am trying to rewrite a url like http://example.com/extras/?cat=help&page=faq to something along the lines of http://example.com/extras/help/faq.
Well, rewrite as in a link or a user types the latter and the server understands that it's supposed to be the former. However, after implementing the rewrite that is ostensibly supposed to do what I want, navigating to pages results in a 403 code, because the server is taking what's in the URL bar literally. Because I have the server set up to disallow direct access to the subfolders, the 403 code is returned.
Below is the php code on my site that handles loading the pages to be included within the /extras index.php:
if(!empty($_GET['cat']) && !empty($_GET['page'])) {
$folder = $_GET['cat'];
$page = $_GET['page'] . '.php';
$pages = scandir($folder);
unset($pages[0], $pages[1]);
$url .= $folder . DIRECTORY_SEPARATOR;
if(file_exists($url . $page) && in_array($page, $pages)) {
$url .= $page;
include($url);
} else {
//Invalid category or page given
header("HTTP/1.0 404 Not Found");
}
} else {
//No category or page given; fall back to contents
include("contents.php");
}
The goal of the above file is for the contents of the subfile in the subfolders to be included in the body of index.php, not for the browser to actually try and navigate to the subfile.
This is (part of) the nginx config:
server {
listen 80 default_server;
listen [::]:80 ipv6only=on;
root /usr/share/nginx/html;
server_name localhost;
error_page 403 /;
error_page 404 /error/404.php;
error_page 500 502 503 504 /error/50X.php;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ #no-extension;
allow 192.168.0.0/24;
allow 127.0.0.1;
deny all;
}
location /help {
try_files $uri $uri/ #help #no-extension;
}
location #help {
rewrite "^/help/([^/]*)/([^/]*)$" /help/?cat=$1&page=$2 last;
}
location ~ /help/(help|otherfolder|morefolders) {
deny all;
}
# PHP Handler
location ~ \.php$ {
try_files $uri $uri/ =404;
include fastcgi_params;
fastcgi_pass php5-fpm-sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_intercept_errors on;
}
location #no-extension {
rewrite ^(.*)$ $1.php last;
}
}
Any way to make the rewrite take effect instead of the browser trying to access a file it will never be able to?

while not exactly answering your question, would it not be simpler to
location /help {
try_files $uri $uri/ /index.php;
when you load /help/faq/ its passed in REQUEST_URI to /index.php which does the subsequent regex split and file include...no need to rewrite. specific location blocks you want to restrict should be place above, so the deny will match first (if i recall correctly)

Related

How can I define a custom 404 error page for a subdirectory?

I have a simple website with some PHP files in two languages: the main language and the translation, let's say Spanish.
I want to show a 404 error page for the main language if someone tries to load an non-existing URL, and a translated 404 error page for non-existing URLs in the /es subdirectory.
This is my config file:
server {
listen 80;
server_name example.com;
root /var/www/example.com;
location / {
error_page 404 /404.php;
try_files $uri $uri/ #extensionless-php;
index index.php;
}
location /es {
error_page 404 /es/404.php;
try_files $uri $uri/ #extensionless-php;
index index.php;
}
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location #extensionless-php {
rewrite ^(.*)$ $1.php last;
}
}
It is not working as expected. When loading a non-existing URL (whether in the root or in the /es subdirectory) nginx returns the standard 404 page (the one displaying 404 Not Found).
If I add error_page 404 /404.php; in the server block (outside location blocks), then example.com/foo and example.com/es/foo would return the non-translated 404 error page.
How can I fix this?
Any of your requests for the non-existent resources ends up at the location ~ \.php$ { ... } block where you do not have error_page directive at all. If you define your custom error page at the server level, that location inherits that page. You can define two PHP handlers (order of location blocks matters!):
location ~ ^/es.+\.php$ {
error_page 404 /es/404.php;
... # other content is the same as in the question
}
location ~ \.php$ {
error_page 404 /404.php;
... # other content is the same as in the question
}
or better use a map block like
map $uri $errpage {
~^/es /es/404.php;
default /404.php;
}
server {
...
error_page 404 $errpage;
}

In NGINX 'location ~ .*' does not match any string

In the following site config file i am trying to:
Load index.php on the first visit.
Load files as they appear in the /uploads/ directory.
Any other request should still be handled by index.php.
server {
listen 80 default_server;
listen [::]:80 default_server;
root /home/va/www/example;
# Add index.php to the list if you are using PHP
# index index.php;
server_name example.dev;
location ~ .*
{
try_files /dev/null #php;
}
location /uploads/
{
try_files $uri =404;
expires 30d;
}
location #php
{
include snippets/fastcgi-php.conf;
#pretty url
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
}
However, the last part breaks. In other words:
/ works
/?test=test works
/uploads/test.jpeg works
/random_string does not work and returns 404 error
My understanding is that location ~ .* has no choice but to match everything. Where is the 404 error coming from?
Ok. After working on it some more, and completely rewriting the logic, i have gotten a muse from The MediaWiki Nginx guide. The final solution is:
server {
listen 80 default_server;
listen [::]:80 default_server;
root /home/va/www/example;
server_name example.dev;
location = /
{
return 301 /home;
}
location = /index.php
{
include snippets/fastcgi-php.conf;
#pretty url
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
location /uploads/
{
try_files $uri =404;
expires 30d;
}
location /
{
rewrite ^/(?<pagename>.*)$ /index.php;
}
location /classes/ { deny all; }
location /config/ { deny all; }
}
I will then use $_SERVER['REQUEST_URI'] within PHP to route the request. Hopefully this solution will help somebody.

Nginx deny doesn't work for folder files

I'm trying to restrict access to my site to allow only specific IPs and I've got the following problem: when I access www.example.com deny works perfectly, but when I try to access www.example.com/index.php it returns "Access denied" page AND php file is downloaded directly in browser without processing.
I do want to deny access to all the files on the website for all IPs but mine. How should I do that?
Here's the config I have:
server {
listen 80;
server_name example.com;
root /var/www/example;
location / {
index index.html index.php; ## Allow a static html file to be shown first
try_files $uri $uri/ #handler; ## If missing pass the URI to front handler
expires 30d; ## Assume all files are cachable
allow my.public.ip;
deny all;
}
location #handler { ## Common front handler
rewrite / /index.php;
}
location ~ .php/ { ## Forward paths like /js/index.php/x.js to relevant handler
rewrite ^(.*.php)/ $1 last;
}
location ~ .php$ { ## Execute PHP scripts
if (!-e $request_filename) { rewrite / /index.php last; } ## Catch 404s that try_files miss
expires off; ## Do not cache dynamic content
fastcgi_pass 127.0.0.1:9001;
fastcgi_param HTTPS $fastcgi_https;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params; ## See /etc/nginx/fastcgi_params
}
}
That is because your deny/allow rule applies to just one location.
Remove that and try:
server {
listen 80;
server_name example.com;
root /var/www/example;
if ($remote_addr != "YOUR.PUBLIC.IP") {return 403;}
...
}
As the test is outside any specific locationblock, it will apply to all cases.
Note also that IF is not evil here since it just "returns".
OK, so I've found the solution. Nginx processes the most exact regex which in this case is the regex for php files. To make the config work all further locations must be defined within / location rule except for #handler (you cannot put under any rule - only as root)
server {
listen 80;
server_name example.com;
root /var/www/example;
location / {
index index.html index.php; ## Allow a static html file to be shown first
try_files $uri $uri/ #handler; ## If missing pass the URI to front handler
expires 30d; ## Assume all files are cachable
allow my.public.ip;
deny all;
location ~ .php/ { ## Forward paths like /js/index.php/x.js to relevant handler
rewrite ^(.*.php)/ $1 last;
}
location ~ .php$ { ## Execute PHP scripts
if (!-e $request_filename) { rewrite / /index.php last; } ## Catch 404s that try_files miss
expires off; ## Do not cache dynamic content
fastcgi_pass 127.0.0.1:9001;
fastcgi_param HTTPS $fastcgi_https;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params; ## See /etc/nginx/fastcgi_params
}
}
location #handler { ## Common front handler
rewrite / /index.php;
}
}

nginx change root folder for specific url

I have a configuration file like this one below:
server {
listen 80;
server_name localhost;
#charset utf-8;
root html/laravel/public;
index index.html index.php;
#browse folders if no index file
autoindex on;
# enforce NO www
if ($host ~* ^www\.(.*))
{
set $host_without_www $1;
rewrite ^/(.*)$ $scheme://$host_without_www/$1 permanent;
}
# serve static files directly
location ~* \.(jpg|jpeg|gif|css|png|js|ico|html)$ {
access_log off;
#expires max;
}
# removes trailing slashes (prevents SEO duplicate content issues)
if (!-d $request_filename)
{
rewrite ^/(.+)/$ /$1 permanent;
}
# canonicalize codeigniter url end points
# if your default controller is something other than "welcome" you should change the following
# if ($request_uri ~* ^(/lobby(/index)?|/index(.php)?)/?$)
# {
# rewrite ^(.*)$ / permanent;
# }
# removes trailing "index" from all controllers
if ($request_uri ~* index/?$)
{
rewrite ^/(.*)/index/?$ /$1 permanent;
}
# unless the request is for a valid file (image, js, css, etc.), send to bootstrap
if (!-e $request_filename)
{
rewrite ^/(.*)$ /index.php?/$1 last;
break;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /backend/ {
root /html/frontend;
}
location ~ \.php$ {
include fastcgi.conf;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
# catch all
# error_page 404 /index.php;
# location ~ \.php$ {
# try_files $uri =404;
# fastcgi_pass unix:/tmp/php.socket;
# fastcgi_index index.php;
# #include fastcgi_params;
# include /home/tamer/code/nginx/fastcgi_params;
# }
# access_log /home/tamer/code/laravel/storage/logs.access.log;
# error_log /home/tamer/code/laravel/storage/logs.error.log;
}
I have to change root folder to html/backend for any url with $host/backend/. All rules for load pages have to be the same, only root folder have to change.
How can I do that?
server {
location / {
root /data/www;
}
location /images/ {
root /data;
rewrite ^/images/(.+?)$ $1 break; #following is the explation
}
}
use break to continue; the root in location will take effect
use last to internal simulate request; the root in location will not take effect
use permanent to 301 redirect;
use redirect to 302 redirect;
adding 127.0.0.1 to server_name to be able to use the link you provided in the comment 127.0.0.1
server_name localhost 127.0.0.1;
also you still need to have the backend location with root inside it.
location /backend/ {
root /html/backend;
}
I'll take a wild guess here:
location /backend/ {
root /html/backend;
try_files $uri $uri/ /index.php?_url=$uri&$args;
}
This means: all requests to .../backend/* will be redirected to the location block of php followed after:
location ~ \.php${ ... }
and php will handle those requests as backend scripts
Nginx Beginner's Guide has this example:
server {
location / {
root /data/www;
}
location /images/ {
root /data;
}
}
So in theory this should work for you:
server {
listen 80;
server_name localhost;
location / {
root html/laravel/public;
}
location /backend/ {
root html/backend;
}
# common config goes here
}
If I understood the question correctly you can use alias to change just the OS search path for a specific location:
Defines a replacement for the specified location. For example, with the following configuration on request of “/i/top.gif”, the file /data/w3/images/top.gif will be sent.
location /i/ {
alias /data/w3/images/;
}
You need to define new location and use alias instead of root or else the behaviour would be funky. Also you need to define location for .php to use $request_filename.
location /backend {
alias /html/backend;
try_files $uri $uri/ /index.php$is_args$args;
location ~ \.php$ {
fastcgi_param SCRIPT_FILENAME $request_filename;
}
}

Nginx proxy_pass to another port with Wordpress

My problem is following: I use Wordpress on Nginx with "pretty links". I also run 2 other services on ports 88 and 1234 and I want to make a subdomains bugs.mydomain and mail.mydomain. I did the proxypass on location / but it's working only for the main directory, anything that is after the domain/ is falling into Wordpress "pretty links" mechanism. Do you have any idea how to solve this? My config files below:
The server config:
server {
listen <IP>:80;
root /usr/share/nginx/www/domain;
index index.html index.htm index.php;
server_name domain www.domain;
location / {
try_files $uri $uri/ /index.html;
if ( $host ~ "bugs.domain" ) {
proxy_pass http://domain:88;
}
if ( $host ~ "mail.domain" ) {
proxy_pass http://domain:1234;
}
}
location /doc/ {
alias /usr/share/doc/;
autoindex on;
allow 127.0.0.1;
deny all;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
include /home/domain/public_html/nginx.conf;
}
the config for specified domain (with Wordpress):
#First there is many rewrites for the W3TC plugin, like minification, caches etc
if ($host ~* ^www\.(.*))
{
set $host_without_www $1;
rewrite ^/(.*)$ $scheme://$host_without_www/$1 permanent;
}
#
# unless the request is for a valid file, send to bootstrap
if (!-e $request_filename)
{
rewrite ^(.+)$ /index.php?q=$1 last;
}
Now, when I enter domain:88 or domain:1234 it works. When I enter bugs.domain the website loads, but no CSS or images works as the url is bugs.domain/somapath and this falls into the Wordpress bootstrap. I run out of the ideas.
why create only 1 server with if's in it, separate the servers
server {
listen 80;
server_name bugs.example.com;
proxy_pass http://example.com:88;
}
server {
listen 80;
server_name mail.example.com;
proxy_pass http://example.com:1234;
}
server {
listen 80;
# the rest of your main server
#
}
So the problem was completely different then I thought. it was failing on this line:
try_files $uri $uri/ /index.html;
The problem was, that file index.html didn't exist, I only had index.php. Changing it solved the problem.

Resources