Configuring the Linux Agent
Every key of /etc/reportedip-agent/config.yaml with its default and its range, the detection thresholds per source, the ban ladder, the operating limits, the mail that arrives when something is wrong, and which change takes effect on the next sync, on a restart, or within a minute.
Configuration
One file, one whitelist, one state directory. What the installer writes is already correct for the
host; most installations only ever change two things, the value of mode and whether
local banning is on.
| Path | Owner, mode | What it holds |
|---|---|---|
/etc/reportedip-agent/config.yaml | root:root, 0600 | Every setting. The agent refuses to start when group or others can read it, because the file holds your API key, and it prints the chmod that fixes it. |
/etc/reportedip-agent/whitelist.conf | root:root, 0600 | Never block, never report. One address or CIDR per line, # starts a comment. A broken line is skipped with a warning. Created by install, with an explanatory comment at the top. |
/var/lib/reportedip-agent/ | root:root, 0700 | State: offsets.json (read positions), queue/, dedup.json, bans.json, health.json (health), install-id, auto-whitelist, group-whitelist (written by the sync from your group, not by hand), feed-token, the lock files. |
/var/log/reportedip-agent.log | root:root, 0640 | The agent's own log. It rotates the file itself, so logrotate is neither needed nor in the way. |
/usr/local/bin/reportedip-agent | root:root, 0755 | The binary. One file, statically linked. |
sync_interval, the interval comes from your licence,
and there is no whitelist, the key is whitelist_file. After an edit,
reportedip-agent status exits 2 and names the key when the file cannot be read.
Every key of config.yaml
Only api_key is required. Every other key has the default below, and a key you leave
out keeps that default. A value outside its range is an error that names the key, never a silent
clamp. Dotted names below are nesting, ban.enabled is enabled: indented
under ban:.
| Key | Default | Accepted | What it does |
|---|---|---|---|
api_key | none | your key | The only key without a default. REPLACE_ME counts as missing. |
api_url | https://reportedip.com/wp-json/reportedip/v2 | absolute https | The REST base. Plain http is accepted on the loopback only, for a local proxy. |
update_url | from api_url, path /agent | absolute https | Where agent builds come from. Set it for a mirror. |
auto_update | true | true, false | Install a newer release during the sync run. false for a host whose version is decided elsewhere; update by hand still works and status still reports how far behind the host is. |
log_level | warn | debug, info, warn, error | Nothing else is accepted. A fresh install writes warn, so a new host stays quiet in the journal until something needs attention; the routine lines (a list updated, a report queued, a local ban placed) are at info. A host that is already installed keeps whatever its config file says, and --log-level or REPORTEDIP_LOG_LEVEL sets the value a fresh install writes, so a fleet that wants info does not have to be edited host by host afterwards. The handful of failures that stop the host before the logger exists print at a level above error and always show, whatever this key says: that level is not a verbosity a host can turn down, it is the one line a host that just stopped always leaves behind. |
log_file | /var/log/reportedip-agent.log | path, or empty | The agent's own log, in addition to stderr, so journald keeps everything it has today. Empty switches the file off. |
log_max_mb | 10 | 1 to 1024 | Size at which the agent rotates its own file. |
log_keep | 3 | 0 to 20 | Rotated generations kept. With the defaults the worst case on disk is 40 MB. |
backend | auto | auto, ipset, nftables | Which packet filter is written. A later change needs sync --migrate-backend. |
mode | drop | log, drop, off | What the rules do. Applies to the feed lists and to the local bans. |
confidence | 90 | 1 to 100 | The feed threshold. Lower means a longer list and more borderline addresses. The server names a floor for your plan and the higher of the two values wins. |
limit | 50000 | 1 to 50000 | Maximum addresses per list request. |
lists | ssh, edge | ssh, mail, web, ftp, edge, group | Which feed lists are active. ssh and edge are always on and cannot be switched off. group, the list of your account group, is always on as well and needs no entry: on a key in no group it is simply empty. |
lists.ssh.ports | from the real listener | 1 to 65535 each | The ports the ssh rule matches on. The only per-list option; the other lists use fixed port sets. An empty list makes the rule match every port. |
min_entries.<list> | ssh 1000, mail 500, web 500, ftp 500, edge 1000 | 1 or more | The sanity floor: below this many addresses a list is not swapped in. A minimum of zero would let an empty list go live, so it is refused. The group list has no minimum and refuses the key: an empty group is a valid one. |
whitelist_file | /etc/reportedip-agent/whitelist.conf | path | Your never-block, never-report list. |
sources | one sshd entry | list of entries | The log sources. A file with a sources key decides alone, so an entry you add goes next to the ones the installer wrote, not into a second block. |
sources[].type | none | one of the fourteen | An unknown type is an error that lists the valid ones. web-app is not one of them. |
sources[].path | none | one file | Set path or glob, never both. With neither, the agent picks the channel itself, journald or the distribution's default file. The web source has no default and resolves to nothing without one of them. For scan that is the kernel journal, else /var/log/kern.log or /var/log/messages. |
sources[].glob | none | at most 5 wildcard segments | A pattern over many files, rescanned every ten minutes. |
sources[].exclude | none | shell patterns | Drops files a glob caught. Not regular expressions. A pattern without a slash matches the file name alone, one with a slash has to cover the full path. A pattern that matches nothing is warned about at start. |
sources[].poll_minutes | 5 | 1 to 60 | Only for imunify360, which polls a command instead of reading a file. On any other type it is an error. |
min_hits, window_minutes | 5, 10 | 1 or more | The global pair for every event source without its own entry in the built-in table. |
web_min_hits, web_window_minutes | 50, 120 | 1 or more | The same pair for the web source alone, which needs volume rather than a status code. |
thresholds.<event source>.hits, .window_minutes | the built-in table | 1 or more | Overrides the built-in table for one event source. An unknown name is an error, not a setting that never applies. |
dedup_hours | 6 | 1 or more | The same address is reported at most once per this window. |
queue_max | 5000 | 1 or more | Queued report files. Above this the oldest are dropped. |
disk_min_mb | 200 | 0 to 1000000 | Free mebibytes required where the state directory lives. Below it no new report is queued, while detection and blocking carry on. 0 switches the boundary off. |
jail_categories.<jail> | built-in table | positive IDs | Extra mapping from a fail2ban jail name to threat category IDs. There are 63 categories, numbered 1 to 63 without gaps. No upper bound is checked on purpose: the server owns the list and gains entries without the agent being updated, and an ID it does not know costs one rejected report while a refusal to start would cost the whole host. Only relevant while fail2ban is still a source. |
ban.enabled | true | true, false | Whether local finds are blocked at all. A manual ban add works either way. |
ban.time_minutes | 30 | 1 or more | The first ban of an address. |
ban.max_time_minutes | 10080 (7 days) | at least ban.time_minutes | The cap of the escalation. Lowering it later leaves bans already in the kernel as they are; the new cap applies to new bans and to bans restored after a reboot or a flush. |
ban.escalate | 4 | 1 or more | The multiplier per repeat inside the memory window. 1 means no escalation at all. |
ban.memory_hours | 24 | 1 or more | How long a ban counts towards the next escalation. |
notify.email | empty | one address | Empty means this host sends no mail. It still records its states and shows them in status. |
notify.cooldown_hours | 24 | 1 or more | At most one mail per state within this window. 1 means at most one mail an hour for a condition that stays open; 0, a mail per sync run, is refused. |
notify.relay | true | true, false | Send through reportedip.com instead of the local MTA. Tried first, available from Professional. |
notify.smtp.host, .from, .user, .password | empty | strings | Only needed on a host with no local MTA and no relay. With Postfix, Exim or msmtp the agent uses sendmail and no password has to live in a file. |
notify.smtp.port | 587 | 1 to 65535 | Submission port of your own SMTP server. |
notify.smtp.starttls | true | true, false | With true the mail is refused rather than sent in the clear when the server does not offer STARTTLS. |
A complete file that parses, with every block a normal host uses:
# /etc/reportedip-agent/config.yaml root:root 0600
api_key: "YOUR_API_KEY"
api_url: https://reportedip.com/wp-json/reportedip/v2
log_level: warn # debug | info | warn | error
log_file: /var/log/reportedip-agent.log
log_max_mb: 10
log_keep: 3
auto_update: true
backend: auto # auto | ipset | nftables
mode: drop # log | drop | off
confidence: 90 # the feed threshold
limit: 50000
lists: # ssh, edge and group are always on
ssh:
ports: [22] # written by install from the real listener
edge: {}
mail: {}
web: {}
min_entries: # below this many addresses a list is not applied
ssh: 1000
mail: 500
web: 500
ftp: 500
edge: 1000
whitelist_file: /etc/reportedip-agent/whitelist.conf
min_hits: 5 # the global detection pair
window_minutes: 10
web_min_hits: 50 # the web source uses its own
web_window_minutes: 120
dedup_hours: 6
queue_max: 5000
disk_min_mb: 200
sources: # detected at install time
- type: sshd
- type: web
glob: /var/www/*/log/access.log
- type: web-error
path: /var/log/nginx/error.log
- type: postfix
path: /var/log/mail.log
- type: dovecot
path: /var/log/mail.log
- type: scan # the kernel log; adds the port scan rule
thresholds: {} # per event source overrides, see below
ban: # block local finds, not only the feed
enabled: true
time_minutes: 30
max_time_minutes: 10080
escalate: 4
memory_hours: 24
notify:
email: "" # empty means no mail
cooldown_hours: 24
relay: true # through reportedip.com, from Professional
Detection limits, per source
A threshold is a pair: how many hits, inside how many minutes. It is the same idea as
maxretry and findtime of a fail2ban jail. Three levels decide which pair
applies to one event source, and the first one with an answer wins.
| Order | Where the pair comes from | Scope |
|---|---|---|
| 1 | An entry under thresholds | Exactly the one event source you name. |
| 2 | The built-in table, nine entries | The nine event sources in the table below marked "built in". |
| 3 | web_min_hits / web_window_minutes | The web source only. |
| 3 | min_hits / window_minutes | Everything else, web-error included. |
| Event source | Threshold | Where it comes from |
|---|---|---|
sshd | 5 in 10 min | min_hits / window_minutes |
web-error | 5 in 10 min | min_hits / window_minutes |
modsec | 5 in 10 min | min_hits / window_minutes |
exim | 5 in 10 min | min_hits / window_minutes |
web | 50 in 120 min | web_min_hits / web_window_minutes |
web-app | 20 in 60 min | built in |
postfix-sasl | 3 in 60 min | built in, measured |
postfix-reject | 5 in 60 min | built in, 5xx rejects only |
postfix-amavis | 3 in 60 min | built in |
dovecot | 10 in 60 min | built in, measured |
ftp | 20 in 60 min | built in |
named | 20 in 30 min | built in |
panel | 5 in 60 min | built in |
scan | 10 in 10 min | built in |
fail2ban, csf, imunify360 | no threshold | That source counted already, so one line is one report. |
The key under thresholds is the event source from that table, not the source type from
sources. Both numbers have to be at least 1. One thresholds block, one
entry per source you want to change:
thresholds:
# A resolver that sees a lot of refused queries: fewer minutes,
# more hits.
named:
hits: 40
window_minutes: 5
# A mail server whose customers keep mistyping passwords: SASL a
# little more forgiving, Dovecot untouched.
postfix-sasl:
hits: 6
window_minutes: 60
# An exposed SSH port, stricter than the global pair.
sshd:
hits: 3
window_minutes: 10
Raising a threshold makes the agent quieter and lets slow attackers through. Lowering it is the direction that costs something: below about three hits in an hour on a mail or web source you will eventually report a customer with a wrong password in their mail client, and a report is public within the community.
One input is not configurable. An address with a ban history needs fewer hits than an unknown one: one previous ban counts a line double, two count it triple, then five and nine. The weight is cut to the threshold of its event source, so a tenth ban does not turn a single line into a report.
Ban limits
Five values in the ban block, each in the unit its key names. They apply only to
addresses the agent found in your own logs. The community list has no ban time at all, it is
replaced wholesale on every feed pass. With the defaults an address that keeps coming back is
banned for 30 minutes, then 2 hours, 8 hours, 32 hours and 128 hours, and after that for the cap
of 7 days.
| Key | Default | What happens at the extremes |
|---|---|---|
enabled | true | false is a host that reports and does not block. |
time_minutes | 30 | Very short values make the escalation the only thing that matters; very long ones mean a false positive sits in the set for a long time. |
max_time_minutes | 10080 | Below time_minutes the cap would shorten the first ban, and the agent refuses such a file. Lowering it later leaves bans that are still in the kernel as they are: today's cap applies to new bans and to those the sync restores after a reboot or a flush. |
escalate | 4 | 1 means every ban lasts time_minutes. |
memory_hours | 24 | A record also stays for three times the length of its last ban when that is longer, which is what makes the cap reachable without the ban file growing from short bans. |
# Careful: a short first ban, a low cap, a long memory.
ban:
enabled: true
time_minutes: 10
max_time_minutes: 1440 # one day
escalate: 3
memory_hours: 72
# Strict: a long first ban and a hard escalation.
ban:
enabled: true
time_minutes: 60
max_time_minutes: 43200 # thirty days
escalate: 6
memory_hours: 168 # a week
Operating limits
These bound what the agent itself may consume. They are not tuning knobs for detection, and the two worth changing are the queue size and the disk floor.
| Key | Default | Unit | Too low | Too high |
|---|---|---|---|---|
queue_max | 5000 | queued report files | Reports are dropped during an outage of the API, oldest first. | A long outage leaves thousands of small files to send afterwards. |
dedup_hours | 6 | hours | The same attacker is reported again and again and eats your daily quota. | An address that attacks again next week is reported late. |
disk_min_mb | 200 | MB free | The agent can help fill /var, which takes the whole host with it. | Reports stop on a host that is perfectly healthy. 0 removes the boundary. |
log_max_mb | 10 | MB | Rotation on every other write, and a history too short to investigate anything. | Maximum 1024, and together with log_keep this is the worst case on disk. |
log_keep | 3 | generations | 0 keeps no rotated file at all. | Maximum 20, and the disk cost is the product of the two. |
notify.cooldown_hours | 24 | hours | At least 1, and 1 means at most one mail an hour for a condition that stays open. 0 would be a mail per sync run and is refused. | A problem that started this morning is mailed tomorrow. |
poll_minutes | 5 | minutes | A command started too often on a busy host. | Maximum 60, and findings arrive that much later. |
limit | 50000 | addresses per list | A truncated list, so the worst addresses are there and the tail is not. | 50000 is the maximum the API serves. |
min_entries | 1000 / 500 | addresses | An implausibly short list goes live and the host is less protected than it thinks. | A genuinely small list is rejected for ever and the set stays at its previous generation. |
Below the disk floor the agent stops writing new reports to the queue, records the condition and
mails it, and keeps detecting and blocking. A full /var is worse than a missed report.
Limits that do not come from the file
Four numbers are not settings on the host. They come from your account and the agent asks for them, which is why an upgrade takes effect without anyone editing a file.
| Limit | Value | Where you see it |
|---|---|---|
| Reports per day, per account | Free 50, Contributor 200, Professional 1,000, Business 5,000 times your volume multiplier, Enterprise unlimited. Reaching it answers HTTP 429 and the agent keeps the report in its queue. | status, section account |
| Reports per day, per server | Automatic: 150 percent of the fair share, never below 50 and never above the account limit. With one server that is the whole account limit, with two 75 percent each, with six a quarter each. You can pin a fixed number instead, or stop one machine reporting. | status, and Agent Servers in your account |
| Feed interval and minimum confidence | Every 15 minutes with confidence from 75 on Professional, Business and Enterprise; hourly with confidence from 90 on Contributor; no feed without a server licence. Your confidence is used only where it is stricter than that floor. | status, per list |
| Mails through the relay of reportedip.com | 500 a month on Professional, 2,500 on Business. Shared with the Hive plugin. Not available on Free and Contributor, where the agent uses the local MTA. | Your account |
status, and for now that is
all it does. Switching it on will be announced beforehand, and the wording of that line changes
with it, so the line always says which of the two is true.
Mail when something is wrong
What is mailed is a state and not an event, so a feed that fails every hour is one message and not twenty-four. The agent tracks eleven states, remembers when each was last mailed so a restart does not start over, and sends an all-clear when one is over.
| State | What it means |
|---|---|
feed | A list failed three sync runs in a row, because it could not be fetched or came back smaller than min_entries. |
api_key | The key was refused. |
chain | The chain or a set is not what the config says. |
disk | Less free space than disk_min_mb. |
source | A configured source resolves to nothing, or is unreadable. |
update | The self update cannot reach or verify a release. |
queue | The queue has reached queue_max and the oldest reports are being dropped. |
license | The server licence of this host is missing or running out. |
rules | Your rule files under rules.d could not be used, and the shipped rules run alone. |
rule | A guard switched one of your file rules off; rules status names it. |
reputation | The address this host reports from is itself listed in the community database. A warning, not an error: it needs confidence 75, the lowest any feed serves, so it fires when other hosts would really block this address; below that the number only shows in status and in the state file. In status it carries the confidence, the number of reports and the delisting page; the mail names only the state, because this text contains an address and never goes into a mail. It clears with the next sync after delisting. |
The mail carries the hostname, the version, the name of the state, a short sentence with numbers and
the command to look with. Never a log line, never an IP address, never a path. At most one mail per
state within notify.cooldown_hours. Stopping the agent sends exactly one mail naming
what this host stops doing; a reboot and a restart send none, which is what keeps the self update
quiet.
install writes the notify block, so the address is one flag at setup time
rather than an edit afterwards. An existing configuration is never changed, not even by an update.
# Arm the mail while the host is being set up
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install --notify-email ops@example.org
# The same through the install script, so one line arms a whole
# rollout
curl -fsSL https://reportedip.com/agent/install.sh \
| REPORTEDIP_ACCEPT_TERMS=1 REPORTEDIP_KEY=<YOUR-KEY> REPORTEDIP_NOTIFY_EMAIL=ops@example.org sh
Three delivery paths, in the order the agent tries them. Without notify.email none of
them runs.
| Path | When it is used | What it needs |
|---|---|---|
| The relay of reportedip.com | notify.relay: true, the default. Tried first, because a local MTA that accepts a mail and never delivers it is the one failure the agent cannot see. | Professional or above, and a real reachable mailbox as the recipient. A plan without the relay answers 403 once, which is remembered for a day. |
| The local MTA | With relay: false, or after the relay was refused. | Postfix, Exim or msmtp. The agent calls sendmail -t -i and no password lives in a file. A local mailbox such as root@localhost works here and only here. |
| Your own SMTP server | With relay: false and no local MTA. | The notify.smtp block. STARTTLS stays on, so the mail is refused rather than sent in the clear when the server does not offer it. |
# The default: out through the relay of reportedip.com, so this
# host needs no mail server of its own.
notify:
email: "ops@example.org"
cooldown_hours: 24
relay: true
# Local delivery only, through postfix, exim or msmtp.
notify:
email: "ops@example.org"
cooldown_hours: 24
relay: false
# No local MTA and no relay: your own SMTP.
notify:
email: "ops@example.org"
cooldown_hours: 12
relay: false
smtp:
host: mail.example.org
port: 587
from: agent@example.org
user: agent@example.org
password: "..."
starttls: true
Run reportedip-agent doctor after configuring this. Its mail section says
whether a mail could really leave the host, which is a different question from whether the
configuration parses. A missing recipient is named on every run and is not counted as a fault.
When a change takes effect
The agent reads its configuration at start, does not watch the file and has no reload signal. Which command you need depends on which part you touched.
| What you changed | What makes it take effect |
|---|---|
mode, lists, min_entries, confidence, limit, backend | reportedip-agent sync. The sync run builds the chain and the rules, so a new mode is live at the end of it. |
thresholds, min_hits, window_minutes, web_min_hits, web_window_minutes, dedup_hours, ban, notify, log_* | systemctl restart reportedip-agent.service. These are read by the watch daemon. |
A source added to sources | systemctl restart reportedip-agent.service. |
The scan source added | Both: the restart for the source, and reportedip-agent sync for the firewall rule it reads, which only the sync run builds. |
A source removed from sources | A stop and a start, not a restart. On a live host a systemctl restart was not enough and the daemon kept tailing the removed file: systemctl stop reportedip-agent.service && systemctl start reportedip-agent.service. |
whitelist_file content | Nothing, when you use reportedip-agent whitelist add: that writes the file and the kernel set in one step and takes effect at once. Editing the file by hand needs a sync. |
api_key, api_url, auto_update, update_url | Nothing for the next sync, which is a oneshot and reads the file fresh. Restart the watch service so the reporting path picks the key up too. |
# The safe sequence after any edit
reportedip-agent status >/dev/null; [ $? -ne 2 ] && echo "config parses"
reportedip-agent sync
systemctl restart reportedip-agent.service
systemctl status reportedip-agent.service --no-pager
Last updated: · Maintained by the ReportedIP team