Many distributed applications require traffic routing based on user location. Commercial DNS providers like UltraDNS offer this as a managed service, but when you need full control over your infrastructure, self-hosting becomes essential.
Here are three proven solutions for Linux servers:
1. PowerDNS with GeoIP Backend
2. BIND with GeoIP Patch
3. NSD with Lua Scripting
This configuration routes US traffic to 174.1.1.1 and EU traffic to 65.2.2.2:
# powerdns.conf
launch=geoip
geoip-database-files=/usr/share/GeoIP/GeoIP.dat
geoip-zones-file=/etc/powerdns/geo-zones.conf
# geo-zones.conf
x.com. {
geoip-us=A 174.1.1.1;
geoip-eu=A 65.2.2.2;
default=A 192.0.2.1;
}
The BIND GeoIP patch extends BIND 9's functionality:
# named.conf
view "us" {
match-clients { country_US; };
zone "x.com" {
type master;
file "/etc/bind/db.x.com.us";
};
};
view "eu" {
match-clients { country_EU; };
zone "x.com" {
type master;
file "/etc/bind/db.x.com.eu";
};
};
When implementing GeoDNS:
- Use mmdb format for faster GeoIP lookups
- Consider Edge DNS caching
- Monitor query response times
Verify with dig using different source IPs:
dig @your_dns_server x.com +client=1.2.3.4 # US IP
dig @your_dns_server x.com +client=5.6.7.8 # EU IP
Geo-DNS (or Directional DNS) automatically routes users to the nearest server based on their geographic location. Commercial services like UltraDNS and Comwired offer this feature, but many developers prefer self-hosted solutions for better control and cost efficiency.
Here are three robust options for implementing Geo-DNS on Linux:
1. PowerDNS with GeoIP Backend
PowerDNS offers a GeoIP backend that enables geographic routing:
# Install PowerDNS and GeoIP backend
sudo apt-get install pdns-server pdns-backend-geoip
# Sample PowerDNS configuration (pdns.conf)
launch=geoip
geoip-database-files=/usr/share/GeoIP/GeoIP.dat,/usr/share/GeoIP/GeoIPCity.dat
geoip-zones-file=/etc/powerdns/geo.zones
Example zone file (/etc/powerdns/geo.zones
):
example.com. IN SOA ns1.example.com. admin.example.com. ( ... )
example.com. IN NS ns1.example.com.
example.com. IN A 192.0.2.1 ; Default
example.com. US A 198.51.100.1
example.com. EU A 203.0.113.1
2. Bind9 with GeoIP ACLs
Bind9 can implement geographic routing using GeoIP ACLs:
# Install Bind9 and GeoIP support
sudo apt-get install bind9 bind9-utils libgeoip-dev
# Configure named.conf.options
acl "us_clients" {
geoip_country US;
};
acl "eu_clients" {
geoip_country DE,FR,UK,IT,ES;
};
view "us_view" {
match-clients { us_clients; };
zone "example.com" {
type master;
file "/etc/bind/zones/db.example.com.us";
};
};
view "eu_view" {
match-clients { eu_clients; };
zone "example.com" {
type master;
file "/etc/bind/zones/db.example.com.eu";
};
};
3. CoreDNS with GeoIP Plugin
CoreDNS offers a lightweight alternative with its GeoIP plugin:
# Corefile configuration
. {
geoip {
dbpath "/GeoLite2-Country.mmdb"
default_ip 192.0.2.1
rules {
country us 198.51.100.1
country eu 203.0.113.1
}
}
forward . 8.8.8.8
}
When implementing Geo-DNS:
- Use MaxMind's GeoLite2 databases for accurate IP geolocation
- Consider caching TTL values carefully (300-600 seconds is typical)
- Monitor query latency - GeoIP lookups add overhead
- Implement EDNS Client Subnet (ECS) for better accuracy with CDNs
For a complete solution combining PowerDNS and Redis for dynamic updates:
# Python script to update records dynamically
import redis
import geoip2.database
r = redis.StrictRedis()
reader = geoip2.database.Reader('/GeoLite2-City.mmdb')
def update_geo_ip(client_ip, server_ip):
try:
response = reader.city(client_ip)
country = response.country.iso_code
r.hset(f'geo:{country}', client_ip, server_ip)
except Exception as e:
print(f"Error updating GeoIP: {e}")
# Sample usage
update_geo_ip('203.0.113.45', '198.51.100.1')