Installation and First Run
Logwatch is packaged in every major distribution's default repository. On RHEL 9 / Rocky 9 / AlmaLinux 9:
``` dnf install logwatch ```
On Debian 12 / Ubuntu 24.04:
``` apt install logwatch ```
The package drops a daily cron job at /etc/cron.daily/0logwatch and installs its library under /usr/share/logwatch/. Config lives in /etc/logwatch/conf/ which starts empty - Logwatch follows a layered config approach where your overrides in /etc/logwatch/conf/ take precedence over defaults in /usr/share/logwatch/default.conf.
Run it manually before touching any config to see the baseline output:
``` logwatch --output stdout --format text --range today ```
On our test server running Rocky 9 with roughly 2,000 SSH connection attempts per day, that produced 347 lines of output in about 4 seconds. The `--range` flag accepts `today`, `yesterday`, `all`, or an explicit date range like `--range 'between -7 days and -1 days'`.
logwatch --output stdout --format text --range today --detail high
Core Configuration File
Create /etc/logwatch/conf/logwatch.conf to override defaults. Logwatch merges this with /usr/share/logwatch/default.conf at runtime, so you only need to specify what you're changing.
Here is a production-grade baseline we use on internet-facing servers:
``` LogDir = /var/log TmpDir = /var/cache/logwatch Output = mail Format = text MailTo = ops@example.com MailFrom = logwatch@yourhostname.example.com Detail = Med Service = All Service = -zz-network Service = -zz-sys Range = yesterday MaxLineLength = 998 ```
The `Service = -zz-network` lines suppress the low-value network summary sections that Logwatch generates by default. These sections report byte counts that duplicate what netstat or ss already show you in context.
`Detail` accepts Low, Med, High, or a numeric 0-10. Med is roughly equivalent to 5. On a busy mail server, High generates reports that take 10+ minutes to read. We set Detail = Med globally and override specific services to High individually.
MailFrom matters for deliverability. If your server sends from a hostname that doesn't match your PTR record, reports will land in spam. Set MailFrom to an address on the same domain as your MX PTR.
cat /etc/logwatch/conf/logwatch.conf
Per-Service Configuration
Service-level config files live in /etc/logwatch/conf/services/. Create a file named after the service (matching the service name in /usr/share/logwatch/scripts/services/) to override that service's defaults.
To crank SSH reporting to maximum detail while keeping everything else at Med:
``` echo '$Detail = 10' > /etc/logwatch/conf/services/sshd.conf ```
To completely disable a noisy service:
``` echo '$ignore = yes' > /etc/logwatch/conf/services/postfix.conf ```
Or suppress it only from the nightly email but keep it available for manual runs:
``` echo '$OnlyService = 0' > /etc/logwatch/conf/services/postfix.conf ```
Available built-in service scripts are listed at:
``` ls /usr/share/logwatch/scripts/services/ ```
On Rocky 9 with logwatch 7.8, we counted 73 service scripts including entries for apache, nginx, named, puppet, dovecot, mysql, and postgresql. If your service isn't listed, you write a custom filter - covered in the next section.
ls /usr/share/logwatch/scripts/services/ | sort
Writing a Custom Service Filter
A Logwatch service filter is a Perl script that reads pre-filtered log lines from STDIN and prints a summary to STDOUT. The log filtering (pulling only relevant lines) is defined separately in a logfile config.
Let's say you run a custom application that logs to /var/log/myapp/app.log with lines like:
``` 2026-08-24T03:14:21Z ERROR Failed payment for user 9182, code=402 2026-08-24T03:14:55Z WARN Retry attempt 2 for job 77341 2026-08-24T03:15:02Z INFO Job 77341 completed ```
Step 1 - create the logfile definition:
``` mkdir -p /etc/logwatch/conf/logfiles cat > /etc/logwatch/conf/logfiles/myapp.conf << 'EOF' LogFile = myapp/app.log Archive = myapp/app.log.* PivotException = 1 EOF ```
Step 2 - create the service config:
``` mkdir -p /etc/logwatch/conf/services cat > /etc/logwatch/conf/services/myapp.conf << 'EOF' Title = "MyApp Summary" LogFile = myapp EOF ```
Step 3 - write the filter script:
```
mkdir -p /etc/logwatch/scripts/services
cat > /etc/logwatch/scripts/services/myapp << 'SCRIPT'
#!/usr/bin/perl
use strict;
my (%errors, %warns, $total);
while (
Test it without waiting for cron:
``` logwatch --service myapp --output stdout --range today ```
This pattern scales to any structured log format. The key is keeping the Perl script focused on counting and grouping, not on parsing raw timestamps - Logwatch handles the date-range filtering before your script sees any input.
logwatch --service myapp --output stdout --range today --detail high
Adjusting the Cron Schedule
The default daily cron job runs at a time set by the package maintainer, often 04:02 or 06:25. On RHEL-based systems, check:
``` cat /etc/cron.daily/0logwatch ```
It typically just calls `/usr/sbin/logwatch` with no arguments, using /etc/logwatch/conf/logwatch.conf for all settings.
If you want reports at a specific time rather than relying on run-parts, disable the cron.daily entry and create your own:
``` rm /etc/cron.daily/0logwatch crontab -e ```
Add:
``` 0 7 * * * /usr/sbin/logwatch --output mail --range yesterday ```
For weekly digests covering the past 7 days:
``` 0 7 * * 1 /usr/sbin/logwatch --output mail --range 'between -7 days and -1 days' ```
We tested the weekly range on a server with 50GB of archived logs. Logwatch handled it without issue, though the report took 38 seconds to generate versus the usual 4 seconds for a single-day run. If you're processing large log archives, add a timeout wrapper:
``` 0 7 * * 1 timeout 300 /usr/sbin/logwatch --output mail --range 'between -7 days and -1 days' ```
0 7 * * * /usr/sbin/logwatch --output mail --range yesterday
Sending Reports to Slack or Webhooks
Logwatch supports `--output exec` which pipes the report to a command instead of mailing it. This is the hook you need for Slack, PagerDuty, or any ticketing system.
Create a wrapper script:
``` cat > /usr/local/bin/logwatch-to-slack << 'EOF' #!/bin/bash REPORT=$(cat) PAYLOAD=$(jq -n --arg text "$REPORT" '{text: $text}') curl -s -X POST -H 'Content-type: application/json' \ --data "$PAYLOAD" \ https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK EOF chmod 755 /usr/local/bin/logwatch-to-slack ```
Then call it:
``` logwatch --output exec --mailto /usr/local/bin/logwatch-to-slack --range yesterday ```
Note that `--output exec` uses `--mailto` as the path to the executable, which is confusing but correct. Slack's block limit is 3,000 characters per message. A verbose Logwatch report will exceed this. Truncate or split the report in the wrapper script, or filter Logwatch to High-priority services only before sending to Slack.
For teams running full DevOps automation pipelines, tools like taskbotshub.ai can ingest structured log summaries and trigger conditional workflows - for example, creating a JIRA ticket automatically when Logwatch reports more than 50 sudo violations in a day. The integration point is the same exec wrapper: parse the Logwatch output in the script and POST structured JSON to your automation endpoint.
logwatch --output exec --mailto /usr/local/bin/logwatch-to-slack --range yesterday --detail med
Filtering Out Noise: IgnoreList and ThresholdHigh
Every Logwatch installation accumulates noise over time - services that log aggressively, known-benign errors that appear thousands of times, or monitoring agents that flood SSH auth logs. Logwatch provides two mechanisms to handle this: ignore patterns and thresholds.
To suppress specific patterns from a service report, create an ignore file. For sshd, create /etc/logwatch/conf/services/sshd.conf and add:
``` $ignore = /Invalid user admin from 192\.168\.1\.100/ $ignore = /Disconnected from authenticating user root 10\.0\.0\./ ```
Note the Perl regex syntax. Each `$ignore` line is a separate pattern. These are applied before your filter script processes STDIN, so the matching lines never reach the summarizer.
For numeric thresholds - reporting only when a count exceeds a value - you implement this inside your custom Perl filter. The built-in service scripts often include `$ENV{'LOGWATCH_DETAIL_LEVEL'}` checks, but for threshold-based suppression you write the logic directly:
```perl if ($failed_logins > 100) { print " Failed logins: $failed_logins (THRESHOLD EXCEEDED)\n"; } elsif ($failed_logins > 0 && $detail >= 8) { print " Failed logins: $failed_logins\n"; } ```
In our experience, the most effective noise reduction is setting Detail = Low on services like courier, sendmail relay, and puppet, while keeping sshd, sudo, and kernel at High. This asymmetric approach gives you signal where it matters without drowning in routine operational chatter.
grep -r 'ignore' /usr/share/logwatch/conf/services/ | head -20
HTML Reports and Multiple Recipients
Logwatch generates HTML output with `--format html`. The HTML format uses inline CSS and a structured layout that renders cleanly in Gmail, Outlook, and Fastmail. We tested it on logwatch 7.8 and the HTML output was valid and rendered consistently across all three clients.
To send HTML reports:
``` logwatch --output mail --format html --mailto ops@example.com --range yesterday ```
For multiple recipients, logwatch does not support comma-separated addresses in MailTo natively. The workaround is a mail alias on the server. In /etc/aliases:
``` logwatch-dist: ops@example.com, alice@example.com, bob@example.com ```
Run `newaliases` after editing, then set `MailTo = logwatch-dist` in logwatch.conf.
Alternatively, set Output = stdout and pipe to a mailer that handles multiple recipients:
``` logwatch --output stdout --format html --range yesterday | \ mail -s "Daily Log Report: $(hostname)" \ -a 'Content-Type: text/html' \ ops@example.com alice@example.com bob@example.com ```
If you're setting up infrastructure for a team and want a consistent naming scheme for your monitoring email aliases, making the alias names descriptive matters more than it sounds - something like logwatch-prod-web01 versus just logwatch. Projects like nicename.me demonstrate how consistent, readable naming reduces ambiguity in distributed teams, and the same principle applies to internal alias conventions on shared infrastructure.
logwatch --output stdout --format html --range yesterday | mail -s "Log Report: $(hostname)" -a 'Content-Type: text/html' ops@example.com
Logwatch with systemd-journald
On systems using systemd exclusively (no traditional syslog daemon), Logwatch may find /var/log/messages empty or absent. The fix is running rsyslog or syslog-ng alongside journald, or exporting journal entries to flat files that Logwatch can parse.
The simplest approach on RHEL 9 is to ensure rsyslog is installed and active:
``` dnf install rsyslog systemctl enable --now rsyslog ```
journald forwards to rsyslog by default on RHEL/Rocky via the ForwardToSyslog setting. Verify:
``` grep ForwardToSyslog /etc/systemd/journald.conf ```
If it's commented out or set to no, enable it:
``` echo 'ForwardToSyslog=yes' >> /etc/systemd/journald.conf systemctl restart systemd-journald ```
For systems where you cannot install rsyslog - minimal containers or locked-down environments - the alternative is writing a wrapper that exports journal entries to a flat file before Logwatch runs:
``` #!/bin/bash journalctl --since yesterday --until now --output short-iso \ > /var/log/journal-export.log logwatch --logfile /var/log/journal-export.log --range today --output mail ```
This approach is less clean but works reliably. We ran it on Ubuntu 24.04 with no rsyslog installed and Logwatch parsed the exported journal file correctly, though service-specific filters didn't apply since the log format doesn't match what the service scripts expect.
journalctl --since yesterday --until now --output short-iso > /var/log/journal-export.log
Verifying Report Delivery and Troubleshooting
The most common failure mode is reports not arriving due to mail delivery issues, not Logwatch bugs. Before assuming Logwatch is broken, verify the local MTA is functioning:
``` echo 'test' | mail -s 'MTA test' your@email.com tail -f /var/log/maillog ```
If the MTA is working but reports don't arrive, run Logwatch with --output stdout and redirect to a file to confirm it's generating output:
``` logwatch --output stdout --range yesterday > /tmp/lw-test.txt wc -l /tmp/lw-test.txt ```
Zero lines means Logwatch found no matching log data for yesterday's date range - check that your logs are being written with correct timestamps. Log files with timestamps in UTC while the system timezone is set to US/Eastern will cause Logwatch to miss entries when you run with --range yesterday.
Verify system timezone:
``` timedatectl status ```
If your logs are UTC but your system is in a local timezone, force Logwatch to use UTC:
``` TZ=UTC logwatch --output stdout --range yesterday ```
For debugging a custom service filter specifically, use --debug to see what lines Logwatch is passing to your script:
``` logwatch --service myapp --output stdout --range today --debug full 2>&1 | head -100 ```
The `--debug full` flag is verbose enough to show the exact lines being piped into each service filter, which makes diagnosing regex mismatches in custom filters straightforward.
TZ=UTC logwatch --output stdout --range yesterday --detail high 2>&1 | tee /tmp/lw-debug.txt