diff --git a/server/device/geo.go b/server/device/geo.go
index 136c76d1579854cfbb587238816ce31ed903a5c8..83a66b35ae689ff9b92b85b3f9dc176809969166 100644
--- a/server/device/geo.go
+++ b/server/device/geo.go
@@ -1,21 +1,53 @@
 package device
 
-import "net"
+import (
+	"errors"
+	"net"
 
-func (m *Manager) getZoneForIP(ip net.IP) (string, error) {
-	if m.geodb == nil {
-		return "", nil
+	"github.com/oschwald/maxminddb-golang"
+)
+
+var (
+	errGeoNotFound = errors.New("no record found")
+
+	defaultGeoIPPaths = []string{
+		"/usr/share/GeoIP/GeoIP.dat",
+		"/usr/share/GeoIP/GeoIPv6.dat",
+	}
+)
+
+type geoIPDb struct {
+	readers []*maxminddb.Reader
+}
+
+func newGeoIP(paths []string) (*geoIPDb, error) {
+	if len(paths) == 0 {
+		paths = defaultGeoIPPaths
 	}
 
+	db := new(geoIPDb)
+	for _, path := range paths {
+		geodb, err := maxminddb.Open(path)
+		if err != nil {
+			return nil, err
+		}
+		db.readers = append(db.readers, geodb)
+	}
+	return db, nil
+}
+
+func (db *geoIPDb) getZoneForIP(ip net.IP) (string, error) {
 	// Only look up a single attribute (country).
 	var record struct {
 		Country struct {
 			ISOCode string `maxminddb:"iso_code"`
 		} `maxminddb:"country"`
 	}
-	if err := m.geodb.Lookup(ip, &record); err != nil {
-		return "", err
-	}
 
-	return record.Country.ISOCode, nil
+	for _, r := range db.readers {
+		if err := r.Lookup(ip, &record); err == nil {
+			return record.Country.ISOCode, nil
+		}
+	}
+	return "", errGeoNotFound
 }
diff --git a/server/device/manager.go b/server/device/manager.go
index 981da2bc1e37cd594aa783f9f8d003c49affc810..c0fa0af25996477e75a504616647108b15506448 100644
--- a/server/device/manager.go
+++ b/server/device/manager.go
@@ -3,13 +3,14 @@ package device
 import (
 	"crypto/rand"
 	"encoding/hex"
+	"log"
 	"net"
 	"net/http"
+	"strings"
 
 	"git.autistici.org/id/auth"
 	"github.com/gorilla/sessions"
 	"github.com/mssola/user_agent"
-	"github.com/oschwald/maxminddb-golang"
 )
 
 func randomDeviceID() string {
@@ -21,7 +22,7 @@ func randomDeviceID() string {
 // Manager can provide DeviceInfo entries for incoming HTTP requests.
 type Manager struct {
 	store             sessions.Store
-	geodb             *maxminddb.Reader
+	geodb             *geoIPDb
 	trustedForwarders []net.IPNet
 	remoteAddrHeader  string
 }
@@ -29,7 +30,7 @@ type Manager struct {
 // Config stores options for the device info manager.
 type Config struct {
 	AuthKey           string   `yaml:"auth_key"`
-	GeoIPDataFile     string   `yaml:"geo_ip_data"`
+	GeoIPDataFiles    []string `yaml:"geo_ip_data_files"`
 	TrustedForwarders []string `yaml:"trusted_forwarders"`
 	RemoteAddrHeader  string   `yaml:"remote_addr_header"`
 }
@@ -44,12 +45,9 @@ func New(config *Config) (*Manager, error) {
 		return nil, err
 	}
 
-	var geodb *maxminddb.Reader
-	if config.GeoIPDataFile != "" {
-		geodb, err = maxminddb.Open(config.GeoIPDataFile)
-		if err != nil {
-			return nil, err
-		}
+	var geodb *geoIPDb
+	if geodb, err = newGeoIP(config.GeoIPDataFiles); err != nil {
+		log.Printf("Warning: GeoIP disabled: %v", err)
 	}
 
 	// The remote IP header (if any) defaults to X-Forwarded-For.
@@ -93,10 +91,21 @@ func (m *Manager) GetDeviceInfoFromRequest(w http.ResponseWriter, req *http.Requ
 		Browser:   browser,
 	}
 
-	if ip := m.getIPFromRequest(req); ip != nil {
-		d.RemoteAddr = ip.String()
-		if zone, err := m.getZoneForIP(ip); err == nil {
-			d.RemoteZone = zone
+	// Special check for .onion HTTP hosts - sets the zone to
+	// "onion" and skips the IP-based checks.
+	if req.Host != "" && strings.HasSuffix(req.Host, ".onion") {
+		d.RemoteZone = "onion"
+	} else {
+		// If we have an IP address, we can add more information.
+		if ip := m.getIPFromRequest(req); ip != nil {
+			d.RemoteAddr = ip.String()
+
+			// Country lookup.
+			if m.geodb != nil {
+				if zone, err := m.geodb.getZoneForIP(ip); err == nil {
+					d.RemoteZone = zone
+				}
+			}
 		}
 	}