Security through geography: blocking traffic by country, continent, or IP address using ModSecurity

Security through geography: blocking traffic by country, continent, or IP address using ModSecurity

How-Tos Updated on 6 mins

Why do this? The use case

Imagine you’re running a business and you often see malicious-looking web traffic from the other side of the globe hitting your website. A simple solution is to block all traffic originating in the offending country, assuming that your business doesn’t have a presence there and that genuine customers are very unlikely to be connecting from there.

Using our appliance’s WAF functionality, it’s possible to block inbound traffic by location to add an extra layer of security. Our customers have requested this ability so that they can block obvious attempts to attack their services.

How does it work?

Our appliance’s WAF functionality can be easily configured through our intuitive web user interface. The WAF services are based on the free and open source ModSecurity project, which means that the methods presented here can also be applied to a self-built ModSecurity deployment. The big advantage of using our appliance is that its simple, GUI-based setup removes the complexities of creating, configuring, and maintaining a self-built solution.


Performing geolocation blocking involves looking up IP addresses in a database which matches IP addresses to geographic locations, and then potentially blocking by location. The MaxMind company provides a popular geolocation database, both a freely available and paid-for databases (although the usefulness of the more granular location data in the paid-for databases is questionable: can we really pin down where an IP address is located down to the city level, or even the postcode level? Aside from its reliability, is that information actually useful?).

The official legacy database has been end-of-life'd

The version of ModSecurity (v2) that our appliance uses to provide WAF functionality expects to read the legacy form of the MaxMind database. This format of their database has been end-of-life'd, and is not available for download anymore!

Fortunately, Christoph Hansen, user ‘emphazer’ on GitHub, has developed a script that automatically pulls the latest version of the new format MaxMind database and converts it into the legacy .dat format that we need - amazing! It requires a handful of Python scripts and packages to work, but it will complain if anything is missing which makes it easy to install the requisite packages on various Linux distributions.

The conversion (bash) script is available from this GitHub repository:

For simplicity, we’ve already done the heavy lifting and the legacy version of the database is available for download from our website ( has a GeoLite2 Country Redistribution License to allow us to redistribute the database). It can be found at this location:

On our load balancer, we recommend putting the database file in the location:

For those who are interested, an accompanying checksum is also available to verify the integrity of the downloaded database file:

The following ‘one liner’ can be executed on our appliance under Local Configuration > Execute shell command to download the database file and put it in the directory mentioned above:

mkdir -p /usr/local/geo/data; curl -o /usr/local/geo/data/GeoIP.dat

Note: If the appliance is in ‘Secure’ mode then the Execute shell command option will not appear. See the Administration Manual for full details on the considerations for disabling ‘Secure’ mode

Configuring the WAF rules for geolocation blocking

On our appliance, configuring custom rules for a WAF service takes place under Cluster Configuration > WAF - Manual Configuration.

The first thing that must be defined once for each WAF service is a configuration directive to load the geolocation database. This takes the form of:
SecGeoLookupDb /usr/local/geo/data/GeoIP.dat


Theo has already written a great blog about configuring geolocation blocking: “Blocking Japan with ModSecurity and Maxmind Lite”. In that blog, Theo's examples drop traffic that appears to originate from Japan.

In most scenarios, it’s typically useful to explicitly deny unwanted traffic rather than silently dropping it. This presents clients from blocked regions with a 4xx HTTP status code of choice, for example ‘403 Forbidden’ or the more interesting ‘451 Unavailable For Legal Reasons’.

Note: ISO 3166 two-letter country and continent codes are used to define regions for geolocation purposes

Only allow traffic from one country

As an example, to block clients from non-Canadian IP addresses by returning a ‘403 Forbidden’ response, we could use the following:

SecRule REMOTE_ADDR "@geoLookup" "chain,id:5000000,deny,status:403, \
    msg:'Non-CA IP address: address is in %{GEO.COUNTRY_NAME} (%{GEO.COUNTRY_CODE}).'"
SecRule GEO:COUNTRY_CODE "!@streq CA"


An example of what a blocked client would see:

The log files (under Logs > WAF Error) will record instances of any blocking performed, along with the connection’s country of origin, like so:


Only allow traffic from a range of countries

It’s possible to adapt the previous example to block clients from IP addresses outside of a range of countries. For example, to block all non-British, Canadian, and US traffic, we could use:

SecRule REMOTE_ADDR "@geoLookup" "chain,id:5000010,deny,status:403, \
    msg:'Non-GB/CA/US IP address: address is in %{GEO.COUNTRY_NAME} (%{GEO.COUNTRY_CODE}).'"

Explicitly block traffic from a specific country

Using the opposite approach, it is possible to instead block specific territories only. For example, to block all Paraguayan traffic, we could use the following:

SecRule REMOTE_ADDR "@geoLookup" "chain,id:5000020,deny,status:403, \
    msg:'Paraguayan IP address detected.'"
SecRule GEO:COUNTRY_CODE "@streq PY"

Explicitly block traffic from a range of countries

Following the same pattern, connections from a range of countries can be blocked. For example, all Jordanian, Cypriot, and Singaporean traffic can be blocked like so:

SecRule REMOTE_ADDR "@geoLookup" "chain,id:5000030,deny,status:403, \
    msg:'Jordanian, Cypriot, or Singaporean IP address detected (%{GEO.COUNTRY_NAME}).'"

Only allow traffic from one continent

It’s possible to block all connections outside of a specific continent. For example, to block all non-north American traffic, we could use:

SecRule REMOTE_ADDR "@geoLookup" "chain,id:5000040,deny,status:403, \
    msg:'Non-North American IP address: address is in %{GEO.COUNTRY_NAME} (%{GEO.COUNTRY_CODE}).'"

Explicitly block traffic from a specific IP address

Finally, while not strictly related to geolocation, it’s also possible to block connections from a specific IP address (or subnet). This is useful to stop malicious activity from known bad addresses. For example, to block all traffic from the IP address, we could use:

SecRule REMOTE_ADDR "@IPMatch" "id:5000050,deny,status:403"

To block all traffic from the IP address and the entire subnet, we could use:

SecRule REMOTE_ADDR "@IPMatch," "id:5000060,deny,status:403"


The flexibility that our WAF functionality offers by blocking and allowing traffic based on its place of origin adds another layer of security to a deployment. Guessing geographic location by IP address will never be perfect but in many scenarios it’s a good way of blocking obvious bad traffic. It’s true that this can be done by hand by manually parsing log files for bad IP addresses and creating an IP address blacklist, but that’s both time consuming and easy to mess up. If nothing else, geolocation blocking using our WAF functiontality is another tool to add additional security and make your life a little bit easier.