ReportedIP Agent for Linux
The agent is a single static binary that does two jobs on a Linux server: it keeps the community blacklist in the kernel firewall, and it reports the attackers it finds in that server's own logs. Optionally it also blocks those local finds itself. It replaces the hand-written sync script documented on this site, and it takes over the work that fail2ban used to do.
linux/amd64 and linux/arm64.
Each server needs a licence, one is included
with Pro and three with Business; see Server licences.
Licences are added and removed under Agent Servers in your
account.
What it does
Community blacklist into the kernel
Five lists, one per exposed service (ssh, mail, web, ftp, edge), plus a sixth,
group, with what the other servers in your account group reported, fetched as often as your
licence allows and swapped into an ipset or an nftables set atomically. A list that came back
empty, or shorter than both min_entries and half of the set already loaded, is never applied, so a failed fetch leaves the
previous set in place. The group list is the exception: it has no minimum, and an empty group
list empties its set.
Local attacks recognised and reported
Fourteen source types, eighteen event sources, each with its own threshold. The agent tails the logs the server already writes, counts hits per address and reports the address once its threshold is reached. No log line, no user name and no URL leaves the machine.
Local finds blocked
On after the installation (ban.enabled: true). An address the agent caught
itself goes into a kernel set with a timeout, and a repeat offender is banned for longer each
time. Set it to false for a host that is only meant to report.
Where to go next
- Installation: the terms of use, the checks, every question, the flags and variables of a rollout, a host without systemd.
- Installation examples: Ansible and cloud-init, a golden image, control panels, a mail or database host, LXC containers and cloud firewalls.
- Configuration: every key of config.yaml, the thresholds, the ban ladder, the mail, and when a change takes effect.
- Detection and rules: the source types, port scans from the kernel log, adding a log the installer missed, what leaves the machine, and the rule files.
- Blocking: the two kinds of set, how a ban begins and ends, the chain in the kernel, and lifting a ban.
- Groups: fleet-wide bans shared across your servers and WordPress sites, the tariff limits, the whitelist and the webhook.
- Running it: commands, exit codes, the units, monitoring, the reputation of the host's own address, and the troubleshooting table.
- Licences: a licence per server, the report budget, groups from Pro, and what runs without one.
- Replacing fail2ban: the migration in three steps, and the way back.
Requirements
| Needed | Why, and what happens without it |
|---|---|
A packet filter you can write to: ipset with iptables, or nftables | apt install ipset iptables or apt install nftables, on the RHEL family dnf instead. Without one the host reports and does not block, which is a supported installation. ip6tables is used when it is there, and without it IPv6 bans are recorded but not enforced. |
| systemd | The one hard requirement of install, because it writes and enables three units. Checked by reading /run/systemd/system, not by the presence of systemctl. The binary itself does not depend on it: watch for the detection plus a sync from cron is a working host, set up by hand. |
| Root | It writes firewall rules and reads logs that are not world-readable. There is no reduced mode that runs as a normal user. |
| An API key from your account | Used twice during the installation, once to download the licensed build and once to register the host. One key per host is the recommendation; several hosts on one key is allowed and costs nothing extra, because a host is identified by its install ID. |
curl or wget, plus sha256sum | Only for install.sh, for the download and the checksum. It refuses to install when the checksum tool is missing. |
Nothing else. The binary is statically linked and built without cgo, so it brings no interpreter, no shared library, no package repository and no cron helper of its own. It was developed and measured on Debian 12 (bookworm) on arm64 with ISPConfig, nginx, Postfix, pure-ftpd and bind.
ipset or nftables, and what auto decides
Both backends do the same job. What matters is which one the host already uses, because two packet filters writing the same INPUT chain is how an afternoon disappears.
backend | What happens |
|---|---|
auto (default) | ipset is preferred when both ipset and iptables are on the host, otherwise nft is used. The preference exists because CSF and the published firewall guide use that tool chain, so a host with existing rules keeps them. |
ipset | Sets in ipset, rules in the rip-blacklist chain of iptables and ip6tables. Fails with a clear message when either tool is missing, rather than falling back. |
nftables | Sets and rules in the native table inet reportedip. Fails when nft is missing. |
Only a binary that exists counts, never a version string: iptables --version on a host
with the nft backend says something that looks right and means nothing. Once a host has synced, the
chosen backend is recorded, and a later change needs
reportedip-agent sync --migrate-backend instead of silently building a second set of
rules.
Check the host first
doctor is the command to run before installing. It changes nothing at all: no
file, no directory, no rule, no set. Everything it reports it read.
reportedip-agent doctor
| Section | What it answers |
|---|---|
system | The distribution as its own os-release names it, the kernel, the platform, whether systemd is really running, the time zone that logs without an offset are read in, and the free space where the state directory will live. |
ssh | Which unit runs sshd, and the port. With ssh.socket the port comes from the listening process, because sshd -T still answers 22 while the real port is in the socket unit. doctor says which of the two applied. |
firewall | Where ipset, iptables, ip6tables and nft are, which backend that resolves to, and whether the local ban set carries a per-entry timeout. That last point decides whether the kernel expires a ban on its own. |
panel | ISPConfig, Plesk, cPanel or DirectAdmin. Only the ISPConfig per-site log layout is known to this version; the others are named rather than guessed, because a wrong glob would have the agent read byte counters instead of access logs. |
sources | Every configured source resolved to real files, how many there are and how many are readable, the largest one with an estimated line count, and a note about files that are there and empty. On the largest panel host measured, the web globs resolve to 196 files. It also names a log this host writes that no source reads, which is what a service installed after the agent looks like, and that finding is worth exit 1. |
mail | Whether an alert could actually leave this host. Of eighteen hosts measured, fourteen could deliver mail, two had sendmail with a stopped MTA, and two had no MTA at all. |
verdict | The summary, and it maps onto the exit code. |
Reading the verdict
| Exit | Verdict | What it means |
|---|---|---|
0 | everything the agent needs is here | Install. |
1 | runs on this host, with the limits above | Every limit is printed by name. A missing firewall backend means this host reports and does not block, which is a valid installation. A source that resolves to nothing is worth fixing first. |
2 | cannot run on this host | Not a Linux host, or a host that can neither block nor read a single log. Fix the cause before installing. |
Run doctor again after the installation. With a config in place it reports the configured
sources instead of the detected ones, and it adds the clock drift per source that the daemon
measured. A source whose log stamps every line more than a minute away from the system clock has a
counting window that never fills, and that is the one fault which looks exactly like "the detector
does not work".
Quick install
Three commands on a host that has none of this yet. Run them in this order and the agent is reporting, the community lists are in the kernel, and nothing has been dropped that you did not agree to.
# 1. Install, as root. At a terminal it shows the terms of use and
# takes a yes, then asks for your key, then three short questions;
# an Enter answers drop and local bans on.
curl -fsSL https://reportedip.com/agent/install.sh | sh
# For automation, and for fifty hosts, nothing is asked: the terms
# are accepted up front and the key comes from the environment.
curl -fsSL https://reportedip.com/agent/install.sh | REPORTEDIP_ACCEPT_TERMS=1 REPORTEDIP_KEY="YOUR_API_KEY" sh
# 2. The first sync builds the chain and the sets. The installation
# itself touches no firewall rule.
reportedip-agent sync
# 3. See that it ran, and read what would be dropped before it is.
reportedip-agent status
reportedip-agent doctor
reportedip-agent test /var/log/auth.log
What each one leaves behind: the first places the binary in /usr/local/bin, writes
/etc/reportedip-agent/config.yaml from what it found on this host and installs the
three systemd units. The second creates the firewall chain and the sets, which the installation
deliberately does not touch, and fills them. The third is the proof: status shows an
entry count per list and the licence state of this host, doctor names anything missing,
and test replays your own log to show which address would have crossed which threshold.
The rules deny from the first sync on, because that is what the installation writes, so the
whitelist is the thing to get right before that sync and not two days after it. Answer
log instead for a host you want to read a journal on first. The
installation page is the same
installation explained in full, and that detail is the reason it exists.
The first hour, in order
Each step answers a question the next one depends on. In a different order a working installation looks broken.
reportedip-agent doctor. Before anything else, because it is the only command that changes nothing.reportedip-agent sync. The first run builds the chain, creates the sets and downloads the configured lists, one after another with a short pause between them.reportedip-agent status. Check that every configured list has a plausible IPv4 count, that the chain saysok, that the whitelist holds the addresses you expect, and thataccountshows your role and the licence state of this host.- Fix the whitelist. The installer whitelisted the address your SSH session came from, which is a guess. Replace it with the range you administer from, and add your monitoring, your backup host and your office range.
- Read
statusagain after a day. The packet counters of the chain say how much the rules actually stopped, andban listsays which addresses this host caught in its own logs. - Only on a host you answered
logfor: the rules are fully built and they match, they only log instead of dropping, so the kernel log shows exactly whatdropwould have cut off. Read one journal, then setmode: dropand runsync, which rebuilds the rules.
reportedip-agent doctor
reportedip-agent sync
reportedip-agent status
reportedip-agent whitelist add 203.0.113.0/24 "management"
reportedip-agent whitelist add 198.51.100.7 "monitoring"
reportedip-agent whitelist list
log for gives you those two days as well, a host that took
the default does not.
Upgrades and release notes
The agent looks for a new release every six hours, during the sync run. It verifies the download
against an Ed25519 signature with the public key built into the binary, puts the running binary back if the
swap fails, and restarts the watch service itself. reportedip-agent update does the same on
demand, and update --check only reports what is published.
Running the install command again upgrades a host as well. A host below the minimum version the
service supports still updates itself; until it has, the agent warns about it in its log, in
status (exit 1) and in update --check.
Downgrades are refused in general, so a host that pulled a newer version does not go back by itself.
What the current release changed is public and needs no key:
curl -s https://reportedip.com/agent/whats-new
reportedip-agent update --check
Changes to the REST API itself, the response fields and status codes an integrator has to care about, are in the API changelog instead.
Recent releases
- 0.3.52: The all-clear mail says what was over: the last state of the condition, when it began and ended and how long it lasted. The volume alarm says who the flood is: one client, a few, or a spread over many, and whether a rule counts the loudest. The mail carries only that verdict,
statusnames the loudest five clients with their share. - 0.3.51: A rule no longer shows as switched off after a restart when it runs in full. A guard keeps its suspension in the running daemon only; after an update the suspension was gone, but
status,rules status, the state mail and monitoring went on naming the rule as switched off. The daemon now reports at start that no suspension is active. - 0.3.50: The simulated rule
web-query-crawlno longer counts what a week of measurement on 36 servers found among its hits: crawlers that name themselves without the word bot, links out of a newsletter, cache busters, a bare flag as the first query key andwp-cronbehind a double slash. Among 100 addresses it had found three people, 30 declared crawlers and five tools; replayed with the change, no person is left. The rule stays simulated for another week. - 0.3.49: A client stuck in a loop on a directory listing is banned and reported. The new shipped rule
web-listing-loopcounts requests with a second sort parameter behind the first, such as/uploads/?C=N;O=D?C=N;O=D, which no browser sends; 10 in 10 minutes are a ban. The state mail now says for each problem what it means, which commands to run and where in this documentation to read on, and the mails about rule files, ignore files and the reputation of this host no longer arrive astext withheld. - 0.3.48: A filtering mail relay in front of your server is no longer banned for the senders it forwards.
postfix-rejectcounted a rejected envelope sender (Sender address rejected) against the connecting server, and behind a filtering relay that server is always the relay. Five messages from senders without a mailbox in one hour were enough to ban and report it, and no mail came in until the ban ran out. A rejected sender no longer counts. Measured over 20 days of a mail server, this changed the verdict for one address only, the relay. - 0.3.47: A logged-in WordPress admin is no longer banned for using the dashboard.
admin-ajax.phpanswers 400 for every action without a handler, and the dashboard of a logged-in admin produces that dozens of times an hour. Theweb-appdetector counted it like any other 400 under/wp-admin/, so an admin could reach the threshold.admin-ajax.phpis no longer counted, every other path under/wp-admin/still is. - 0.3.46:
web-query-crawlcounts only clients posing as a browser. A crawler that names itself without a word like bot in its name, measured live with a research crawler, could reach the threshold. The rule now counts only lines whose user agent starts withMozilla/, which every browser and every disguised crawler sends; a tool or crawler with its own name is never counted. The list of crawler names stays for the ones that start withMozilla/as well. - 0.3.45: An operator rule is no longer shadowed by a shipped rule. A log line is counted by one rule only. Until 0.3.44 the shipped regex rules ran first, so the shipped
web-query-crawl, which only simulates, took every query-string line with status 200 before an operator rule for the same requests could see it, and the operator rule could not ban. The regex rules of a source now run in the order operator, signed pack, shipped; the compiled detectors still run before all of them. A young operator rule therefore takes its lines from a shipped rule that would report them, and reports nothing itself during its first day. A suspended rule passes its lines on. A rule the burst guard suspended whole used to keep every line it matched, and its events were dropped. Now that operator rules run first, that would have silenced the shipped rules of the same source until the file was fixed; the next rule of the chain counts the line instead. - 0.3.44: A volume alarm per web log. The daemon counts every line of every log it reads and keeps a smoothed baseline of lines per hour: a plain running mean during the first week of full hours, then an exponential average with a week of memory. When the running hour of a web log (sources
webandweb-error) reads at leastvolume_factortimes that baseline and at leastvolume_min_lineslines, the health statevolumegoes to warning, thirteen states now. The defaults are 8 and 20000;volume_factor: 0switches the alarm off, otherwise it takes 2 to 1000, andvolume_min_linestakes 0 to 100000000. Nothing alarms before 24 full hours of baseline. The alarm bans and reports nothing: its text names the loudest site (the name of the log's directory, usually the vhost) and points toreportedip-agent statusfor the full list, and it clears after a full quiet hour.statusshowslines=andhour=<running>/<baseline>per source, withLOUDwhile alarming,/-while the baseline is younger than a day and/offfor a log the alarm ignores.status --json(schema 1, additive) gainslines,hour_lines,baseline_hourandvolume_alarmper source. The baseline survives a restart within an hour; a daemon that stood still longer starts the week again. A shipped rule,web-query-crawl, in simulation. It countsGETrequests with a query string, status 200 and no referer of their own (empty, or a search engine such as Google or Bing), 20 in 60 minutes per address, category 19. It ignores static assets,/wp-json/and/wp-admin/paths,wp-cron, the ordinary query keys of WordPress, WooCommerce and tracking (p,s,wc-ajax,utm_*,fbclid,gclidand the like) and every line whose user agent declares a crawler (bot,crawler,spider, Barkrowler, Turnitin, GPTBot, Ahrefs, Semrush and others): a crawler that says who it is is not banned by this rule. A forged crawler name escapes it on purpose; the scanner and login rules still count it. It reports nothing, a crawl is a nuisance and not community data. It ships withsimulate: true, so it logssimulated banwarnings and bans nothing. Switch it off withreportedip-agent rules disable web-query-crawl; arm it with an override file under/etc/reportedip-agent/rules.d/withformat: 1and, underrules:,- id: web-query-crawlandaction: {simulate: false}, a policy field, so no observation day follows. Measured on five servers over two days before shipping: no visitor and no monitoring over the threshold, only crawlers and bots. - 0.3.43: A ban time per rule.
action.time_minutesin a rule file sets the first ban time of that rule, from one minute to a year; the escalation andmax_time_minutesof the config apply as before, and a repeat escalates from the first time of the rule that fires it. The shipped rules set none, so a host changes nothing by updating.rules listshowsban 120 min. Simulation per rule.reportedip-agent rules simulate <id>keeps the rule detecting and reporting and turns its ban into a warning,simulated ban, once per rule and address for the rule's first ban time, capped bymax_time_minutes, with the address, the rule and how to end it. Find it withjournalctl -u reportedip-agent | grep "simulated ban".rules enable <id>ends it. A rule that does not ban cannot be simulated. Ignore patterns per source./etc/reportedip-agent/ignore.d/<source>.confholds one regular expression per line; a matching line is dropped before any detector sees it. A health check, a monitoring probe or an ACME path needs no rule override any more, and nothing is put under observation. A pattern that would swallow a line the shipped rules are tested to detect is refused, so are patterns under four bytes and patterns that match the empty string; a bad file is rejected alone, the rules keep running, andrules check,statusanddoctorname the file. The daemon reports it under the new health stateignore. A pattern applies to every source that reads the same log.statuscountsignored=per source,testprintsignored:.ban list --jsonandban list --plain. The JSON (schema 1) carries every record with the kernel state, count, source and categories, plus the kernel-only entries.--plainprints the addresses in effect, one per line, for an nginxdenyinclude or an HAProxy ACL file, in any mode. When the kernel set cannot be read the export exits 1 and prints no list, so a script never reloads a short deny file. A policy override no longer makes a rule young. Until 0.3.42 any override of a shipped rule, evenaction: {report: false}alone, put the rule under observation for a day: it banned and did not report. Now only an override ofsource,event,match,addr,thresholdorexamplesdoes. A host whose operator silenced a ban that way reports again after this update; the burst guard and the whitelist gate stay in place. A wrongcategoriesoverride now reaches the feed without the day of observation as well: wrong labels, never wrong addresses, and that trade is deliberate. - 0.3.42: A refused page resource no longer counts as a blocked request. The
web-errorsource counted everyaccess forbidden by ruleandclient denied by server configurationline, whatever was asked for. A stylesheet with a mistyped font path under a dotfiledeny allrule made one host write four refusal lines per page view and ban fifty visitors of a customer portal in a day, none of them a scanner. A refusal of a font, image, style or script, or of anything under/.well-known/, which browsers and password managers probe on their own, is now ignored. Probes for/.env,/.git/and the like keep counting. The font suffixes.ttf,.otfand.eotjoin the asset list of the access log source as well. A host with such a site should look at its error log anyway: the page is broken, the agent just stops punishing its visitors for it. - 0.3.41:
--admin-iptakes a CIDR range for real. Up to 0.3.40 a range was cut down to its network address, so only that one address landed in the auto whitelist. Withbackend: autothe agent prefers ipset, and the installer notes now say so. - 0.3.40:
status --jsonfor monitoring: one document with schema 1 and the same judgement and exit code as the text, sent without a request to the API, and a broken configuration (exit 2) still prints a document.statusnow reports a stopped watch daemon as exit 1: the daemon leaves a heartbeat every 30 seconds, and one older than two minutes counts as stopped. Setup for common monitoring systems is under Monitoring. - 0.3.39: the port scan rule no longer bans FTP clients in passive mode. Every data connection goes to a port the server opens for that one transfer, the rule knew the listening ports only from the last sync, and so uploading a folder looked like a scan within seconds. The rule now asks the kernel whether a socket listens on the port at that moment (
-m socket --nowildcardwith iptables,socket transparentwith nftables), and the passive range of a running pure-ftpd, proftpd or vsftpd is read from its configuration and left out of the rule. A server without a configured range gets the ephemeral port range of the kernel left out instead. The chain is rebuilt once at the next sync. - 0.3.38:
installtakes the log level, as--log-level debug|info|warn|erroror asREPORTEDIP_LOG_LEVEL. The default is unchanged atwarn. A rollout that wants to read along on every host saysinfoonce instead of editing the config of every host afterwards, and a level the parser would refuse is refused before anything is written. - 0.3.37: a service that listens on loopback only is no longer treated as reachable. The port probe counted every listener in the kernel table whatever address it was bound to, so the stub resolver that holds
127.0.0.53:53on a normal Debian host looked like a name server, and a mail server that only takes mail from its own machine looked like one that takes it from the internet. A port that has a loopback socket next to a real one still counts. This also decides which ports the port scan rule treats as open. - 0.3.35:
doctornames a log this host writes that no source reads, and says where to add the source. Detection runs once, at install time, and that stays as it is, because a service stopped for maintenance must never switch a source off. The other direction had nobody watching it: a mail server installed on a host that had none before was not picked up, and the host looked healthy while that service went unwatched. The finding is worth exit 1. - 0.3.34:
installreads the web logs out of the web server's own configuration, the virtual host files underconf.dandsites-enabledincluded, instead of a list of default names. A real hosting setup names its logs after the site, so the default paths found the default log and missed the rest: on a measured host two further logs, one of them an access log with real addresses, would have gone unread. A log whose format masks the client address is still left out, and every log taken this way is named in one line at install time. - 0.3.32:
statusno longer calls a host degraded because its group list is empty, which is the normal state of a key that is in no group, anddoctorno longer claims there is no log source on a host whose sshd writes to the journal instead of to a file. - 0.3.29: a fresh install writes
log_level: warninstead ofinfo, so a new host stays quiet in the journal until something wants attention. The routine lines, a list updated, a report queued, a local ban placed, are unchanged and one word away. A host that is already installed keeps what its config file says. - 0.3.27: the whitelist of the group: addresses and prefixes a group in your account names are never blocked and never reported by any member. The sync reads it with the group list, writes it to
group-whitelistunder the state directory and applies it in the same run to the kernel whitelist set, the list filter and the reporting gate;whitelist listandstatusshow the entries with their notes. The whitelist of this host, the auto whitelist and the built-in layers stay as they are. - 0.3.26: a fast port scan stayed below its own threshold: the scan rule logged ten SYNs a minute with the kernel's default burst of five, so a scanner that probes a thousand ports in a few seconds left five lines and the rule needs ten. The rule now bursts twenty, the first twenty probes of a scan are logged at once, and the rate keeps the log quiet after them. The chain is rebuilt once.
- 0.3.25: a host whose key is in no group lost its chain on 0.3.24, because the group set is only created by its first feed and the rule that names it was refused. Every list set is now created before a rule names it. 0.3.24 was withdrawn.
- 0.3.24: the group list
groupin the setrip-groupon every port; the reputation of the address this host reports from as the health conditionreputation; port scan detection from the kernel log with the sourcescan. The chain is rebuilt once after this update and its packet counters start over.
Resource use
Measured on a Debian 12 arm64 host with seven log files under the agent: 16.6 MB of resident memory. There is no interpreter to start, no database and no cache directory to warm up, which is most of the reason it stays that small.
What the agent is not
It is not a malware scanner, not a web application firewall and not a replacement for Imunify360. It does not read your files, it does not inspect request bodies and it does not quarantine anything. It blocks and reports addresses, and it does that on a small, auditable surface. For protection at the level of a single request on a WordPress site, use the Hive plugin instead. The two run on the same machine without interfering with each other.
Last updated: · Maintained by the ReportedIP team