Auto-ban
A server on the internet receives a steady stream of hostile traffic: bots that guess passwords, senders that probe for valid recipients, and scanners that look for known vulnerabilities. Each attempt costs little, but together they waste resources and, for password guessing, put accounts at risk. Auto-ban protects the server by blocking the IP addresses that behave this way.
The mechanism is simple. The server counts suspicious events per source address, and when a count exceeds its threshold, it records the address as blocked. From then on, every connection from that address is closed as soon as it is accepted, on every listener. The server closes it before the TLS handshake and before it sends any greeting.
Banned addresses are stored as BlockedIp objects (found in the WebUI under Settings › Security › Blocked IPs). They share one list with the addresses that an administrator blocks by hand. In a cluster, all nodes share this list, so an address banned on one node is refused by every other.
Counted events
Section titled “Counted events”The server watches for four kinds of suspicious behaviour, and keeps a separate count for each. Every category has its own limit, expressed as a number of events within a period, and an address that reaches the limit in any one category is banned. The limits are fields of the Security singleton (found in the WebUI under Settings › Security › Settings):
| Category | Counted events | Rate field | Default | Ban reason |
|---|---|---|---|---|
| Authentication failures | Failed sign-ins on every protocol | authBanRate | 100 per day | authFailure |
| Abuse | Failed RCPT TO commands: relay attempts and probing for valid recipients | abuseBanRate | 35 per day | rcptToFailure |
| Loitering | SMTP sessions closed because they stayed open longer than the session time limit (421 4.3.2 Session open for too long.) | loiterBanRate | 150 per day | loitering |
| Scanning | Unknown commands or unparsable input from clients that have not authenticated, on SMTP, IMAP, POP3 and ManageSieve, and malformed HTTP requests | scanBanRate | 30 per day | portScanning |
Two categories are also counted by their target, not only by the address. Authentication failures are counted per account name as well as per address, so an attack that rotates through many addresses against one account still triggers bans. Abuse is counted per recipient in the same way. Once an address is banned, its counters are reset.
Setting a rate to null disables counting for its category. Scanning has one exception. An HTTP request for a path that no endpoint serves, and that matches one of the scanBanPaths patterns, bans the address at the first request, whatever the value of scanBanRate. This ban is therefore disabled only by emptying scanBanPaths. The default patterns, all listed in the reference, match common exploit probes, such as paths containing .php, cgi-bin, wp- or ../.
Ban duration
Section titled “Ban duration”Automatic bans are permanent unless a ban period is set. Each category has its own period field: authBanPeriod, abuseBanPeriod, loiterBanPeriod and scanBanPeriod. None of them is set by default, so every automatic ban lasts until its BlockedIp entry is deleted.
When a period is set, the ban’s expiresAt is set accordingly, and the ban is no longer enforced once that time has passed. The expired entry stays in the blocked list until the list is next loaded. At that point it is deleted, and a security.ip-block-expired event is logged. A new ban for an address that is already banned never shortens the existing ban.
The choice between permanent and temporary bans is a tradeoff. A permanent ban keeps a persistent attacker out for good, but it also turns every false positive into a lockout that an administrator has to clear by hand. Shared addresses make false positives likely: behind a single NAT address, such as an office network, the failures of every user add up. A deployment whose users connect from shared addresses should therefore either set ban periods or add those addresses to the allowed list described below.
Exemptions
Section titled “Exemptions”Some addresses must never be banned, whatever they do. The server never bans or blocks the following:
- Addresses and networks in the allowed list, stored as AllowedIp objects (found in the WebUI under Settings › Security › Allowed IPs), each optionally with an expiry time
- The loopback addresses
127.0.0.1and::1 - The networks in
SystemSettings.proxyTrustedNetworks, so that a load balancer sending the PROXY protocol is not banned for the clients behind it
The allowed list is the place for sources that must always reach the server: monitoring systems, the administrator’s network and the addresses of internal relays. Addresses on it are also exempt from the HTTP rate limits.
Auto-ban is suspended entirely during recovery mode and bootstrap. In those modes no address is banned, and existing bans are not enforced.
Clients behind a proxy
Section titled “Clients behind a proxy”The server bans the address it sees as the client, and behind a proxy that is not necessarily the client’s own address. A reverse proxy or load balancer that does not pass the client’s address on makes every client appear with the proxy’s address. A single misbehaving client, or the sum of all clients’ failed sign-ins, then bans the proxy, and with it every user. To avoid this, pass the client address with the PROXY protocol or, for HTTP, with a forwarding header, as described in Reverse proxy.
With a forwarding header, HTTP bans work slightly differently. When Http.clientIpSource takes the client address from a forwarding header, HTTP bans apply to that address:
- A request for a
scanBanPathspattern bans the address from the header. - Malformed requests are not counted.
- A banned forwarded address does not close the connection, because the connection itself belongs to the proxy. Instead, each request from that address is answered with HTTP
403and logssecurity.ip-blocked.
Diagnosing and clearing a ban
Section titled “Diagnosing and clearing a ban”A ban is hard to recognise from the client side. The connection is closed before the server sends anything, so the symptoms do not point to the cause:
- A client reports “connection reset”, “connection closed” or a TLS handshake failure.
- A reverse proxy in front of the server returns
502 Bad Gateway. - A port, including 8080, seems to stop answering for one host while it works from others.
- Behind a proxy that sends a forwarding header, requests from one client receive HTTP
403.
To confirm a ban:
-
Look for the
security.ip-blockedevent, which is logged for every refused connection with the remote address and the listener. The ban itself was logged when it happened, assecurity.authentication-ban,security.abuse-ban,security.loiter-banorsecurity.scan-ban. -
Look for the address in the blocked list in the WebUI, or list the entries with the CLI:
Terminal window stalwart-cli query BlockedIpThe
reasonfield shows which category caused the ban.
To clear the ban, delete the BlockedIp entry, in the WebUI or with the CLI. Then reload the list of blocked addresses with the ReloadBlockedIps action, so that the server stops enforcing the deleted entry:
stalwart-cli delete BlockedIp --ids <id>stalwart-cli create Action/ReloadBlockedIpsAdding the address to the allowed list also lifts the ban, because allowed addresses are never blocked. The allowed list, however, takes effect only after the ReloadSettings action or a restart.
Clearing a ban does not stop it from recurring. To keep it from happening again, add the address to the allowed list, set a ban period for the category, or correct the client that caused it. The cause is typically a device that keeps retrying with an old password.
The administrator’s own address can be banned too. The CLI still works when it is run on the server host itself, because loopback is never banned. When that is not possible either, start the server in recovery mode, where bans are not enforced, and delete the entry from there.