Skip to main contentSkip to footer

Blocking with the Linux Agent

The community feed in the kernel and the addresses this host bans itself: the two kinds of set and their lifetimes, how a local ban begins and ends, the escalation with real numbers, the chain in the kernel, the two switches mode and ban.enabled, and how one ban or everything is lifted.

The community feed

This is the part the Network-Level Blocking page describes as a shell script. The agent does the same thing, with the parts that are easy to get wrong when you write it yourself already handled: one conditional request per list so an unchanged list costs nothing, a size check before anything goes live, an atomic swap so there is never a window with an empty set, and port-scoped rules so a web attacker cannot lock anyone out of SSH.

ListKernel setPorts the rule matches
sshrip-ssh, rip-ssh-v6from lists.ssh.ports
mailrip-mail, rip-mail-v625, 465, 587, 110, 995, 143, 993
webrip-web, rip-web-v680, 443
ftprip-ftp, rip-ftp-v621
edgerip-edge, rip-edge-v6every port, on purpose
grouprip-group, rip-group-v6every port; see below

How often the feed is fetched depends on your plan, and the server decides it, not the agent. On a licensed server the interval is every 15 minutes with a minimum confidence of 75 from Professional upwards, and hourly with a minimum confidence of 90 on Contributor. Business and Enterprise are as Professional. The agent asks which values apply to this host and follows the answer, so an upgrade takes effect without anyone editing a file. The answer is cached for up to 24 hours, and only an open api_key, license or reputation condition makes it ask on every pass, so a plan change reaches the host within a day. There is no configuration key for the interval, and a key you invent for it stops the agent with exit 2.

Two more rules sit on top of that. A list that was fetched less than 15 minutes ago is not fetched again, because that is how long the server caches one, so running sync by hand is always allowed and never harmful. And every fetch is conditional: an unchanged list answers 304 and costs one request and no transfer, which is why a short interval is cheap for both sides.

The group list

A key in a group gets a sixth list, fetched as json with the community feed. Every address is dropped on every port in the sets rip-group and rip-group-v6, whatever service this host exposes. A key in no group changes nothing: the set stays empty and status prints group=none.

The group's whitelist, if it carries one, is read in the same request and written to /var/lib/reportedip-agent/group-whitelist, in the format of whitelist.conf with each note as a comment. Do not edit that file by hand, the next sync writes it again. It is applied in the same sync run in all three places a whitelist works: the kernel whitelist set, the filter of every list, and the reporting gate of the watcher, one more layer next to this host's own whitelist.conf and its auto whitelist. A key that leaves its group loses the file with the next sync.

whitelist list and status show the group's entries on lines marked group:, with their notes. status also names the group and its size next to the account line and shows the entry count of the group list under the lists like any other. See Groups for what a group is, the tariff limits, the whitelist and the webhook on the portal side.

Blocking

The agent writes two very different things into the kernel, and an admin coming from fail2ban usually conflates them for the first week. They have different sources, different lifetimes and different switches, so it is worth separating them once and properly.

Two kinds of set, two lifetimes

Community blacklistLocal finds
Setsrip-ssh, rip-mail, rip-web, rip-ftp, rip-edge, rip-group, and a -v6 set for eachrip-local and rip-local-v6
Where the addresses come fromEvery reporter in the community, filtered by your confidenceOnly this host's own logs
How an entry arrivesThe whole set is replaced on every feed passOne address at a time, when a threshold is reached
How an entry leavesWith the next swap, when the feed no longer lists itThe kernel expires it when its own timeout runs out
Per-entry timeoutnoneyes, that is the whole design
Switchmodeban.enabled, and mode on top
Needs a licenceyes, this is the paid partno

How a local ban begins and how it ends

A ban begins when a detector's counter for one address crosses the threshold of its event source. What happens then, in order:

  1. The whitelist is checked first, before anything else is even asked. A whitelisted address leaves no trace at all, so you never find your own management range in reportedip-agent ban list wondering whether it was blocked. The check reports which layer matched: a built-in reserved range, one of this host's own addresses, or your file.
  2. The ban store, /var/lib/reportedip-agent/bans.json, is asked for the ban time. It records the address, when the ban expires, how many bans fell inside the memory window, when the last one started, the source and the categories. The file is written before the kernel entry, so a crash right after the kernel write cannot lose the record that expires it.
  3. The address goes into rip-local with that time as a per-entry timeout. The kernel expires it. There is no cleanup timer, no cron job and no unban queue, which is the reason a crashed or removed agent leaves no permanent block behind.
  4. A line is written to the log with the address, the ban time, the source, how many bans that address has inside the window, the lag between the log line and the kernel entry, and the two commands to undo it.

A running ban is never shortened. When a new hit arrives for an address that is already banned, nothing happens: it is not an escalation either, because escalation counts bans and not hits, exactly like the recidive jail it replaces. An address that is already blocked cannot be blocked again. That rule is also what keeps the transition period honest, when the agent sees one attack twice, once as a raw line from auth.log and once as a ban line from fail2ban.log.

A replayed log line does not produce a ban either. A hit whose timestamp is not newer than the last ban of that address is a replay after a hard stop, not a new offence, and the reporting path has the same guard in its dedup file.

The state survives more than a restart. With ban.enabled: true, every sync run compares bans.json against what the kernel actually holds and puts back what is missing, with the time the ban has left:

  • After a reboot that is all of them, because neither an ipset timeout nor the set itself survives a reboot. This is why the sync service is enabled for multi-user.target and runs after every firewall service.
  • During normal operation it is whatever took the set away: a firewall-cmd --reload, a csf -r, an ipset flush. Entries the kernel still holds are left alone, so the gap is a part of one sync interval and not the rest of the ban. The log says explicitly when entries were missing under active bans, because that means something edited the set under the agent.
  • An expired record never comes back. Yesterday's attacker is no reason to cut someone off today.
  • The whitelist is re-checked on every restore. It may have grown while the host was down, and an address you whitelisted yesterday must not be blocked again by a record from the day before.

Escalation, with real numbers

The first ban lasts time_minutes. Every repeat inside the memory window multiplies it by escalate, and max_time_minutes caps it. With the defaults (30 minutes, multiplier 4, cap one week) an address that keeps coming back goes:

BanCalculationDuration
1sttime_minutes30 minutes
2nd30 × 42 hours
3rd120 × 48 hours
4th480 × 432 hours
5th1920 × 4128 hours, five days and eight hours
6th and afterwould be 512 hours, capped7 days, the value of max_time_minutes

The chain in the kernel

With the ipset backend the agent owns exactly one chain, rip-blacklist, and one jump into it. A sync rebuilds the chain whenever its fingerprint (the config and the chain as the kernel reports it) differs, so its content is always what the config says; otherwise it leaves the chain alone, so the packet counters keep counting. The jump is inserted at position 1 of INPUT only if it is not already there.

bash
# What a sync builds, in this order:
iptables -N rip-blacklist
iptables -F rip-blacklist
iptables -A rip-blacklist -m set --match-set rip-whitelist src -j RETURN
iptables -A rip-blacklist -m set --match-set rip-local src -j DROP
iptables -A rip-blacklist -p tcp --dport 22 \
         -m set --match-set rip-ssh src -j DROP
iptables -A rip-blacklist -p tcp -m multiport --dports 25,465,587,110,995,143,993 \
         -m set --match-set rip-mail src -j DROP
iptables -A rip-blacklist -p tcp -m multiport --dports 80,443 \
         -m set --match-set rip-web src -j DROP
iptables -A rip-blacklist -m set --match-set rip-edge src -j DROP
iptables -A rip-blacklist -m set --match-set rip-group src -j DROP

# Only with a scan source in the config: a SYN to a port this host
# does not listen on is logged, at most ten a minute. Listening ports
# and the passive range of a running FTP server return first, then any
# port a socket listens on at this very moment.
iptables -N rip-blacklist-scan
iptables -A rip-blacklist-scan -p tcp -m multiport --dports 21,22,25,80,443,40110:40210 -j RETURN
iptables -A rip-blacklist-scan -p tcp -m socket --nowildcard -j RETURN
iptables -A rip-blacklist-scan -m limit --limit 10/min --limit-burst 20 \
         -j LOG --log-prefix "rip-scan: "
iptables -A rip-blacklist -p tcp --syn -j rip-blacklist-scan

iptables -I INPUT 1 -j rip-blacklist

The whitelist is the first rule and returns, it is not the last rule. A RETURN in front of everything else means a whitelisted address leaves the chain before any set is consulted, so it outranks every ban including one that is already in rip-local. That is what makes whitelist add a working way out of a lockout, with no sync and no restart. A whitelist placed at the end would be a list of exceptions that arrive too late, and on a host where you have locked yourself out, "too late" is the entire problem.

The local ban rule carries no port match, because a host that attacked your mail server has no business on port 22 either. The rules for ssh, mail, web and ftp are port-scoped, so an address on the web list is dropped on 80 and 443 and can still reach SSH. That is deliberate: a shared address, a carrier NAT or a compromised proxy on the web list must not lock an administrator out of the machine. Only edge and group match every port: for edge that is what the category behind it means, for group it is the point of sharing a ban.

With the nftables backend the shape is the same in native form: a table inet reportedip, a chain hooked into input at priority -10 with policy accept, the two whitelist return rules first, then rip-local, then the feed lists with their port matches. The whitelist sets are interval sets so they hold CIDR ranges; the local sets carry the timeout flag, which both tools require on the set before any element may carry a timeout at all.

In mode: log the same rules are built, and the verdict is a rate-limited log line (six per minute, prefixed rip-<list>:) instead of DROP. Everything else is identical, which is why the log mode is a real rehearsal and not a simulation.

mode and ban.enabled are two switches

This is the single most common misunderstanding, so it gets its own paragraph. mode decides what the rules do. ban.enabled decides whether your own finds ever become an entry at all. Both are on by default (mode: drop, ban.enabled: true). With ban.enabled: false, mode: drop gets you the community list enforced and nothing of your own.

modeban.enabledCommunity listYour own finds
logfalseMatched and logged, nothing droppedReported to the community, not blocked locally
logtrueMatched and loggedRecorded in rip-local and visible in ban list, logged instead of dropped
dropfalseDroppedReported only. This is a perfectly sensible steady state.
droptrueDroppedDropped as well, with escalation. The full configuration.
offeitherNothing in the packet path: the jump is removed; with ipset the chain is left with its rules, with nftables the chain is deletedRecorded in the store, no effect

off is the emergency switch and it is honest about it. It removes what hooks the chain into the packet path, up to five times to catch duplicate jumps, and fails loudly if a jump survives, because saying "off" while still dropping traffic is worse than an error. With nftables the chain itself is deleted. The sets keep their entries in both backends, so switching back needs no full download.

A manual ban ignores both switches on purpose: reportedip-agent ban add works with ban.enabled: false, because that is a deliberate act by an operator, the same way fail2ban-client set banip was. It still respects the whitelist, and it still refuses when the host has no firewall backend at all.

Switching from log to drop

bash
# 1. What would have been dropped? The rules already match in
#    log mode, so ask the kernel log.
journalctl -k --since "24 hours ago" | grep -c "rip-"
journalctl -k --since "24 hours ago" | grep -o "rip-[a-z]*:" | sort | uniq -c

# 2. Is anything of yours in there? Check every source address
#    against your whitelist before you switch.
reportedip-agent whitelist list

# 3. Switch, and rebuild the rules.
sed -i "s/^mode: log/mode: drop/" /etc/reportedip-agent/config.yaml
reportedip-agent sync
reportedip-agent status

# 4. Only now the second switch, if you want it.
#    ban.enabled: true, then restart the daemon.
systemctl restart reportedip-agent.service
reportedip-agent ban list

Looking at what is really in the kernel

reportedip-agent status is the summary, and it reads both the state files and the live kernel so the two can be compared. Where they disagree, the kernel is the truth and status says so: a record without a kernel entry is a ban that is not in effect.

bash
# The agent's own view
reportedip-agent status
reportedip-agent ban list

# ipset backend, straight from the kernel
ipset list -t rip-ssh            # header and entry count only
ipset list rip-local             # entries, each with its timeout
iptables -S rip-blacklist
iptables -L INPUT -n --line-numbers | head

# nftables backend
nft list table inet reportedip
nft list set inet reportedip rip-local

In ban list the column kernel is what the kernel still has on the clock and record is what bans.json expects. A dash under kernel next to a running record means the ban is not in effect and the next sync will restore it. A row whose record says none exists in the kernel only, which means somebody used ipset or nft by hand, or the store was lost: it will still expire, but no reboot brings it back.

Lifting one ban, and removing everything

The way out of a lockout is one command. The whitelist rule sits in front of the block rule, so reportedip-agent whitelist add <address> takes effect immediately and outranks every ban, including one already in the set. It writes the file and the live kernel set in one step, and it needs no sync and no restart.
bash
# Lift one ban and forget its history. The next detection starts
# from a first offence rather than escalating on top of a verdict
# you disagreed with.
reportedip-agent unban 203.0.113.45

# Never ban and never report this address or range, from now on.
reportedip-agent whitelist add 203.0.113.0/24 "customer office"

# Ban by hand. Works even with ban.enabled: false. A time given
# here is the time, not the first step of an escalation.
reportedip-agent ban add 203.0.113.45
reportedip-agent ban add 203.0.113.45 1440

# Stop blocking without uninstalling: no jump, the sets keep their entries.
sed -i "s/^mode: .*/mode: off/" /etc/reportedip-agent/config.yaml
reportedip-agent sync

# Remove the agent from the packet path completely (ipset backend).
iptables  -D INPUT -j rip-blacklist; iptables  -F rip-blacklist; iptables  -X rip-blacklist
ip6tables -D INPUT -j rip-blacklist; ip6tables -F rip-blacklist; ip6tables -X rip-blacklist
for s in $(ipset list -n | grep "^rip-"); do ipset destroy "$s"; done

# nftables backend
nft delete table inet reportedip

unban and whitelist add both take the sync lock, so the restore step of a running sync cannot put back what you are lifting. Removing an entry from the whitelist file is the one operation that is not instant: it takes effect with the next sync rebuild, and removing an exception never needs to be immediate.

Never let a saved ruleset reference a rip- set. iptables-restore discards the whole file when it hits an unknown set, so one stale line in /etc/iptables/rules.v4 can take your entire firewall down at the next boot. The agent rebuilds its chain on every boot by itself and needs nothing persisted. status warns when it finds rip- in rules.v4, rules.v6, /etc/sysconfig/iptables, ip6tables or /etc/nftables.conf. It does not look into /etc/nftables.d.

Last updated: · Maintained by the ReportedIP team

Security Focused
GDPR Compliant
Made in Germany
Back to Docs