Enforce geo-blocking on apache (PHP app)

Download Markdown

Geo-blocking by country on Bitnami Apache (AWS Lightsail)

Block visitors from a specific country and redirect them to an error page, using MaxMind GeoLite2 and mod_maxminddb on a Bitnami stack running on AWS Lightsail.

Environment:

  • AWS Lightsail instance (Debian 12)
  • Bitnami Contao stack
  • Apache 2.4.54 at /opt/bitnami/apache

Step 1: Install build dependencies and MaxMind library

sudo apt-get install -y libmaxminddb0 libmaxminddb-dev mmdb-bin gcc make

Step 2: Compile and install mod_maxminddb

Bitnami ships its own Apache binary, so the Debian package libapache2-mod-maxminddb won't work. Compile from source against Bitnami's Apache instead.

cd /tmp
wget https://github.com/maxmind/mod_maxminddb/releases/download/1.2.0/mod_maxminddb-1.2.0.tar.gz
tar xzf mod_maxminddb-1.2.0.tar.gz
cd mod_maxminddb-1.2.0
autoreconf -fiv
./configure --with-apxs=/opt/bitnami/apache/bin/apxs
make
sudo make install

Verify the module installed and was auto-added to httpd.conf:

ls /opt/bitnami/apache/modules/ | grep maxmind
grep -i maxminddb /opt/bitnami/apache/conf/httpd.conf

Expected output: LoadModule maxminddb_module modules/mod_maxminddb.so


Step 3: Download the GeoLite2 database

Register for a free account at https://www.maxmind.com/en/geolite2/signup, then generate a license key under Account → Manage License Keys.

sudo mkdir -p /opt/bitnami/apache/geoip
cd /opt/bitnami/apache/geoip
sudo wget --user=YOUR_ACCOUNT_ID --password=YOUR_LICENSE_KEY \
  "https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz" \
  -O GeoLite2-Country.tar.gz
sudo tar xzf GeoLite2-Country.tar.gz --strip-components=1 --wildcards "*.mmdb"
ls *.mmdb

Test the database works:

mmdblookup --file /opt/bitnami/apache/geoip/GeoLite2-Country.mmdb --ip 8.8.8.8 country iso_code
# Should return: "US"

Step 4: Enable mod_remoteip

AWS Lightsail sits behind internal load balancers, so Apache sees a private 172.26.x.x IP rather than the real client IP. The real IP arrives in the X-Forwarded-For header. Enable mod_remoteip to handle this:

sudo sed -i 's/#LoadModule remoteip_module modules\/mod_remoteip.so/LoadModule remoteip_module modules\/mod_remoteip.so/' /opt/bitnami/apache/conf/httpd.conf

Verify:

grep "LoadModule remoteip_module" /opt/bitnami/apache/conf/httpd.conf

Step 5: Configure the HTTP vhost

Edit /opt/bitnami/apache/conf/vhosts/example.com-vhosts.conf:

<VirtualHost 127.0.0.1:80 _default_:80>
  ServerAlias *
  DocumentRoot /opt/bitnami/contao/web

  RemoteIPHeader X-Forwarded-For
  RemoteIPInternalProxy 172.16.0.0/12

  MaxMindDBEnable On
  MaxMindDBFile COUNTRY_DB /opt/bitnami/apache/geoip/GeoLite2-Country.mmdb
  MaxMindDBEnv MM_COUNTRY_CODE COUNTRY_DB/country/iso_code

  RewriteEngine On
  RewriteCond %{ENV:MM_COUNTRY_CODE} ^US$
  RewriteRule !^/blocked.html$ /blocked.html [R=302,L]

  <Directory "/opt/bitnami/contao/web">
      Options -Indexes +FollowSymLinks -MultiViews
      AllowOverride All
      Require all granted
      DirectoryIndex index.html index.cgi index.pl index.php index.xhtml
  </Directory>
</VirtualHost>

Step 6: Configure the HTTPS vhost

Edit /opt/bitnami/apache/conf/vhosts/example.com-https-vhosts.conf:

<VirtualHost 127.0.0.1:443 _default_:443>
  ServerAlias *
  DocumentRoot /opt/bitnami/contao/web
  SSLEngine on
  SSLCertificateFile "/opt/bitnami/apache2/conf/bitnami/certs/server.crt"
  SSLCertificateKeyFile "/opt/bitnami/apache2/conf/bitnami/certs/server.key"

  RemoteIPHeader X-Forwarded-For
  RemoteIPInternalProxy 172.16.0.0/12

  MaxMindDBEnable On
  MaxMindDBFile COUNTRY_DB /opt/bitnami/apache/geoip/GeoLite2-Country.mmdb
  MaxMindDBEnv MM_COUNTRY_CODE COUNTRY_DB/country/iso_code

  RewriteEngine On
  RewriteCond %{ENV:MM_COUNTRY_CODE} ^US$
  RewriteRule !^/blocked.html$ /blocked.html [R=302,L]

  <Directory "/opt/bitnami/contao/web">
      Options -Indexes +FollowSymLinks -MultiViews
      AllowOverride All
      Require all granted
      DirectoryIndex index.html
  </Directory>
</VirtualHost>

Step 7: Create the blocked page

Place an error page at /opt/bitnami/contao/web/blocked.html. A minimal example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Not Available</title>
  <style>
    body { font-family: sans-serif; background: #f5f5f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
    .container { text-align: center; max-width: 480px; padding: 2rem; }
    h1 { font-size: 1.375rem; font-weight: 500; margin-bottom: 0.75rem; }
    p { color: #6b7280; line-height: 1.7; }
  </style>
</head>
<body>
  <div class="container">
    <h1>This website is not available</h1>
    <p>Access to this website is restricted in your region. If you believe this is an error, please contact the site administrator.</p>
  </div>
</body>
</html>

Step 8: Test and restart Apache

/opt/bitnami/apache/bin/httpd -t
sudo /opt/bitnami/ctlscript.sh restart apache

Verification

To confirm everything is working, add temporary debug logging to one vhost:

LogFormat "%h %{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %{MM_COUNTRY_CODE}e" geoip_debug
CustomLog /opt/bitnami/apache/logs/geoip_debug.log geoip_debug

Then tail the log and make requests from different locations. You should see:

  • US IPs → 302 redirect to /blocked.html with country code US
  • All other IPs → 200 with their respective country codes

Remove the debug directives once confirmed.


Notes

  • The RewriteRule !^/blocked.html$ exclusion prevents an infinite redirect loop.
  • The RemoteIPInternalProxy 172.16.0.0/12 covers the full AWS internal IP range (172.16.0.0–172.31.255.255).
  • The GeoLite2 database should be updated monthly. Consider a cron job using the same wget command in Step 3.
  • To block additional countries, extend the RewriteCond line: RewriteCond %{ENV:MM_COUNTRY_CODE} ^(US|CA|GB)$