Running the Linux Agent
The commands and their exit codes, what the three systemd units do, how status and doctor fit into monitoring, test in place of fail2ban-regex, and the troubleshooting table: symptom, cause, command.
Commands
| Command | What it does | Exit |
|---|---|---|
REPORTEDIP_KEY=K install [--accept-terms] [--admin-ip IP|CIDR] [--ssh-port N] [--mode log|drop] [--ban|--no-ban] [--notify-email A] [--log-level L] [--expert] | Detects the services, writes the config, the auto-whitelist and the install ID, installs and enables the units, and verifies the key. The key comes from the environment; --key still works and warns that its value is in the process list. Never overwrites an existing config. Root only. --expert also asks for the ban ladder, the thresholds, the limits and an SMTP server. | 0, 2 on a rejected key before anything is written or on a config problem, 1 when the key check after the install fails |
sync | One feed pass: rebuild the whitelist set, fetch each list conditionally, swap what passed the size check, rebuild the chain, restore missing local bans, run the housekeeping, check for an update every six hours. This is what the timer runs and what runs at boot. | 0, 1 degraded, 0 also when another sync holds the lock |
watch | The daemon. Tails every source, counts, reports and bans if that is enabled. Stops on SIGTERM. | 1 when the queue lock is still held after a minute or the daemon stops on an error, 2 on a config problem |
report-queue | Sends the queued reports once. Useful after an API outage, or on a host where the daemon does not run. | 0, 1 degraded |
status | Version, backend, mode, tools, per-list counts and last success, whitelist, chain check, per-source read positions and hit counts, queue and sender, account and licence, the group of this key, the reputation of the address this host reports from, disk, local bans, open health conditions. | 0 healthy, 1 degraded, 2 config |
doctor | What the host offers and what is missing, including a log this host writes that no source reads. Changes nothing, works without a config. | 0, 1 with limits, 2 cannot run |
test <file>... [--type T] [--rule ID] [--lines] | Runs the detectors over real log files. Reports nothing, bans nothing, writes nothing. --rule runs one rule from a rule file on its own. | 0, 2 on a file or type problem |
ban list [--json|--plain] | add <ip> [minutes] | rm <ip> | Shows the local bans against what the kernel really holds, sets one by hand, lifts one. --json prints the list as one document, --plain only the addresses in effect, one per line, for a proxy or a script. | 0, 1 on a kernel failure, with --json and --plain then without a list, 2 on a bad argument or while a sync holds the lock; add also on a whitelisted address or when the host has no firewall backend |
unban <ip> | The same as ban rm, under the word an operator types when something is on fire. | as above |
whitelist add <ip|cidr> [comment] | list | rm <ip|cidr> [--auto] | Manages the never-block, never-report list. add writes the file and the kernel set at once. list prints your file, the auto-whitelist and the whitelist of your group, the last on lines marked group: with their notes. --auto removes an entry the installer wrote. | 0, 1 when the file changed but the set did not, 2 on a bad address or while a sync holds the lock |
update [--check] | Checks the distribution point and replaces this binary, then restarts the watch service. --check only reports. This is the one command that reads the config loosely, so it still works on a host whose config is newer than its binary. | 0, 1 when the check or the install failed, 2 when the config cannot be read or the state directory cannot be created |
housekeeping | Removes the agent's own leftovers and prints the numbers. The sync run does the same silently. | 0, 2 on a config or state problem |
rules list | show <id> | check [file...] | export <id> | status | enable <id> | disable <id> | simulate <id> | The detection rules: the shipped ones and the files under /etc/reportedip-agent/rules.d that override them or add to them. check tests a file before it goes live, export prints a rule as a starting point, status adds the hits and which rules are young, enable and disable switch one rule on or off, simulate keeps a rule reporting and turns its bans into warnings in the log until enable ends it. | 0, 1 when a file was skipped or the operator rules are not used, 2 on a bad argument or when check finds a problem |
version | Prints the version and nothing else. | 0 |
help | The command list, with the config and state paths. | 0 |
test, instead of fail2ban-regex
test runs the detector chain over a log file you already have, with the thresholds and
the whitelist of this host, and prints which addresses would have crossed a threshold and where in
the file. It reports nothing, blocks nothing and writes nothing, so it is safe on a production host
and it is the honest way to answer "would this have caught last week's attack".
# A configured source: the type is taken from the config
reportedip-agent test /var/log/auth.log
# Any other file: name the type yourself
reportedip-agent test --type postfix /var/log/mail.log.1
# A rotated log and its successor as one stream, in this order,
# so a window that spans the rotation counts like the daemon saw it.
# A .gz is read inflated.
reportedip-agent test /var/log/mail.log.2.gz /var/log/mail.log.1 /var/log/mail.log
# Every single hit, with line number, address and timestamp
reportedip-agent test --lines /var/log/auth.log
The summary line gives the lines read, how many matched, how many an ignore pattern dropped, how many carried no usable timestamp and how many were skipped as overlong. Then one row per address with its hits, its resets, whether it would have been banned and how often, the time of the first hit, and the threshold that was applied. An address that is on your whitelist is shown with the layer that matched and marked as never banned and never reported, which makes this the fastest way to prove a whitelist entry actually works.
Two things test does not do. It does not guess a format: without --type the
file has to be one of the configured source paths, and otherwise it tells you so and lists the valid
types. And it does not apply the ban-history weight, so the daemon needs fewer hits for an address
with previous bans than this replay suggests.
Running it
Three systemd units, written and enabled by install and brought up to the state of the
running version by every sync. Put your own changes into a drop-in under
/etc/systemd/system/<unit>.d/, where they survive an update. A unit you edited
directly is replaced, the previous version is left beside it as <unit>.bak, and
the journal names both.
| Unit | When it runs | Details |
|---|---|---|
reportedip-agent-sync.timer | Every 15 minutes, plus a fixed random offset of up to 2 minutes | It has to fire at the fastest rate any licence can ask for, because it is the only thing that starts a sync. How many of those runs really fetch is decided by the licence, and a run that comes too early for its plan fetches nothing. |
reportedip-agent-sync.service | At boot, and on every timer tick | Enabled for multi-user.target, after network-online.target and after every firewall service, with no random delay. One sync pass: whitelist set, the six lists, the chain, the restore of missing local bans, the housekeeping, and an update check every six hours. |
reportedip-agent.service | Continuously | The watch daemon. Restarts on failure after ten seconds, at most five times in five minutes, and never on exit code 2, so a misconfigured host stops with a readable reason instead of being retried for ever. |
Both units run with NoNewPrivileges, ProtectHome,
PrivateTmp, a locked personality and a restricted set of address families.
| What runs by itself | How often | What it does |
|---|---|---|
| Log rotation | On every write | The agent rotates log_file at log_max_mb and keeps log_keep generations. It does not depend on logrotate. Every line also goes to stderr and so into the journal of the unit. |
housekeeping | In every sync | Old queue files, expired dedup entries, stale read positions, leftover temporary and lock files, expired ban records, surplus generations of the agent's own log, files in the mirror of the shipped rules that the binary no longer carries, and the previous binary once it is a month old. Run it by hand to see the numbers. |
| Self update | Every six hours | A new release is verified against an Ed25519 signature with the public key compiled into the binary before it is installed, so a tampered download fails on your machine rather than being trusted because it arrived over HTTPS. A swap that fails puts the running binary back, so a broken update leaves the host on the version it had. |
| State mail | Per condition, with notify.cooldown_hours | One mail when something is wrong, one when it is fixed. From Pro it can go out through the relay of reportedip.com. |
| The disk floor | On every queue write | Below disk_min_mb the agent queues no new reports and says so, while detection and blocking keep running. |
systemctl list-timers reportedip-agent-sync.timer
systemctl status reportedip-agent.service --no-pager
journalctl -u reportedip-agent.service -n 50 --no-pager
journalctl -u reportedip-agent-sync.service -n 50 --no-pager
tail -n 100 /var/log/reportedip-agent.log
Exit codes
| Code | Meaning | What to do |
|---|---|---|
0 | Healthy | Nothing to do. |
1 | Degraded, but it will try again | Read the output. A retry may well fix it. An open error condition in the health record colours the exit code even when the run itself went through, because an exit 0 next to a recorded error is no signal for a monitoring system. For status, a watch daemon that stopped is one of these reasons. |
2 | Not repeatable without a human | The config, the file permissions, a missing tool, a lock held by another process. Run doctor. systemd will not retry the watch service on a 2. |
That makes reportedip-agent status usable as a monitoring check directly, with no
wrapper script and no output parsing. status --json prints the same result as a JSON
document; the setup for Zabbix, Nagios, Icinga, Checkmk and Prometheus is under
Monitoring.
The reputation of this host's own address
The sync run asks the server what it thinks of the address this host reports from, at least once a day and on every run while the condition below is open. If that address
is itself listed in the community database, status warns and exits 1. With a confidence of 75 or more
the health condition reputation is open as well, with the confidence, the number of reports and the link to the
delisting page in its text. It is a warning and not an error, because a listed
address is a fact about the network the host lives in and not necessarily about the host: a shared
address, a carrier NAT or a neighbour behind the same proxy can put it there. It is still worth a
look, because a host that reports from a listed address is what a compromised host looks like from
outside. Check what leaves the machine, request the delisting once it is clean, and the condition
clears with the next sync after the delisting; there is nothing to reset on the host. Below 75 no health condition is raised: a handful of old reports is honest data and not something an operator can act on, and no feed serves an address at that score.
Bot floods: what the agent does and does not do
A page answered with 200 is never an attack. A flood from thousands of addresses that each ask for an ordinary page once or twice is load, and a rule that counts per address sees one request from each of them and nothing to act on. The agent does not ban such a swarm and is not meant to, because the same lines come from real visitors. It does two things instead: it tells you that a site is being flooded, and it counts the one pattern in the flood that visitors do not produce. The rest belongs in the web server. The article on bot flood detection and countermeasures goes through a worked case with measured numbers.
The volume alarm
The daemon counts every line of every log it reads and keeps a baseline of lines per hour for each of
them: a running mean over the full hours of the first week, then an average with a week of memory.
When the running hour of a web log, one of the sources web and web-error,
reads at least eight times its baseline and at least 20,000 lines, the health state
volume opens as a warning. Its text names the loudest site, which is the name of the
directory the log lies in and usually the virtual host, and reportedip-agent status shows
every loud log. The state clears after a full quiet hour.
Since 0.3.52 the alarm also says who the flood is. While a log is loud, the daemon counts its clients, up to 20,000 addresses, and adds a verdict to the text: one client made most of the lines, and whether a rule counts it; a few clients made most of them; or the lines are spread over many clients, where a rule fits better than bans. The mail carries only that verdict and never an address. reportedip-agent status names the five loudest clients under the log, with their share and ruled=, the number of their lines a rule took. ruled=0 means no rule counts them, and reportedip-agent ban add <address> bans one. The list stays after the flood until the next one starts. The all-clear mail repeats the last state of the alarm with its start, its end and how long it lasted.
Nothing alarms before the baseline holds 24 full hours, so a fresh install stays quiet on its first
day. The alarm bans nothing and reports nothing: it makes the flood visible, and what to do about it
is your decision. A flood that grows slowly over more than a week lifts the baseline with it, so the
factor catches jumps and not ramps. Two keys in config.yaml set the limits;
Monitoring shows where the running hour and the baseline appear in
status.
volume_factor: 8 # times the usual hour; 0 switches the alarm off, otherwise 2 to 1000
volume_min_lines: 20000 # lines in the running hour, at least; 0 to 100000000
The rule web-query-crawl
The pattern visitors do not produce: GET requests with a query string, answered with 200,
without a referer of their own (empty, or a search engine such as Google or Bing), 20 of them inside
60 minutes from one address. That is a client walking calendar filters, attachment ids or faceted
listings. The rule 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,
gclid and the like), and every line whose user agent declares a crawler
(bot, crawler, spider, GPTBot, Ahrefs, Semrush and others). It counts only clients whose user agent starts with Mozilla/, which every browser and every disguised crawler sends, so a tool or crawler that names itself is never counted; the name list covers the crawlers that start with Mozilla/ as well, such as Googlebot.
Since 0.3.50 it also leaves out what a measurement over one week and 36 servers found among its hits,
each by shape and not by name: a user agent that names itself as (compatible; Name), carries
a token inside the browser bracket (like Gecko; Name) or a contact address; links out of a
mail, such as newsletter views and open or click tracking (noti, fluentcrm,
mailpoet_*, mc_cid); a cache buster, a number under a short key such as
cb, nc, nocache or *_check; a bare flag as the first
key, such as ?start_customizing; and wp-cron behind a double slash.
A crawler that says who it is is not banned by this rule. It may be an SEO service the site owner
booked, and the place to slow it down is the web server or robots.txt. That is also why
there is no allowlist of user agents to keep up to date: the rule counts only clients that pose as
browsers. A forged crawler name escapes it on purpose; the scanner and login rules still count such a
client. The rule reports nothing, because a crawl is a nuisance and not community data.
It ships simulated: it writes a simulated ban warning to the log and bans nothing. Switch
it off, or arm it with an override file. simulate is policy, so arming the rule starts no
day of observation; the daemon picks the file up within a minute.
# What the rule would have banned so far
journalctl -u reportedip-agent | grep "simulated ban"
# Switch it off
reportedip-agent rules disable web-query-crawl
# Arm it: real bans instead of simulated ones
cat > /etc/reportedip-agent/rules.d/50-web-query-crawl.yaml <<EOF
format: 1
rules:
- id: web-query-crawl
action: {simulate: false}
EOF
reportedip-agent rules check
In the web server
Everything that limits load regardless of who sends it belongs in the web server. Four measures, shown for nginx; adapt the path, the domain and the numbers to your site.
- A
limit_reqzone with a fixed key for the filter path. A zone per address does nothing against thousands of addresses with one request each. A zone keyed on$server_namecaps how many requests per second reach PHP on that path, however many addresses send them. - A FastCGI cache keyed on the query string. With
$request_uriinfastcgi_cache_key, a repeated filter call is answered without PHP. Fewer filter combinations make it work better:rel="nofollow"on filter links and the filter path inrobots.txt. - A 429 for requests without the site's own referer, as an emergency brake. It holds until the bot forges the referer.
- A prefix block of rented networks, after a whois check. A block of a hosting provider can go as a whole. A consumer provider cannot, because one prefix there holds many customers behind carrier-grade NAT.
# http {}
limit_req_zone $server_name zone=filter_all:1m rate=10r/s;
map $http_referer $own_referer {
default 0;
"~^https://(www\.)?example\.org/" 1;
}
# server {}
location ^~ /events/ {
limit_req zone=filter_all burst=20;
limit_req_status 429;
if ($own_referer = 0) { return 429; } # emergency brake, only while it holds
try_files $uri $uri/ /index.php?$args;
}
# server {}, only after whois names a hosting provider
deny 198.51.100.0/24;
Troubleshooting
One symptom, one likely cause, one command. reportedip-agent doctor and
reportedip-agent status between them answer most of these, and they are the first two
things support will ask for.
| Symptom | Cause | Command |
|---|---|---|
| Refuses to start and names its config file | The file is readable by group or others, and it holds your API key. | chmod 0600 /etc/reportedip-agent/config.yaml && chown root:root /etc/reportedip-agent/config.yaml |
| Exit 2 with "field ... not found in type" | An unknown key in the config. The parser is strict on purpose. | Remove or correct the key the message names. The list of valid keys is above. |
| Every command exits 2 after a rollback | The binary is older than the config and cannot parse a newer block. | reportedip-agent update, the one command that reads the file loosely and names the keys it does not know. |
An older binary exits 2 on a config that carries notify.relay | That key is newer than the binary. The same class of trap as the row above, and it has left hosts unprotected twice. | Remove the relay: line from the notify block, or go forward instead of back: reportedip-agent update. |
| The sets exist but are empty | Either the feed was refused or the list failed the size check. | reportedip-agent status says which of the two, and the per-list line carries the last error. |
status says unlicensed | The account has no licence to spare for this host. | Add one under Agent Servers. The list in the kernel keeps working meanwhile. |
| Nothing is ever reported | The source that would have fired was never detected, or its threshold is not reached. | reportedip-agent test /path/to/log, then reportedip-agent status and read the sources section. |
| Nothing is ever blocked, although reports go out | ban.enabled was set to false, or mode to log (by default both block). Two switches. | reportedip-agent ban list says which of the two, in its last lines. |
| No source state at all | The watch daemon never ran. | systemctl enable --now reportedip-agent.service |
A source shows files=0 | The path or glob resolves to nothing on this host. | reportedip-agent doctor names the source and the pattern it tried. |
| A service installed after the agent reports nothing | Detection runs once, at install time, and that is deliberate: a service stopped for maintenance must never switch a source off. The other way round nothing happens by itself either, so a mail server added to a host that had none before is not picked up. | reportedip-agent doctor names the log that service writes, says no source reads it and exits 1. Add the source to config.yaml and restart the watch service. |
| A log source is listed as unreadable | The file is not readable even for root. Usually a control panel that sets mode 000 on rotation. | ls -l on the path, and fix the rotation that produced it. |
| A source has hits but never reports | Its log timestamps are more than a minute from the system clock, so the counting window never fills. | reportedip-agent doctor, the drift lines under sources. Fix the time zone of that log. |
| Your own address got banned | It was not on the whitelist. | reportedip-agent whitelist add <address>. Instant, because the whitelist rule sits before the block rule. |
A ban is in bans.json but not in the kernel | A reboot without a sync, or a firewall reload that took the set with it. | reportedip-agent sync restores it with the time it has left. |
An entry in the kernel with record none | Somebody banned by hand with ipset or nft, or the store was lost. It expires, but no reboot brings it back. | reportedip-agent ban list |
| A set exists with the wrong type, or without a timeout | A leftover from the documented shell script, or from another tool. | ipset destroy <set> and then reportedip-agent sync, which rebuilds it correctly. |
Both tool chains have rip- objects | The backend was changed without a migration. | reportedip-agent sync --migrate-backend, and status prints the exact removal commands for the other side. |
| The firewall is gone after a reboot | A saved ruleset references a rip- set, and iptables-restore discards the whole file on an unknown set. | grep -n rip- /etc/iptables/rules.v4 /etc/iptables/rules.v6 and remove those lines. The agent needs nothing persisted. |
| A removed source is still being read | A systemctl restart is not enough for a removed source. | systemctl stop reportedip-agent.service && systemctl start reportedip-agent.service |
| Reports stop but detection continues | The disk-space floor was reached. | Free space, then reportedip-agent housekeeping. |
| HTTP 429 on reports | The daily report limit of your plan. | The limits are listed under Authentication. |
| The queue keeps growing | The sender is paused after a failure, with a backoff. | reportedip-agent status shows the pause and the reason; while the daemon runs it sends by itself, and reportedip-agent report-queue only sends when the daemon is not running. |
| A traffic flood the agent does not ban | Many addresses with a few ordinary requests each, answered with 200. A swarm with one request per address stays below every rule that counts per address, by design. | Limit it in the web server with limit_req, a cache or a 429 brake. The bot flood guide shows the measures with measured numbers. |
| Every web hit is the same handful of addresses | nginx sees a proxy or Cloudflare and not the visitor. | Configure set_real_ip_from. reportedip-agent install warns about exactly this case. |
| "more addresses than the counter tracks at once" | A scan wider than the per-source counter. Not a fault. | Nothing. A scan that wide is a case for the community list, not for local bans. |
status says the host is behind on versions | The automatic update is not getting through, or auto_update is off. | reportedip-agent update --check, then reportedip-agent update. |
status says group=none | The key of this host is in no group. Not a fault: the set rip-group stays empty. | Create a group under Groups in your account and put the keys of the hosts in it; the next sync fills the set. |
status warns about the reputation of this host's address | The address this host reports from is listed in the community database. | Check what leaves the machine, then request delisting. The condition clears with the next sync after that. |
No rip-scan: lines in the kernel log | No scan source in the config, or the sync that builds the rule has not run since it was added. | Add - type: scan under sources, restart the watch service, then reportedip-agent sync. Whitelisted and already dropped addresses never produce a line. |
Last updated: · Maintained by the ReportedIP team