10

We just started running nginx a few weeks ago and need to block access to certain files/location. e.g.:

/wordpress/wp-admin/
/wp-admin/
/test/wp-admin/
/hudson/login
/phpmyadmin/index.php
/mysql/index.php
/myadmin/index.php
/wp-cron.php
/xmlrpc.php

In general we would like to block any file request except /index.php and also any location such as /wp-admin/, /test/wp-admin/, /wordpress/wp-admin/, etc. These files/locations don't exist, so anybody accessing them is trying to hack/abuse the system.

In Apache we would use .htaccess to block such. How do block in Nginx?

Current Conf

server {
    listen       80;
    root /home/public_html;
    index index.php;
    server_name  domain.com;

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


    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
    }
}
Mugoma J. Okomba
  • 285
  • 1
  • 2
  • 9
  • 1
    Read about location. From a quick look, this article might shed some light, too. – Satō Katsura May 06 '17 at 06:26
  • @SatoKatsura: That comment could well be expanded in a proper answer. – Julie Pelletier May 06 '17 at 06:40
  • @SatoKatsura do you think a customised location is necessary here? if the URI's don't exist, nginx will, by default, send a response with the 404 code and HTML with <h1>404 not found</h1> etc, I would have thought thats as good as you can do? – the_velour_fog May 06 '17 at 08:05
  • @the_velour_fog we use Laravel framework and have only one php file. So, externally nginx doesn't have a way of telling whether a request exists. We need to tell it explicitly what to ignore. Have expanded the question. – Mugoma J. Okomba May 06 '17 at 11:29
  • ok I see what you mean. yes laravel has that huge app/routes.phpfile so the app has to determine if the url is valid. yes its possible to write a location block that matches those wordpress urls and get nginx to check those first and then send all remaining requests to laravel. – the_velour_fog May 06 '17 at 11:37
  • but what are you trying to achieve? to simply prevent the application from having to do the work of responding to 404s? or do you want to block the ip addresses of the user agents making the requests? e.g. by having fail2ban monitor logs and add rules to iptables when a host requests those bad urls? – the_velour_fog May 06 '17 at 11:40
  • @the_velour_fog we need prevent the application from having to respond to 404s. We belief doing it an nginx level is more efficient. At a different level we would also ban IPs. – Mugoma J. Okomba May 06 '17 at 11:57
  • but what do you want to happen? "block" could mean lots of things. your application is already "blocking" in that it will already be sending 404's for these urls, just like it will with real users who make a typo when requesting urls right? – the_velour_fog May 06 '17 at 12:05
  • @the_velour_fog the question is more like which is more efficient. The application does a lot of things. By the time it determines that a request is not valid a lot of resources would have been used, e.g. the application would have made DB connection and gone through several other steps to determine the request was not valid. Doing it at nginx level would eliminate resource and time wastage. – Mugoma J. Okomba May 06 '17 at 12:28

1 Answers1

15

The config below should cause nginx to respond to the "abuse" URLs with a 404 status and a basic nginx, 404 page; all other URLs ending in .php should be proxy passed to the application/php engine as usual. I've tested on some of the patterns, but you should test all the patterns that you want to be managed by nginx and not the application. I thought this config may have had be some issues on URLs like /phpmyadmin/index.php where the \.php regex of the proxy pass location had higher priority, but my testing shows it works, for me at least.
Also, yes it would be nicer if there were not so many location blocks, but I can't think of how you would achieve that.

server {
    listen       80;
    root /home/public_html;
    index index.php;
    server_name  domain.com;

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


    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
    }

    # The ^~ means if this prefix pattern matches use this location block
    # and don't continue onto the regex location blocks - which would load
    # the laravel application
    location ^~ /wordpress/wp-admin/ {
        return 404;
    }
    location ^~ /wp-admin/ {
        return 404;
    }
    location ^~ /test/wp-admin/ {
        return 404;
    }
    location ^~ /hudson/login {
        return 404;
    }
    location ^~ /phpmyadmin/index.php {
        return 404;
    }
    location ^~ /mysql/index.php {
        return 404;
    }
    location ^~ /myadmin/index.php {
        return 404;
    }
    location ^~ /wp-cron.php {
        return 404;
    }
    location ^~ /xmlrpc.php {
        return 404;
    }

} 
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • this looks fine. Instead of many location entries, e.g. many /wp-admin/, is it possible to use a wild card, something like location ^~ *wp-admin*. This would handle even unknown cases since hackers always try to vary URLs. We could even go more wide and use location ^~ *admin*. 2ndly, is the match case insensitive, i.e. will location ^~ /wp-admin/ also match location ^~ /wp-Admin/? – Mugoma J. Okomba May 06 '17 at 18:42
  • 1
    You can go through the below article for detailed answer of case insensitive question & related syntax.

    https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

    – NameNotFoundException Aug 31 '17 at 11:16