A monitoring audit reviews your server's brute-force defences, log management and login tracking to confirm that security events are detected, recorded, and not silently lost to disk pressure. This page documents the 3 checks in the CtrlOps Logging & Monitoring audit, in the order the audit runs them.
Brute-force protection, log rotation and failed-login tracking are the three things that decide whether a security incident leaves a forensic trail or a mystery. Every threshold below is transcribed from the audit script itself, not from general advice. Where the script passes a value, this page says it passes; where the script warns rather than fails, this page says so and explains why the distinction matters.
Key takeaways
Three checks cover the detection and recording layer every other audit depends on. Without logging you cannot prove a fix worked. Without brute-force protection your auth log fills with noise that hides the real attack. Each check takes two to three minutes by hand, which is fine for one server and tedious at scale.
- Nothing here is rated HIGH. These are defensive and informational rather than direct attack vectors. A server with no Fail2Ban and no working rotation will still eventually fill its disk, stop logging, and leave you with no record of what happened.
- All three are marked as needing root: listing Fail2Ban jails, scanning
/var/logfor oversized files, and reading the auth log. Two of them degrade to a partial answer without it rather than skipping outright. - The whole audit takes about 9 seconds when CtrlOps runs it over your existing SSH connection, against roughly 8 minutes by hand.
- A 100MB auth log is itself the finding. Either brute-force protection is missing, or rotation is broken, or both, and the audit's other two checks tell you which.
| # | Check | Severity | What it catches | Root needed |
|---|---|---|---|---|
| 1 | Brute-force protection | MEDIUM | No Fail2Ban, CrowdSec or sshguard running | Yes |
| 2 | Log rotation | MEDIUM | Missing logrotate, or log files over 100MB | Yes |
| 3 | Failed SSH logins | LOW | Failed SSH login volume in the last 24 hours | Yes |
/var/log/auth.log (Debian, Ubuntu) or /var/log/secure (RHEL, CentOS, Rocky, Alma), falling back to journalctl -u ssh -u sshd if neither file is readable. Both unit names are queried because distributions disagree on which one sshd runs under. Every branch is windowed to today's date: an earlier version counted the whole rotated log against the same threshold the journal branch applied to 24 hours, so a long-lived auth.log warned on ordinary noise while a freshly rotated one never warned at all.Check 1: Is brute-force protection active?
A server on the public internet takes thousands of automated SSH login attempts a day. Without brute-force protection every one of them completes, fills the auth log, and creates enough noise to hide a real targeted attempt inside it. Fail2Ban, CrowdSec and sshguard all solve the same problem: after a set number of failures the source IP is blocked for a set duration.
The audit looks for Fail2Ban first, since it is the most widely deployed, then falls back to CrowdSec and sshguard. For Fail2Ban it does two things rather than one: confirms the service is running, and then reads the actual jail list. Those are separate questions, and the gap between them is where this check earns its place.
How to check manually
# Fail2Ban: is it running, and what is it protecting?
systemctl is-active fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
# Alternatives
pgrep -x crowdsec
pgrep -x sshguard| Result | When | What it means |
|---|---|---|
| PASS | Fail2Ban is running with at least one active jail, or CrowdSec or sshguard is running | Brute-force attempts are being blocked. The jail names are listed in the result. |
| WARN | No brute-force protection is installed at all, or Fail2Ban is running but returns an empty jail list | Either install Fail2Ban, or fix the jail configuration. A running service with no jails blocks nothing. |
| FAIL | Fail2Ban is installed but the service is not running | It crashed or was never enabled. Run systemctl enable --now fail2ban. |
| SKIP | Fail2Ban is running but root was unavailable to list the jails | The service state is known, the jail list is not. Re-run with sudo for the full answer. |
The two WARN cases are worth separating in your head. "Nothing installed" is a gap you know about. "Running with no jails" is the worse one, because systemctl status fail2ban shows a healthy green service while nothing is actually being protected, usually because of a socket problem or a jail.local that never enabled [sshd]. That is why the check reads the jail list rather than trusting the service state.
Note also that a CrowdSec or sshguard pass needs no root at all: the check finds those by process name, so an unprivileged run still returns a real answer on those hosts.
How to fix it
# Install (Debian/Ubuntu)
sudo apt install fail2ban
# Enable and start
sudo systemctl enable --now fail2ban
# Confirm the SSH jail actually exists
sudo fail2ban-client status sshdConfigure a local jail rather than editing the packaged one. Create /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = ssh
maxretry = 5
bantime = 3600jail.conf directly. Package updates overwrite it and silently reset every setting you changed, which produces exactly the "service running, no jails" state above. jail.local survives upgrades. And be clear about what this buys you: Fail2Ban does not make SSH more secure, it reduces noise so real attacks become visible in the log, and it raises the cost of brute-forcing from near-zero to impractical. Key-only authentication is the control that actually closes the door.Check 2: Is log rotation working?
A log file past 100MB is either not being rotated or the rotation is failing quietly. A 500MB auth log fills a small VPS disk, and when the disk fills the server stops writing logs entirely: the moment you most need forensic data is the moment it stops being recorded.
The audit asks three questions in order. Is logrotate installed? Are there files in /var/log over 100MB? And are there rotated archives (.1, .gz) proving rotation has actually run at least once? Only a yes to all three passes.
How to check manually
# Is logrotate installed?
which logrotate
# Any log file over 100MB?
sudo find /var/log -xdev -type f -size +100M
# Has rotation ever actually run?
ls /var/log/*.1 /var/log/*.gz 2>/dev/null | head -5| Result | When | What it means |
|---|---|---|
| PASS | logrotate is installed, no file exceeds 100MB, and rotated archives exist | Rotation is running and keeping up. |
| WARN | logrotate is missing, or a file exceeds 100MB, or no rotated archive exists anywhere | The specific reason is reported, and the oversized file paths are listed. Each of the three cases has a different fix. |
This is the only check on the page with just two outcomes: there is no FAIL and no SKIP. Nothing here is an exposure on its own, so nothing rises to a failure, and the oversized-file scan falls back to an unprivileged find when root is unavailable, so it always returns something rather than skipping.
The third WARN case is the one people miss. logrotate installed, no oversized files yet, and no archives anywhere means rotation has never run at all: the package is there and the timer that invokes it is not. You are looking at a disk that fills in a month rather than one that is already full.
How to fix it
# Install it (Debian/Ubuntu)
sudo apt install logrotate
# Dry run: shows what would happen, changes nothing
sudo logrotate -d /etc/logrotate.conf
# Force a rotation to prove it works
sudo logrotate -f /etc/logrotate.conf
# Confirm it recorded the run
cat /var/lib/logrotate/statussystemctl is-active logrotate.timer, or check that /etc/cron.daily/logrotate exists and is executable.Check 3: How many SSH login attempts failed in the last 24 hours?
This check is not about whether failed logins exist. On a public-facing server they always will. It is about the count. A handful is ordinary background noise. Over 1,000 in a day means an active campaign is running against this host, and the thing to confirm is that Fail2Ban is banning those source IPs rather than merely writing them down.
The audit reads the auth log, or the journal if no log file is readable, filters to today's date, and counts lines matching Failed password.
How to check manually
# Debian / Ubuntu
sudo grep "$(date '+%b %e')" /var/log/auth.log | grep -c 'Failed password'
# RHEL / CentOS / Rocky / Alma
sudo grep "$(date '+%b %e')" /var/log/secure | grep -c 'Failed password'
# systemd journal fallback, both unit names
sudo journalctl -u ssh -u sshd --since '24 hours ago' | grep -c 'Failed password'| Result | When | What it means |
|---|---|---|
| PASS | 1,000 or fewer failed attempts inside the current day window | Ordinary background noise. The exact count is reported, which is the number worth tracking over time. |
| WARN | More than 1,000 failed attempts inside the current day window | An active brute-force campaign. Confirm Fail2Ban is banning the source IPs and not just logging them. |
| SKIP | No auth log was readable and root was unavailable for the journal | Nothing was counted, so nothing is claimed. Re-run with sudo. |
Root is the fallback here rather than the requirement. The check tries an unprivileged read first, so on a host where auth.log happens to be world-readable it returns a real count without sudo. That is convenient and it is also a finding in its own right: a world-readable auth log is exactly what check 4 of the file system audit warns about, because the same file that lets this audit count attempts lets any local account read every username that has ever tried to log in.
How to fix it
If the count is high and Fail2Ban is running:
# Is it actually banning, or only watching?
sudo fail2ban-client status sshd
# What is currently banned?
sudo fail2ban-client get sshd banned
# Tighten the jail, in /etc/fail2ban/jail.local
maxretry = 3
bantime = 86400If Fail2Ban is not running at all, that is check 1's finding, not this one's. If it is running but banning nothing while the count climbs, the jail is not matching: check that its port value matches the port sshd actually listens on, and that its filter matches your log format. A jail configured for port 22 on a host running SSH on 2222 matches nothing at all and reports itself as perfectly healthy while doing so.
What this audit does not cover
Being explicit about scope is part of the point of publishing thresholds. This audit reads logging infrastructure and counts events. It does not analyse log content for attack patterns.
- No log content analysis. The audit counts failed SSH logins. It does not correlate source IPs, detect distributed attacks, or tell a targeted attempt apart from a spray. That is a SIEM's job.
- No application-level logging. Web server access and error logs, application logs and database query logs are outside this audit. It covers system-level logging infrastructure only; the web server logging audit covers nginx and Apache separately.
- No alerting verification. Nothing here checks whether a log event reaches a human. A server that rotates logs perfectly and notifies nobody still leaves you discovering incidents by accident.
- No log forwarding or retention. Remote syslog, centralised logging (ELK, Loki, Datadog) and retention policy are not evaluated. If logs live only on the host that was compromised, whoever compromised it can delete them.
- No firewall-level rate limiting. Fail2Ban is one way to slow brute force; connection rate limits in nftables or a cloud WAF are another, and this check does not see them. A host protected at the network edge can WARN here and still be fine.
The manual audit problem at scale
Running these 3 checks on one server takes about 8 minutes. It is the shortest audit in the set, and it is also the one where a single run tells you the least, because every number it returns only means something next to the last one.
The failed-login count is the clearest example. Is 247 failed attempts in 24 hours normal for this server? You have no idea unless you checked yesterday, and the day before, and the day before that. Without a baseline every number is either "probably fine" or "should I worry", and neither answer is based on anything.
Fail2Ban status has the same shape. The service is running and the sshd jail shows 3 bans. Is that good? It depends entirely on how many attempts came in, which is check 3, from the same window, on the same host. Read on its own, a ban count is not evidence of anything.
Manual auditing gives you snapshots. What this category actually needs is a trend line: the same checks, on the same servers, at regular intervals, stored somewhere you can compare. That is the same server management drift problem as everywhere else, except here the drift is in the numbers rather than the configuration.
How CtrlOps runs all 3 checks in one click
Instead of running these commands on every server by hand, CtrlOps runs the whole Logging & Monitoring audit over the SSH connection you already have open. Nothing is installed on the server: no agent, no daemon, no package, no new credentials. The checks are read-only POSIX shell that executes, streams its results back, and leaves nothing behind.
Watch it run
The recording below walks through the SSH & Access audit rather than this one, but the flow is identical for every audit in the catalog: pick it, watch the checks stream in, read the score, send the failures to the AI Terminal. The written steps that follow cover the same ground if you would rather read than watch.
Step 1: Open the Audit tab
Connect to your server in CtrlOps and open the Audit tab in the left sidebar.
Step 2: Select Logging & Monitoring
Choose the Logging & Monitoring audit from the catalog. All 3 checks are listed with their descriptions, severity levels and the estimated run time (about 9 seconds in total). Toggle off any check you want to skip.
Step 3: Run it
The audit runs over your existing SSH session. Results stream in as they complete, so you watch each check pass, warn or fail rather than waiting on a final report.
Step 4: Read the report
You get a summary containing:
- A hardening score out of 100
- A severity breakdown: how many HIGH, MEDIUM and LOW findings
- Pass, warning, failed and skipped counts
- A findings table you can sort and filter
Step 5: Fix what failed
Select the failed findings and send them to the AI Terminal. Each proposed fix command appears with an explanation of what it changes. Nothing runs until you review and approve it.
Step 6: Re-run and compare
Run the same audit again after applying the fixes and watch the hardening score move. This is the audit where that history matters most: the failed-login count is only meaningful against the last one, and a stored report is what turns a number into a trend.
| Task | By hand, 8 servers | CtrlOps, 8 servers |
|---|---|---|
| Run all 3 checks | About 65 minutes | About 90 seconds |
| Compare against last week's counts | Only if you wrote them down | Automatic |
| Get a scored report | Not available | Automatic |
| Export a PDF for a client | Not available | One click |
| Fix what failed | Copy-paste from your notes | AI-generated commands behind an approval gate |
| Re-audit after fixing | Another 65 minutes | Another 90 seconds |
Conclusion
Logging and monitoring are what make every other audit provable. Brute-force protection keeps the auth log readable, rotation keeps the disk from filling and the logging from stopping, and the failed-login count tells you whether the first two are keeping up. None of the three is a HIGH severity finding on its own, and all three are what you will wish you had configured when something goes wrong.
Checking them by hand across a fleet burns hours and produces snapshots when what you need is a trend. The CtrlOps Security Audit runs all three in about 9 seconds per server, agentlessly, and stores each report so this week's numbers sit next to last week's.
For the layers around this one, the neighbouring checklists cover SSH and access control, the firewall and network perimeter, file system permissions and the application and database layer.
Frequently asked questions
A monitoring audit checks the detection and recording infrastructure that every other security audit depends on. It covers brute-force protection (Fail2Ban, CrowdSec, sshguard), system log rotation health, and failed SSH login tracking. The goal is to confirm that security events are being detected, blocked where appropriate, and written to logs that will not fill the disk and stop recording.
Run sudo fail2ban-client status to list the active jails, then sudo fail2ban-client status sshd for the SSH jail's ban counts. Checking the service is running is not enough on its own: a running Fail2Ban with an empty jail list protects nothing while reporting itself healthy. Cross-reference the ban count with the failed-login count from the same window. High attempts and zero bans means the jail is not matching your log format or your SSH port.
All three ban source IPs after repeated failed logins. Fail2Ban is the most widely deployed, reads log files directly and is highly configurable. CrowdSec adds a community blocklist, so you benefit from attacks seen on other installations. sshguard is lighter and focused specifically on SSH. This audit treats any of the three as sufficient, and finds CrowdSec and sshguard by process name, which means those two produce a real answer even without root.
The audit flags any file in /var/log over 100MB. On a typical VPS, well-rotated logs stay under 10MB each. A 100MB auth log usually means one of two things: logrotate is broken for that specific file, or brute-force volume is outrunning rotation. Fix the brute-force protection first, because it is generating the volume, then fix the rotation config.
On a public-facing server with SSH on port 22, yes. That level of automated background scanning is routine. The audit only warns above 1,000, because that is where the volume stops looking like generic scanning and starts looking like a campaign aimed at this host. The absolute number matters less than the trend: a jump from 200 to 2,000 in a day is more informative than a steady 800.
Because none of the three is an exposure by itself. A missing Fail2Ban does not let anyone in; it lets them keep trying cheaply. Broken rotation does not grant access; it eventually fills the disk and stops the logging. These are the controls that make an incident survivable and provable rather than the ones that prevent it, which is why the audit rates them MEDIUM and LOW and the SSH, firewall and file system audits carry the HIGH findings.
No. CtrlOps audits whether the logging plumbing is healthy: protection is running, logs are rotating, failed-login counts are in the expected range. A SIEM such as Splunk, Elastic SIEM or Wazuh analyses the log content, correlates events across sources and alerts on patterns. This audit checks the plumbing; a SIEM reads what flows through it. Neither substitutes for the other.
Install it with sudo apt install logrotate, though it is usually present already. Per-service rules live in /etc/logrotate.d/. Test your config with sudo logrotate -d /etc/logrotate.conf for a dry run, then force one with sudo logrotate -f /etc/logrotate.conf and check /var/lib/logrotate/status for the recorded timestamps. Finally verify the timer with systemctl is-active logrotate.timer, because an installed logrotate with a disabled timer never runs.
No. The audit only reads whether brute-force protection is present and running, using the status of tools already on the host. Nothing is installed and nothing is modified. If none is found the audit warns, and the AI Terminal can then propose the install command, but nothing executes until you review and approve it.
If all your logs ship to a remote service and nothing meaningful stays on disk, the local rotation check tells you little. If brute-force protection is handled at the network edge by Cloudflare, an AWS WAF or a cloud firewall's rate limiting, check 1 will warn on a host that is genuinely protected. This audit is for servers you administer directly over SSH, where the logging and banning happen on the host itself.
Yes. All three checks work on any Linux host reachable over SSH: EC2, DigitalOcean Droplets, Linode, Vultr, Hetzner, bare metal or any other VPS. Check 3 in particular adapts to the distribution, reading /var/log/auth.log on Debian and Ubuntu images, /var/log/secure on RHEL-family images, and falling back to the systemd journal on minimal images that ship neither.