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:
- Extract the remote IP from the socket.
- Check the CIDR block list first (explicit IP blocks always run).
- If GeoIP is enabled, call
GeoIpManager.isAllowed(addr). - 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.
- 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:
- Compliance-driven deployments. HIPAA, PCI DSS, and SOX audits often ask “can you restrict access by geography?” A checkbox answer backed by a real enforcement point in the connection pipeline — before the handshake, logged, auditable — satisfies the question directly.
- Internet-facing SFTP with a known user base. If your trading partners are all in North America and Western Europe, allow-mode with five or six country codes eliminates entire continents of noise from your audit trail.
- Servers under sustained brute-force pressure. GeoIP blocking runs before the SSH key exchange. A blocked connection costs the server a TCP accept and a disconnect — microseconds, not the 200–500 milliseconds that a full handshake and failed login would consume. Under a volumetric credential scan, that difference is the margin between a server that stays responsive and one that does not.
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.