Protect your cloud server with geoip firewall rules using UFW in Alpine linux

Deploy an ADS-B receiver with a software defined radio dongle using Docker to gather metrics from nearby airplanes

Daniel Rivero 2025 Sep, 21 4 mins

Banner Inspired by: Frederik de Wit Cartography from 17th century


1. Introduction

To have open ports on the internet to provide services is not safe if you cannot segment your public from unwanted visitors. That’s why we need methods to segment which services/protocols you would open to the world; that’s why firewalls are available. Likewise, if you have a public service targeting only specific continents or countries.

This quick article talks about how to configure GeoIP firewall rules with UFW (or IPtables) in Alpine Linux.


2. Why?

  • I have a micro server for general usage where I would like to have remote access and probably some services.

  • My cloud provider has basic stateless firewall options. Sadly, this is not enough.

  • This is not a new topic, but I was not able to find a good guide/tutorial to deploy it in Alpine; that is why I am sharing it.


3. Requirements

  • Alpine Linux server
  • Public IP address (IPv4/IPv6) (provided by my cloud provider).
  • Some service/protocol you want to be public.
  • Must have already installed UFW (which comes by default with Alpine vanilla)

4. High-Level design

Here a quick topology of how I picture the system, mosly for reference:

High-Level Design


5. Procedure


5.1 Update and install requirements

First, we are going to update and upgrade the server.

sudo apk update
sudo apk upgrade

Next, we install the requirements:

apk add curl xtables-addons perl unzip perl-net-cidr-lite perl-text-csv_xs
  • curl to download the GeoIP databases,
  • xtables-addons is a set of extensions FOR Xtables/iptables packet filter,
  • perl, perl-net-cidr-lite, perl-text-csv-xs for data manipulation
  • unzip decompress the databases

5.2 Prepare the environment

Create a directory where we are going to store the geoip database

sudo mkdir -p /usr/share/xt_geoip

Next, We add an empty file in /usr/local/bin/

sudo touch /usr/local/bin/update-geoip.sh

Now we are ready to insert the script, but let’s first understand what is going to do


5.3 Analyze and prepare the script

The main stages of the script consist in

  1. Creating a temporary directory where we are going to manipulate the downloaded databases (Line: 7)
#!/bin/bash
# Create temporary directory
mkdir -p /usr/share/xt_geoip/tmp/
  1. Download db-ip.com database, there’s command available in xtables-addons.
# Download latest from db-ip.com
cd /usr/share/xt_geoip/tmp/
/usr/libexec/xtables-addons/xt_geoip_dl
  1. Next, we download databases from mailfud and we are going to adapt them to the db-ip database.
# Download mailfud databases then parse them
wget https://mailfud.org/geoip-legacy/GeoIP-legacy.csv.gz -O /usr/share/xt_geoip/tmp/GeoIP-legacy.csv.gz
gunzip /usr/share/xt_geoip/tmp/GeoIP-legacy.csv.gz
cat /usr/share/xt_geoip/tmp/GeoIP-legacy.csv | tr -d '"' | cut -d, -f1,2,5 > /usr/share/xt_geoip/tmp/GeoIP-legacy-processed.csv
rm /usr/share/xt_geoip/tmp/GeoIP-legacy.csv
rm /usr/share/xt_geoip/tmp/GeoIP-legacy.csv.gz
  1. Next, we download a final set of geoIP databases
# Download latest from https://github.com/sapics/ip-location-db
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/geo-whois-asn-country/geo-whois-asn-country-ipv4.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/geo-whois-asn-country/geo-whois-asn-country-ipv6.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/iptoasn-country/iptoasn-country-ipv4.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/iptoasn-country/iptoasn-country-ipv6.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/dbip-country/dbip-country-ipv4.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/dbip-country/dbip-country-ipv6.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/geolite2-country/geolite2-country-ipv4.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/geolite2-country/geolite2-country-ipv6.csv

Note: If you are in a memory-constrained system (under 2 GB) or if you are not using IPv6, comment (#) on some of the databases, as the memory usage during the build will be extensive.

  1. We combine and remove duplicates from the databases and delete the temporary directory
cd /usr/share/xt_geoip/tmp/
cat *.csv > geoip.csv
sort -u geoip.csv -o /usr/share/xt_geoip/dbip-country-lite.csv
# Remove temp directory 
rm -r /usr/share/xt_geoip/tmp/
  1. Finally, we are going to build the database to be used by UFW and reload it (we are removing the database in .csv format to clean up the directory).
/usr/libexec/xtables-addons/xt_geoip_build -i /usr/share/xt_geoip/dbip-country-lite.csv -s
rm /usr/share/xt_geoip/dbip-country-lite.csv
# reload ufw
ufw reload

Find the script here


5.4 Launch the script

Let’s make it executable the script and we are going to launch it.

sudo chmod +x /usr/local/bin/update-geoip.sh

/usr/local/bin/update-geoip.sh

6. Configure UFW rules

So, the process to configure the rules is following exactly the same method you use to configure IPtables rules. Here are some examples:

-A ufw-before-input -p tcp --dport 22 -m geoip --src-cc FR -j LOG --log-prefix "[ACCEPTED SSH FROM FR] "
-A ufw-before-input -p tcp --dport 22  -m geoip --src-cc FR -j ACCEPT

The first rule adds in table ufw-before-input and accepts the port 22/TCP (SSH) coming from France IPv4 addresses and logs using the following prefix [ACCEPTED SSH FROM FR]. Finally, the second rule is accepting this traffic. There’s an implicit deny all after, that’s why I am using only these two rules.


6.1 Log examples

Here’s a log when the server accepts a flow from an IPv4 in France.

 ~ sudo dmesg | grep "ACCEPTED SSH"
[522806.513324] [ACCEPTED SSH FROM FR] IN=eth0 OUT= MAC=56:00:05:89:1d:e7:7e:45:40:93:90:bd:08:00 SRC=92.183.128.243 DST=Y.Y.Y.Y LEN=60 TOS=0x00 PREC=0x00 TTL=244 ID=4299 DF PROTO=TCP SPT=52995 DPT=22 WINDOW=64240 RES=0x00 SYN URGP=0

7. Wrap-up and take aways

This is a basic procedure for servers facing the internet that are not behind a robust NGFW firewall. Even simple tasks like this one will drastically change the exposure and posture of your devices facing the internet. I recommend you always have an NGFW firewall fronting production devices and even two layers of protection, depending on the type of server.

I complemented this feature with Fail2Ban just to block abusive IPs and bots from the countries I am accepting traffic from. You can find multiple guides on the internet to configure here the official guide for Alpine.


8. Rereferences

  1. seenlyst - geo blocking ufw iptables

  2. cyberciti Block entier country using iptables




The fact that we live at the bottom of a deep gravity well, on the surface of a gas covered planet going around a nuclear fireball 90 million miles away and think this to be normal is obviously some indication of how skewed our perspective tends to be.

The Salmon of Doubt: Hitchhiking the Galaxy One Last Time (Douglas Adams)

δ


Related posts: