Menu

RTCXpression

Close

An Nginx Application Firewall that will Work for Most People

- August 10, 2016

Nginx firewall Nginx is a powerful web server that’s lean on resources. An application firewall can be built with just two of the modules included in a standard build. The ngx_http_geo_module, the ngx_http_map_module and two text files can make it all seem seamless. If you don’t care about controlling the error messages, you can use the ngx http access module instead of the “geo” module.

The Firewall Files

I used to break up everything into separate files but I found out using two files was a lot easier to manage. At the HTTP context level, I have the first part of the firewall as:

include /etc/nginx/conf.d/forbidden.conf;

Even though most of the file is blocking something, there’s a section devoted to allowing IP addresses and ranges.

I have the second part of the firewall at the SERVER context level as:

include /etc/nginx/conf.d/restrictions.conf;

This file will produce errors if the first file isn’t already loaded with Nginx (restart or reload).

I’ll start by showing you the “forbidden.conf” file, one section at a time.

The Firewall “forbidden.conf” File

It doesn’t matter what order you place each section. There are no overlapping variables.

This section is for single IP addresses or address ranges. If you don’t mind a “403 Forbidden” response for blocked IP addresses, you can use the “deny” directive. Otherwise, you should use the “geo” directive. Using the “geo” directive means you can return a 444 (no response) if you wish.

If you use “deny”:

##############################
# Forbidden IP addresses
#
deny 192.168.1.1;
deny 192.168.1.0/24;

If you use the “geo” module with CIDRs:

##############################
# Forbidden IP addresses
#
geo $forbidden_ip_cidr {
default 0;
192.168.1.1 1;
192.168.1.0/24 1;
}

If you use the “geo” module with ranges:

##############################
# Forbidden IP addresses
#
geo $forbidden_ip_range {
ranges;
default 0;
192.168.1.1 1;
192.168.1.0-196.168.1.255 1;
}

You can do it with or without ranges, but you can’t use both CIDRs and ranges within the same variable.

This section is for unwanted referrers:

##############################
# Forbidden Referrers
#
map $http_referer $forbidden_ref {
default 0;
~*buttons-for-your-website.com 1;
~*buttons-for-website.com 1;
~*semalt.com 1;
}

This section is for unwanted user agents. If they’re well-known crawlers or spiders, you’ll want to add them to your robots.txt file as well:

##############################
# Forbidden User Agents
#
map $http_user_agent $forbidden_ua {
default 0;
~*AhrefsBot 1;
~*BaiduSpider 1;
}

This section is for URI strings. These are URIs that would normally generate 404 errors as well as URIs you want to block access to.

##############################
# Forbidden URIs
#
map $request_uri $forbidden_uri {
default 0;
~*^/phpmyadmin 1;
~*^/wp-config 1;
}

This section is for IP addresses allowed to connect, regardless of other restrictions:

##############################
# Allowed IP Addresses
#
geo $allowed {
default 0;
123.123.123.123 1;
123.123.123.0/24 1;
123.123.0.0/16 1;
}

These should be the IP address ranges assigned to your connection at any given time. If you use SSH tunneling, you should also include your server’s IP address.




The Firewall “restictions.conf” File

Again, it doesn’t matter what order you place each section. There are no overlapping variables.

I won’t get into it here, but you can prevent logging of certain requests (I’ll have to write another article for it). I have it shown here so you can see how it works. Don’t include “set $loggable 0;” if you don’t have Nginx set up that way.

Any instance of 403 can be replaced with 444 if you don’t want to return any response at all.

Let’s start with some generic stuff:

##############################
# Generic
#
if ($request_method !~ ^(GET|HEAD|POST)$) { set $loggable 0; return 403; }
if ($request_uri ~* /(apple.touch.icon|browserconfig.xml|favicon|mstile)) { set $loggable 0; }

This section is for the things that don’t need nested conditions. You can only use the first condition if you use the “geo” module to block IP addresses (if you use the “http access” module, you don’t need it anyway):

##############################
# Drop Forbidden IPs/Referrers
#
if ($forbidden_ip)  { set $loggable 0; return 403; }
if ($forbidden_ref) { set $loggable 0; return 403; }

This section is for restricting logins with WordPress. It can only be used if you’re the only one that logs in. This example will work with similar platforms:

##############################
# Restrict Logins
#
if ($request_uri ~* ^/(wp-login)) { set $drop_login 1; }
if ($allowed) { set $drop_login 0; }
if ($drop_login) { set $loggable 0;return 403; }

This section is for restricting the administrative back-end for WordPress to logged in users. This example will work for similar platforms:

##############################
# Restrict Admin
#
if ($request_uri ~* ^/wp-admin) { set $drop_admin 1; }
if ($http_cookie ~* wordpress_logged_in) { set $drop_admin 0; }
if ($drop_admin) { set $loggable 0;return 403; }

This section is for dropping user agents. If they want access to robots.txt, let them have it. You have to “whitelist” yourself if you use a user agent (other than a web browser) to reach your website and you block it for everyone else (like wget or cURL):

##############################
# Drop Forbidden User Agents
#
if ($forbidden_ua) { set $drop_ua 1; }
if ($request_uri ~* ^/robots.txt) { set $drop_ua 0; }
if ($allowed) { set $drop_ua 0; }
if ($http_cookie ~* wordpress_logged_in) { set $drop_ua 0; }
if ($drop_ua) { set $loggable 0;return 403; }

This section is for returning a 404 error for specific URIs. If your URI list includes things that often generate 404 errors (and should), this will allow you to prevent the logging of them. Again, you have to “whitelist” yourself:

##############################
# Drop Forbidden URIs
#
if ($forbidden_uri) { set $drop_uri 1; }
if ($allowed) { set $drop_uri 0; }
if ($http_cookie ~* wordpress_logged_in) { set $drop_uri 0; }
if ($drop_uri) { set $loggable 0;return 404; }

Other Notes about this Application Firewall

This firewall may seem complex but it really isn’t. I didn’t include some of the things I use because they’re not required by most people. I’ll probably refer back to this article when (and if) I explain some of the things I’m doing.

I designed this application firewall for WordPress but it can be used as a basis for other platforms.


Share:  

Subscribe:  

Categories:

Technology

Previous and Next Articles:

« »

Comments:

Your comment will appear below the form when it's approved. When the page redisplays after hitting the send button (it can take a few seconds), your comment has been sent.

When replying to someone else's comment, please start the comment with "@" and the name so I can put it in the right place.

Please read some of my more important pages if you have the time:

Comments Policy           Privacy Policy

RTCXpression established Feb 28, 2011
Copyright © 2013-2017 RT Cunningham
Hosted at Digital Ocean