The folks over at Perishable Press publish a decent firewall set of rules that can stop a lot of common script-kiddie attacks. This is known as the 6G Firewall.

The original firewall in the link is written to be used in the .htaccess file in a website’s root folder. Since we’re using Nginx, and Nginx ignores .htaccess, we need to use a different method if we want to implement these rules.

I’ve found the easiest way to do this is just to create a file in the /etc/nginx/snippets folder, and call it from within the server block.

Below is a modification of the 6G rules for Nginx. Some of the rules may break your site, depending on how you serve it. For example the rule to block hidden files (\.) will also block access to your .well-known folder and potentially break LetsEncrypt SSL certificates. I’ve changed it to \.ht and highlighted it in the text below.

Safe the following file as 6G.conf, then call it from within your Nginx server block:

## Add here all user agents that are to be blocked.
map $http_user_agent $bad_bot {
	default 0;
	"~*([a-z0-9]{2000})" 1;
	~*(archive.org|binlar|casper|checkpriv|choppy|clshttp|cmsworld|diavol|dotbot
	|extract|feedfinder|flicky|g00g1e|harvest|heritrix|htmlparser|libwww|httrack
	|kmccrew|loader|miner|nikto|nutch|planetwork|postrank|purebot|pycurl|python
	|seekerspider|siclab|skygrid|sqlmap|sucker|turnit|vikspider|winhttp|xxxyy
	|youda|zmeu|zune) 1;
	}

## Add here all referrers that are to blocked.
map $http_referer $bad_referer {
	default 0;
	"~*([a-z0-9]{2000})" 1;
	~*(semalt.com|todaperfeita) 1;
	}

# query strings that should be blocked
map $query_string $bad_querystring {
	default 0;
	~*(eval\() 1;
	~*(127\.0\.0\.1) 1;
	"~*([a-z0-9]{2000})" 1;
	"~*(javascript:)(.*)(;)" 1;
	~*(base64_encode)(.*)(\() 1;
	~*(GLOBALS|REQUEST)(=|\[|%) 1;
	~*(<|%3C)(.*)script(.*)(>|%3) 1;
	~*(\\|\.\.\.|\.\./|~|`|<|>|\|) 1;
	~*(boot\.ini|etc/passwd|self/environ) 1;
	~*(thumbs?(_editor|open)?|tim(thumb)?)\.php 1;
	~*(\'|\")(.*)(drop|insert|md5|select|union|concat) 1;
	}

map $request\_uri $bad\_request {
	default 0;
	"~*(\[a-z0-9\]{2000})" 1;
	~*(ftp|php):/ 1;
	~*(base64_encode)(.*)(\() 1;
	~*(=\\\'|=\\%27|/\\\'/?)\. 1;
	# "~*/(\$(\&)?|\*|\"|\.ht|,|&|&amp;?)/?$" 1; \## \.ht was originally \.
	~*(\{0\}|\(/\(|\.\.\.|\+\+\+|\\\"\\\") 1;
	"~*(~|`|<|>|:|;|\\|\s|\{|\}|\[|\]|\|)" 1;
	~*/(=|\$&|_mm|cgi-|etc/passwd|muieblack) 1;
	"~*(&pws=0|\_vti\_|\(null\)|\{\$itemURL\}|echo(.*)kae|etc/passwd
		|eval\(|self/environ)" 1;
	~*\.(aspx?|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|sql|svn|swp
		|tar|rar|rdf)$ 1;
	~*/(^$|(wp-)?config|mobiquo|phpinfo|shell|sqlpatch|thumb|thumb_editor|thumbopen
		|timthumb|webshell)\.php 1;
	}

map $request_method $not_allowed_method {
	default 0;
	~*^(connect|debug|delete|move|put|trace|track) 1;
	}

Call it like this:

include snippets/6G.conf

server {
	listen 80;
	server_name example.com www.example.com;
	root /var/www/example.com/html;
	index index.php;

	include snippets/supercache.conf
	include snippets/secrules.conf

  if ($bad_bot) { return 403; }
	if ($bad_referer) { return 403; }
	if ($bad_querystring) { return 403; }
	if ($bad_request) { return 403; }
	if ($not_allowed_method) { return 405; }
	location / {
		try_files $cachefile $uri $uri/ /index.php;
	}

	location ~ \.php$ {
        proxy_pass http://localhost:8080$request\_uri;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

	location ~ /\. {
		deny all;
	}
}