Skip to main contentSkip to footer

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.

PathOwner, modeWhat it holds
/etc/reportedip-agent/config.yamlroot:root, 0600Every 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.confroot:root, 0600Never 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, 0700State: 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.logroot:root, 0640The agent's own log. It rotates the file itself, so logrotate is neither needed nor in the way.
/usr/local/bin/reportedip-agentroot:root, 0755The binary. One file, statically linked.
An unknown key stops the agent with exit 2. The parser checks field names, so a misspelled key is an error with a line number instead of a value that silently never applies. The table below is the complete list, and a key that is not in it does not exist. Two that get invented often: there is no 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:.

KeyDefaultAcceptedWhat it does
api_keynoneyour keyThe only key without a default. REPLACE_ME counts as missing.
api_urlhttps://reportedip.com/wp-json/reportedip/v2absolute httpsThe REST base. Plain http is accepted on the loopback only, for a local proxy.
update_urlfrom api_url, path /agentabsolute httpsWhere agent builds come from. Set it for a mirror.
auto_updatetruetrue, falseInstall 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_levelwarndebug, info, warn, errorNothing 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.logpath, or emptyThe agent's own log, in addition to stderr, so journald keeps everything it has today. Empty switches the file off.
log_max_mb101 to 1024Size at which the agent rotates its own file.
log_keep30 to 20Rotated generations kept. With the defaults the worst case on disk is 40 MB.
backendautoauto, ipset, nftablesWhich packet filter is written. A later change needs sync --migrate-backend.
modedroplog, drop, offWhat the rules do. Applies to the feed lists and to the local bans.
confidence901 to 100The 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.
limit500001 to 50000Maximum addresses per list request.
listsssh, edgessh, mail, web, ftp, edge, groupWhich 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.portsfrom the real listener1 to 65535 eachThe 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 10001 or moreThe 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.confpathYour never-block, never-report list.
sourcesone sshd entrylist of entriesThe 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[].typenoneone of the fourteenAn unknown type is an error that lists the valid ones. web-app is not one of them.
sources[].pathnoneone fileSet 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[].globnoneat most 5 wildcard segmentsA pattern over many files, rescanned every ten minutes.
sources[].excludenoneshell patternsDrops 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_minutes51 to 60Only for imunify360, which polls a command instead of reading a file. On any other type it is an error.
min_hits, window_minutes5, 101 or moreThe global pair for every event source without its own entry in the built-in table.
web_min_hits, web_window_minutes50, 1201 or moreThe same pair for the web source alone, which needs volume rather than a status code.
thresholds.<event source>.hits, .window_minutesthe built-in table1 or moreOverrides the built-in table for one event source. An unknown name is an error, not a setting that never applies.
dedup_hours61 or moreThe same address is reported at most once per this window.
queue_max50001 or moreQueued report files. Above this the oldest are dropped.
disk_min_mb2000 to 1000000Free 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 tablepositive IDsExtra 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.enabledtruetrue, falseWhether local finds are blocked at all. A manual ban add works either way.
ban.time_minutes301 or moreThe first ban of an address.
ban.max_time_minutes10080 (7 days)at least ban.time_minutesThe 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.escalate41 or moreThe multiplier per repeat inside the memory window. 1 means no escalation at all.
ban.memory_hours241 or moreHow long a ban counts towards the next escalation.
notify.emailemptyone addressEmpty means this host sends no mail. It still records its states and shows them in status.
notify.cooldown_hours241 or moreAt 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.relaytruetrue, falseSend through reportedip.com instead of the local MTA. Tried first, available from Professional.
notify.smtp.host, .from, .user, .passwordemptystringsOnly 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.port5871 to 65535Submission port of your own SMTP server.
notify.smtp.starttlstruetrue, falseWith 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:

yaml
# /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.

OrderWhere the pair comes fromScope
1An entry under thresholdsExactly the one event source you name.
2The built-in table, nine entriesThe nine event sources in the table below marked "built in".
3web_min_hits / web_window_minutesThe web source only.
3min_hits / window_minutesEverything else, web-error included.
Event sourceThresholdWhere it comes from
sshd5 in 10 minmin_hits / window_minutes
web-error5 in 10 minmin_hits / window_minutes
modsec5 in 10 minmin_hits / window_minutes
exim5 in 10 minmin_hits / window_minutes
web50 in 120 minweb_min_hits / web_window_minutes
web-app20 in 60 minbuilt in
postfix-sasl3 in 60 minbuilt in, measured
postfix-reject5 in 60 minbuilt in, 5xx rejects only
postfix-amavis3 in 60 minbuilt in
dovecot10 in 60 minbuilt in, measured
ftp20 in 60 minbuilt in
named20 in 30 minbuilt in
panel5 in 60 minbuilt in
scan10 in 10 minbuilt in
fail2ban, csf, imunify360no thresholdThat 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:

yaml
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.

KeyDefaultWhat happens at the extremes
enabledtruefalse is a host that reports and does not block.
time_minutes30Very 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_minutes10080Below 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.
escalate41 means every ban lasts time_minutes.
memory_hours24A 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.
yaml
# 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.

KeyDefaultUnitToo lowToo high
queue_max5000queued report filesReports are dropped during an outage of the API, oldest first.A long outage leaves thousands of small files to send afterwards.
dedup_hours6hoursThe same attacker is reported again and again and eats your daily quota.An address that attacks again next week is reported late.
disk_min_mb200MB freeThe 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_mb10MBRotation 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_keep3generations0 keeps no rotated file at all.Maximum 20, and the disk cost is the product of the two.
notify.cooldown_hours24hoursAt 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_minutes5minutesA command started too often on a busy host.Maximum 60, and findings arrive that much later.
limit50000addresses per listA truncated list, so the worst addresses are there and the tail is not.50000 is the maximum the API serves.
min_entries1000 / 500addressesAn 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.

LimitValueWhere you see it
Reports per day, per accountFree 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 serverAutomatic: 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 confidenceEvery 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.com500 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
The per-server budget is counted and not enforced. No report is refused because of it today. It is measured, shown in the portal and in 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.

StateWhat it means
feedA list failed three sync runs in a row, because it could not be fetched or came back smaller than min_entries.
api_keyThe key was refused.
chainThe chain or a set is not what the config says.
diskLess free space than disk_min_mb.
sourceA configured source resolves to nothing, or is unreadable.
updateThe self update cannot reach or verify a release.
queueThe queue has reached queue_max and the oldest reports are being dropped.
licenseThe server licence of this host is missing or running out.
rulesYour rule files under rules.d could not be used, and the shipped rules run alone.
ruleA guard switched one of your file rules off; rules status names it.
reputationThe 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.

bash
# 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.

PathWhen it is usedWhat it needs
The relay of reportedip.comnotify.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 MTAWith 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 serverWith 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.
yaml
# 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 changedWhat makes it take effect
mode, lists, min_entries, confidence, limit, backendreportedip-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 sourcessystemctl restart reportedip-agent.service.
The scan source addedBoth: 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 sourcesA 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 contentNothing, 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_urlNothing 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.
bash
# 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

Security Focused
GDPR Compliant
Made in Germany
Back to Docs