Operational/how-to references updated to the role entry playbooks after the ADR-0001 migration. Historical incident narrative (dated callouts, commit refs) preserved. - clamav-fleet-deployment: override + re-run -> clamav.yml; role note - ssh-hardening-ansible-fleet: note this is now the ssh_hardening role - vps-migration-baseline-checklist: table -> clamav.yml / ssh_hardening.yml - ssh-socket-tailscale-race-condition: Affected Hosts + Prevention + References -> tailscale role tasks (network_wait/ssh_only_ubuntu/ssh_only_fedora) - freshclam-logwatch-false-no-updates: codify refs -> clamav role
80 lines
4.7 KiB
Markdown
80 lines
4.7 KiB
Markdown
---
|
|
title: "Logwatch Falsely Reports 'No freshclam updates' in ClamAV Daemon Mode"
|
|
domain: troubleshooting
|
|
category: security
|
|
tags: [clamav, freshclam, logwatch, false-positive, fedora, ubuntu, ansible]
|
|
status: published
|
|
created: 2026-06-06
|
|
updated: 2026-06-06
|
|
---
|
|
# Logwatch Falsely Reports "No freshclam updates" in ClamAV Daemon Mode
|
|
|
|
Logwatch's daily `clam-update` section emails:
|
|
|
|
> No updates detected in the log for the freshclam daemon (the ClamAV update process). If the freshclam daemon is not running, you may need to restart it.
|
|
|
|
…even though freshclam **is** running and signatures **are** current. It's a parser quirk specific to running freshclam as a daemon. Don't act on the "restart it" suggestion — first confirm whether signatures are actually stale.
|
|
|
|
> Seen on **tttpod** (2026-06-06). All four freshclam hosts (majorlinux, majortoot-hetzner, teelia, tttpod) hit this on quiet days.
|
|
|
|
## First: is it real or false?
|
|
|
|
```bash
|
|
systemctl is-active clamav-freshclam # active?
|
|
ls -l /var/lib/clamav/daily.c[lv]d # mtime today/yesterday?
|
|
grep 'updated' /var/log/clamav/freshclam.log | tail # real download events
|
|
```
|
|
|
|
- **Fresh `daily.cld` + active service → false positive** (this page).
|
|
- **`daily.cld` weeks old / service disabled → real.** Re-enable freshclam and update (see Related). A daemonless box still needs freshclam enabled — `clamav_use_daemon: false` only disables the *scanner* daemon, not the updater.
|
|
|
|
## Why It False-Alarms
|
|
|
|
logwatch's `clam-update` script (`/usr/share/logwatch/scripts/services/clam-update`) decides "updated" by counting **`ClamAV update process started`** lines (`$UpdatedNum`) within its range (`Range = yesterday`). It does **not** count the actual `daily.cld updated (version: …)` download lines.
|
|
|
|
freshclam emits "update process started" **only when the daemon (re)starts** — not on its periodic in-daemon checks (`Checks 24`, `ExecStart=/usr/bin/freshclam -d`). So on any day the box doesn't reboot or restart freshclam, yesterday's log has zero "started" lines → `$UpdatedNum == 0` → the warning fires, regardless of whether signatures downloaded. (Conversely, on a day you *do* reboot, the warning won't fire.) The script was written for the old cron-driven freshclam, which started a fresh process each run.
|
|
|
|
## Fix
|
|
|
|
Silence just that one message — real `ERROR` / `WARNING` / outdated alerts still report:
|
|
|
|
```bash
|
|
# /etc/logwatch/conf/services/clam-update.conf
|
|
$ignore_no_updates = 1
|
|
```
|
|
|
|
No service restart needed; logwatch picks it up on its next daily run. (The variable is read as `$ENV{'ignore_no_updates'}` by the script — note: **not** prefixed `clam_update_`, despite what the script's own self-help text suggests.)
|
|
|
|
## Codify (Ansible)
|
|
|
|
Deploy the drop-in wherever freshclam runs in daemon mode. On the fleet it's a task in the `clamav` role (`roles/clamav/tasks/install.yml`, group `clamav`), right after freshclam is enabled — originally added in MajorAnsible commit `cb27c93`:
|
|
|
|
```yaml
|
|
- name: Suppress logwatch clam-update false "no updates" alert (daemon-mode freshclam)
|
|
ansible.builtin.copy:
|
|
dest: /etc/logwatch/conf/services/clam-update.conf
|
|
mode: '0644'
|
|
content: |
|
|
$ignore_no_updates = 1
|
|
tags: [logwatch]
|
|
```
|
|
|
|
## Key Notes
|
|
|
|
- **Confirm freshness before suppressing.** If signatures really are stale (freshclam off / no update timer), suppressing hides a genuine security gap. On a daemonless host that disabled freshclam, the warning is *true*.
|
|
- The script's built-in options B/C (about syslog format) don't apply when freshclam logs to its own file (`LogSyslog false`); `$ignore_no_updates` is the right lever.
|
|
- **Don't alert with `mail`.** The `mail`/`mailx` CLI is absent on most fleet hosts (only Postfix's `/usr/sbin/sendmail` is guaranteed). A health script that ends in `mail -s … root` silently fails to send. Pipe a headered message to `/usr/sbin/sendmail -t` addressed to `admin_email` directly (don't rely on an `/etc/aliases` `root` rewrite either).
|
|
|
|
## Proactive monitoring (don't rely on logwatch for "is it updating?")
|
|
|
|
Since logwatch's heuristic is suppressed, a **direct daily watchdog** is what actually catches a dead freshclam. The `clamav` role deploys `/etc/cron.daily/clamav-freshness` (originally MajorAnsible `9d1a1a9`) to every `clamav`-group host: it emails the admin (via `sendmail`) if `clamav-freshclam` is inactive **or** `daily.cld` is older than `clamav_staleness_threshold_days` (default 3) — and stays silent otherwise. Test without emailing:
|
|
|
|
```bash
|
|
CLAMAV_STALE_DAYS=0 /etc/cron.daily/clamav-freshness # forces the stale branch
|
|
```
|
|
|
|
This is what would have caught dcaprod's 20-day drift immediately instead of it surfacing by accident.
|
|
|
|
## Related
|
|
|
|
- [ClamAV CPU Spike: Safe Scheduling with nice/ionice](clamscan-cpu-spike-nice-ionice.md)
|