Jack Moore

Email: jack(at)jmoore53.com
Project Updates

444 Connection Closed Without Response

19 Jan 2022 » web, html, nginx, configuration, docker, load balancing

Recently I have been interested in blocking some geographic regions from accessing this blog :( This post explores building from source and using the Nginx geoip2 module to return 444 codes back to clients who are attempting to request the site from a list of countries based on geocode. Now that I think of it, I should just create a small whitelist of geocodes to allow.

I have been starting to look into load balancing requests (for a static blog you ask, yes I respond), so this is the first step in starting to do so.

It starts in a container. But why? Because I didn’t feel like spinning up a VM.

Oh the joy of docker run --rm. Below is a quick docker snippet I used to commit a running container into an image:

docker run --rm -d --name web1 nginx
# Returns the container ID in stdout, this line:
# fd4ea4e1545c78c79c1b0603d7f4711645bc1d44b61a8d787cdcfa9a6262d8f0
# After running a slew of commands in the web1 container including adding libdev tools and gcc for building nginx from scratch, I really didn't want to start over, so I committed my container to an image.
# Best Practice, likely no, but I had my container in an image with the `geo_ip` tag now and didn't have to worry about killing the container or accidently stopping the service.
docker commit fd4ea4e1545c nginx:geo_ip

Because nginx has to be built from source with the maxmind geoip2 module, here is what the build looks like (from within the debian based nginx container (note this is all done within one docker exec -it web1 /bin/bash)):

# Install the needed tools
apt update
apt install wget software-properties-common gpg build-essential vim
tar zxvf nginx-1.21.4.tar.gz
cd nginx-1.21.4
wget https://github.com/leev/ngx_http_geoip2_module/archive/master.tar.gz
tar zxvf master.tar.gz

# Install Maxmind repo for debian
add-apt-repository ppa:maxmind/ppa
apt update

apt install libmaxminddb0 libmaxminddb-dev mmdb-bin
apt install libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev
# At this point after these two steps we now have mmdblookup command available

# Build nginx from source
cd nginx-1.21.4
./configure --add-dynamic-module=/ngx_http_geoip2_module-master $(nginx -V) --with-compat
make install
# After the make install we now have the ngx_http_geoip2_module.so module located at /nginx-1.21.4/objs/ngx_http_geoip2_module.so

# This can then be copied 
cp /nginx-1.21.4/objs/ngx_http_geoip2_module.so /etc/nginx/modules/

# running below should show it in the list of available modules
ls /etc/nginx/modules

Now that the module is added, a simple check is in order.

At the top of my /etc/nginx/nginx.conf file I added the module:

load_module modules/ngx_http_geoip2_module.so;

Running a check should return that nginx is ok to be reloaded:

nginx -t
#Below is the output
#nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
#nginx: configuration file /etc/nginx/nginx.conf test is successful

# Since everything looks good, going to reload nginx
/etc/init.d/nginx reload

NGINX Configuration

From here we can now pull down a few datasets and update our NGINX Configuration:

cd /opt
mkdir GeoLite2
cd GeoLite2

# Install a few mmdb datasets
wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb
wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb

Now that the mmdb files have been installed, a quick lookup is in order to confirm if mmdblookup is working properly.

# this command should return a json formatted output
mmdblookup --file /opt/GeoLite2/GeoLite2-Country.mmdb --ip

After mmdblookup is confirmed working, time to configure nginx to block or allow responses. I have not done much fine tuning with NGINX in this aspect, but below is an example nginx http block configuration I used to only allow US IPs.

The default NGINX HTTP Block:

http {
    geoip2 /opt/GeoLite2/GeoLite2-City.mmdb {
        auto_reload 60m;
        $geoip2_metadata_city_build metadata build_epoch;
        $geoip2_data_city_name city names en;
    geoip2 /opt/GeoLite2/GeoLite2-Country.mmdb {
        auto_reload 60m;
        $geoip2_metadata_country_build metadata build_epoch;
        $geoip2_data_country_code country iso_code;
        $geoip2_data_country_name country names en;
    # Note: map is a keyword for declaring variables in nginx
    map $geoip2_data_country_code $domain_xyz_allowed_country {
        default no;
        US yes;
    # The rest of this http block has been excluded for brevity...

The default.conf file where the files are being served from:

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;
    location / {
        # Note: this variable is teh variable we set above in our nginx.conf http directive
        if ($domain_xyz_allowed_country = no) {
            return 444;
        root   /usr/share/nginx/html;
        index  index.html index.htm;

    # Again, note the rest of this server block was excluded for brevity..

The two blocks above mean that any request from an IP that is outside the US Country Code will be sent a 444 response! Geoip lookup also means we can serve requests faster if we know where our users are coming from. We are able to direct client requests to a geographically closer server. This means for US Clients, their requests are sent to US Servers, and if the request is from EU, the response is from an EU Server.

This is just the start to load balancing for performance.

© Jack Moore