Blocking website users by country

Sadly, there’s a ton of malicious activity going on all the time on the Internet. One thing that can be done to reduce some of this traffic is simply to limit your website to users coming from specified countries. My approach is to deny by default and whitelist a small number of countries. I’m looking for a better server-wide solution that works with Linux, but here’s something I found that works for individual websites using Apache’s configuration files and MaxMind’s products/services. Important: if you access your web server “locally” (on the same LAN, or even same computer) read the notes at the end.

1. For simplicity, assume all command are run with root privileges:

su -

2. You’ll need some development stuff to build the MaxMind software:

yum -y install gcc-c++ httpd-devel wget

3. Create a temporary directory to make cleanup simpler later:

mkdir ~/tmp

4. Download the geographical database that relates IP addresses to countries (you’ll need to periodically update this as IP address locations change). It used to be you could just download this data. Now they ask for a bunch of personal and contact data to download it, so I’m not sure I’ll keep using this service. I’ll test it out for the time being though just to update my notes. The main download page can be found here: http://dev.maxmind.com/geoip/geoip2/geolite2/. This eventually led me here: https://www.maxmind.com/en/accounts/473295/geoip/downloads (after being forced to divulge an email address, etc.). I downloaded a file for “GeoLite2 Country” named “GeoLite2-Country_20201229.tar.gz”. Again, this used to be an easy thing, so I’m skeptically moving forward…

cd ~/tmp 
tar xf GeoLite2-Country_20201229.tar.gz 
cd GeoLite2-Country_20201229
mkdir -p /opt/geoip && mv GeoLite2-Country.mmdb /opt/geoip/

5. Download, build, and install the MaxMind database library. The main download page can be found here: https://github.com/maxmind/libmaxminddb/releases

cd ~/tmp
wget https://github.com/maxmind/libmaxminddb/releases/download/1.4.3/libmaxminddb-1.4.3.tar.gz
tar xf libmaxminddb-1.4.3.tar.gz
cd libmaxminddb-1.4.3/
./configure
make
make install
sh -c "echo /usr/local/lib  >> /etc/ld.so.conf.d/local.conf"
ldconfig

6. Download, build, and install the MaxMind Apache module. The main download page can be found here: https://github.com/maxmind/mod_maxminddb/releases

cd ~/tmp
wget<a href="https://github.com/maxmind/mod_maxminddb/releases/download/1.2.0/mod_maxminddb-1.2.0.tar.gz"> https://github.com/maxmind/mod_maxminddb/releases/download/1.2.0/mod_maxminddb-1.2.0.tar.gz</a>
tar -xzf mod_maxminddb-1.2.0.tar.gz 
cd mod_maxminddb-1.2.0 
./configure 
make 
make install

7. When the Apache module was installed, it attempted to update the apache/httpd configuration. This worked for me on one server and failed on another, so I would check /etc/httpd/conf/httpd.conf to ensure that the following line is present:

LoadModule maxminddb_module /usr/lib64/httpd/modules/mod_maxminddb.so

8. Restart Apache:

/bin/systemctl restart httpd.service

9. Now let’s use this on a website. Modify the .htaccess for your website to add the following configuration to only allow whitelisted country codes (the example just allows the US and Canada):

<IfModule maxminddb_module> 
  ErrorDocument 403 "403" 
  Order Deny,Allow 
  Deny From All 
  MaxMindDBEnable On 
  MaxMindDBFile DB /opt/geoip/GeoLite2-Country.mmdb 
  MaxMindDBEnv MM_COUNTRY_CODE DB/country/iso_code 
  SetEnvIf MM_COUNTRY_CODE ^(US|CA) AllowCountry 
  Allow from env=AllowCountry 
</IfModule>

Notes

  • To test this, I recommend using something like the privateinternetaccess.com service. By setting different PIA servers you can attempt to access your website from different countries.
  • If you are testing on the same network as your server (using LAN IP addresses), your local addresses are not routable over the Internet and therefore will not be recognized by the geo database. Using the approach I have recommended here will essentially disable access to your website from your LAN. You can simply append “Allow from 192.168.1.0/24” to you .htaccess file for local access (replace 192.168.1.0/24 with whatever your LAN uses).  If you access the website from the same machine, you may need to add “Allow from 127.0.0.1”.
  • If you prefer to put the website’s MacMind DB config in the httpd.conf file (instead of the .htaccess file), just put the same block of code inside a directory container (see complete example below).

Complete example using httpd.conf (no changes to .htaccess)

<VirtualHost *:80>
  ServerName www.domain.com
  ServerAlias domain.com
  DocumentRoot /var/www/domain.com/web
  ErrorLog /var/www/domain.com/log/error.log
  RewriteEngine on
  RewriteCond %{HTTP_HOST} !^www\.
  RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
  ServerAdmin cj@domain.com
  <Directory "/var/www/domain.com/web">
    AllowOverride All
    Require all granted
    <IfModule maxminddb_module> 
      ErrorDocument 403 "403" 
      Order Deny,Allow 
      Deny From All 
      MaxMindDBEnable On 
      MaxMindDBFile DB /opt/geoip/GeoLite2-Country.mmdb 
      MaxMindDBEnv MM_COUNTRY_CODE DB/country/iso_code 
      SetEnvIf MM_COUNTRY_CODE ^(US|CA) AllowCountry 
      Allow from env=AllowCountry 
      Allow from 10.1.10.0/24
    </IfModule>
  </Directory>
</VirtualHost>
<VirtualHost *:443>
  ServerName www.domain.com
  ServerAlias domain.com
  DocumentRoot /var/www/domain.com/web
  ErrorLog /var/www/domain.com/log/error.log
  RewriteEngine on
  RewriteCond %{HTTP_HOST} !^www\.
  RewriteCond %{HTTPS}s ^on(s)|
  RewriteRule ^ http%1://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
  ServerAdmin cj@domain.com
  <Directory "/var/www/domain.com/web">
    AllowOverride All
    Require all granted
    <IfModule maxminddb_module>
      ErrorDocument 403 "403"
      Order Deny,Allow
      Deny From All
      MaxMindDBEnable On
      MaxMindDBFile DB /opt/geoip/GeoLite2-Country.mmdb
      MaxMindDBEnv MM_COUNTRY_CODE DB/country/iso_code
      SetEnvIf MM_COUNTRY_CODE ^(US|CA) AllowCountry
      Allow from env=AllowCountry
      Allow from 10.1.10.0/24
    </IfModule>
  </Directory>
  <IfModule mod_ssl.c>
    SSLEngine on
    SSLProtocol All -SSLv2 -SSLv3
    SSLCertificateFile /var/www/domain.com/ssl/www.domain.com.cert.pem
    SSLCertificateKeyFile /var/www/domain.com/ssl/www.domain.com.key.pem
  </IfModule>
</VirtualHost>

Another Example – Using Redirects Instead

This might be a little friendlier approach. This example “allows” by default and redirects users from the specified countries to a web page (replace “domain.com” and replace “XX|YY|ZZ” with whatever country codes irk you).

# Block list of countries codes
<IfModule maxminddb_module>
Order Allow,Deny
 MaxMindDBEnable On
 MaxMindDBFile DB /opt/geoip/GeoLite2-Country.mmdb
 MaxMindDBEnv MM_COUNTRY_CODE DB/country/iso_code
 RewriteEngine on
 RewriteCond %{ENV:MM_COUNTRY_CODE} ^(XX|YY|ZZ)$
 RewriteCond %{REQUEST_FILENAME} !some_web_page.html
 RewriteCond %{REQUEST_FILENAME} !some_image_used_in_web_page.png
 RewriteRule ^(.*)$ http://www.domain.com/some_web_page.html [L]
 Allow from All
</IfModule>

Getting a little fancier with PHP

I wanted to see how this could be used in a PHP file (to get geo data and do something interesting with it). Here’s what I did…

(Most of this came from https://github.com/maxmind/MaxMind-DB-Reader-php)

PHP library:

curl -sS https://getcomposer.org/installer | php
php composer.phar require geoip2/geoip2:~2.0

C/C++ library (makes things a little faster):

svn co https://github.com/maxmind/MaxMind-DB-Reader-php
cd&nbsp;MaxMind-DB-Reader-php/trunk/ext
yum -y install php-devel
phpize
./configure&nbsp;
make
make install

Edit /etc/php.ini to include “extension=maxminddb.so”. I restarted httpd after this (pretty sure that’s necessary, but I haven’t verified yet).

Download the DBs:

mkdir -p /opt/geoip/ && cd /opt/geoip/
wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz
wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz

I changed the owner of the DBs to apache (not sure if that was really needed). Next, I put the code to use in a PHP file. Here’s an example:

<?php
require 'vendor/autoload.php';
use MaxMind\Db\Reader;
$ipAddress = $_SERVER["REMOTE_ADDR"];
$databaseFile = '/opt/geoip/GeoLite2-City.mmdb';
$reader = new Reader($databaseFile);
$city = $reader->get($ipAddress)[city][names][en];
$country = $reader->get($ipAddress)[country][names][en];
$subdivisions = $reader->get($ipAddress)[subdivisions][0][names][en];
$reader->close()
?><html>
<head>
<title>Hello<?php if ($country != "") echo " " . $country; ?>!</title>
<style>
a:link { color: #ffffff; }
a:visited { color: #ffffff; }
a:hover { color: #ffffff; }
a:active { color: #ffffff; }
</style>
</head>
<body style="background-color:#000000; color:#ffffff">
<br /><br />
<div style="text-align:center">
<h1>Hello<?php if ($country != "") echo " " . $country; ?>!</h1>
<hr />
<?php if ($city != "" || $subdivisions != "" || $country != "")
{
 if ($city != "")
   echo "<br />City: " . $city;
 if ($country != "")
   echo "<br />Country: " . $country;
}
?>
</div>
</body>
</html>

Leave a Comment