Geographic IP blocking: turning on country filters in 60 seconds

· ~4 minute read

Most SFTP servers that face the public internet get probed by hosts in countries that have no business connecting to them. The authentication layer will stop those probes — eventually. But every probe still costs a full SSH key exchange, a cryptographic handshake that burns CPU time and occupies a session slot while it runs. If the connection should never have been accepted in the first place, the cheapest thing to do is refuse it before the handshake starts.

Stylus SFTP Server ships a GeoIP filter that does exactly that. Two XML elements, one database file, and connections from the wrong country are disconnected before the SSH protocol even begins. Here is the setup, what happens under the hood, and when the feature earns its keep.

The config: two elements

Open sftp-server.xml and add a <geo-blocking> block inside <access-control>:

<access-control>
    <geo-blocking>
        <mode>allow</mode>
        <countries>US, CA, GB</countries>
    </geo-blocking>
</access-control>

That is the entire configuration. <mode> is either allow (whitelist — only these countries may connect) or deny (blacklist — these countries are blocked, everyone else gets through). <countries> is a comma-separated list of ISO 3166-1 two-letter codes. Case does not matter.

The data: one file

The filter needs an IP-to-country database to resolve addresses. Drop a MaxMind .mmdb file (GeoLite2 Country or GeoIP2 Country) into the geodb/ folder under your data directory and restart the server. That is step two.

If you prefer not to use MaxMind, a plain CSV works too. The file needs three columns — ip_from, ip_to, country_code — with IP addresses in either dotted-decimal or numeric format:

ip_from,ip_to,country_code
1.0.0.0,1.0.0.255,AU
1.0.1.0,1.0.3.255,CN
1.0.4.0,1.0.7.255,AU

Free CSV databases are available from DB-IP Lite and IP2Location LITE. Either will get you started without a MaxMind account.

Allow vs deny: which mode to pick

Allow is stricter. You list the countries your users connect from; everything else is refused. If your SFTP server serves a known set of trading partners in three countries, allow mode cuts the attack surface to those three countries and nothing else. The downside is that a legitimate user connecting from an unlisted country (a business trip, a VPN exit node) is blocked until you add the code.

Deny is more permissive. You list countries you never expect traffic from; everything else is allowed. This is the safer choice when your user base is broad or hard to enumerate. You accept connections from most of the world and block the regions that produce nothing but scans.

Neither mode is wrong. Allow is a whitelist — deny by default. Deny is a blacklist — allow by default. Pick the one that matches how well you can predict where your users are.

What happens at connection time

The filter runs inside IpFilter.sessionCreated(), a MINA SSHD SessionListener callback. This fires the moment a TCP connection is accepted — before the SSH version string is exchanged, before any key exchange, before the client sends a single byte of the SSH protocol.

The sequence is:

  1. Extract the remote IP from the socket.
  2. Check the CIDR block list first (explicit IP blocks always run).
  3. If GeoIP is enabled, call GeoIpManager.isAllowed(addr).
  4. The manager converts the address to a 32-bit unsigned integer and runs a binary search over a sorted in-memory array of IP ranges.
  5. If the IP resolves to a blocked country, the session is disconnected with SSH code 14 (SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT) and the message “Access denied: connections from your region are not permitted.”

If the IP is not found in any range — a private address, a newly allocated block not yet in the database — the connection is allowed. The filter degrades gracefully: an incomplete database never accidentally locks out a legitimate user.

This is the first layer in the security stack:

Connection → GeoIP check → IP block list → Rate limiter → SSH handshake → Authentication

By the time a blocked client's TCP connection is torn down, the server has spent no CPU on cryptography and no memory on session state. On a server that handles thousands of connections per day, this is meaningful overhead that never happens.

Hot-reload: update without restart

MaxMind updates its GeoLite2 database weekly. When you download a fresh .mmdb file and drop it into the geodb/ folder, a WatchService thread detects the change, re-imports every range into the sftp_geo_ip table, rebuilds the in-memory array, and swaps it atomically via an AtomicReference. No restart, no downtime, no window where the old data is gone and the new data is not yet loaded.

The same hot-reload works for CSV files. Replace the file; the server picks it up within a few seconds.

When it earns its keep

GeoIP blocking is not authentication. It will not stop an attacker who routes through a VPN in an allowed country. It is a volume control: it eliminates the vast majority of automated scanning traffic that originates from regions with no legitimate reason to connect to your server.

It earns its keep in three scenarios:

The limits

Be honest about what GeoIP does not do. IP geolocation databases are not perfect — edge cases include satellite ISPs, corporate VPNs with exit points in unexpected countries, and newly allocated IP blocks that have not yet been classified. The graceful-degradation rule (unknown IPs are allowed) means a missing entry is a false negative, not a lockout. That is the safer failure mode for a file transfer server.

GeoIP also does not replace authentication, MFA, or IP block lists. It is one layer in a stack. Use it alongside the others, not instead of them.

Want to try GeoIP blocking?

Free evaluation key. Install on Windows or Linux, drop a MaxMind database into the geodb folder, add two lines of XML, and country-level blocking is live.

Request Evaluation Key More articles ›