Keeping ClamAV virus definitions current in production
ClamAV is only as effective as its virus database. The Cisco Talos team publishes database updates multiple times per day. Running with a database that is days or weeks old means recent malware families — including fast- moving ransomware and phishing droppers — may not be detected.
This guide covers every common deployment scenario and gives you a Node.js health-check snippet to monitor database age in your application.
freshclam tool ships with ClamAV and downloads updated
definitions from the ClamAV mirror network. Run it periodically and it
handles incremental updates, mirrors, and retries automatically.
Bare metal / VPS
Using the clamav-freshclam systemd service
On Debian / Ubuntu, installing clamav-freshclam creates a
systemd service that runs freshclam as a daemon and checks for updates
every few hours:
sudo apt-get install -y clamav-freshclam sudo systemctl enable --now clamav-freshclam sudo systemctl status clamav-freshclam
This is the recommended approach on Linux servers — you do not need to manage a cron job yourself.
Manual cron job
If you prefer explicit control, disable the freshclam service and add a cron entry:
sudo systemctl disable --now clamav-freshclam # Add to crontab (crontab -e) # Run freshclam daily at 02:30, log to /var/log/freshclam.log 30 2 * * * /usr/bin/freshclam --quiet >> /var/log/freshclam.log 2>&1
freshclam.conf Checks parameter controls this.
systemd timer (alternative to cron)
# /etc/systemd/system/freshclam.service [Unit] Description=ClamAV virus database updater [Service] Type=oneshot ExecStart=/usr/bin/freshclam --quiet User=clamav # /etc/systemd/system/freshclam.timer [Unit] Description=Run freshclam daily [Timer] OnCalendar=daily RandomizedDelaySec=1800 # spread load across mirrors Persistent=true [Install] WantedBy=timers.target
sudo systemctl daemon-reload sudo systemctl enable --now freshclam.timer
Docker
Using the official clamav/clamav image
The official clamav/clamav image runs freshclam on container
startup. For long-running containers, it also starts a freshclam daemon that
checks for updates periodically.
To prevent slow cold starts on every container restart, mount a named volume
at /var/lib/clamav so the database persists across restarts:
services:
clamav:
image: clamav/clamav:stable
ports:
- "3310:3310"
volumes:
- clamav_db:/var/lib/clamav # persists the virus database
volumes:
clamav_db:
Custom image (database baked in)
For Lambda or environments where you cannot use volumes, bake the database into your image and rebuild weekly:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y clamav && \
freshclam && \
apt-get clean && rm -rf /var/lib/apt/lists/*
Schedule a weekly CI/CD pipeline to rebuild and push this image. See Serverless virus scanning with AWS Lambda for a complete Lambda container example.
Kubernetes
Persistent volume for the database
Mount a PersistentVolumeClaim at /var/lib/clamav
in the clamd Deployment so the database survives pod restarts:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: clamav-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
---
# In the clamd Deployment spec:
volumes:
- name: clamav-db
persistentVolumeClaim:
claimName: clamav-db
CronJob for daily updates
A Kubernetes CronJob that runs freshclam against the shared PVC:
apiVersion: batch/v1
kind: CronJob
metadata:
name: freshclam-update
spec:
schedule: "0 3 * * *" # Daily at 03:00 UTC
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: freshclam
image: clamav/clamav:stable
command: ["freshclam", "--config-file=/etc/clamav/freshclam.conf"]
volumeMounts:
- name: clamav-db
mountPath: /var/lib/clamav
volumes:
- name: clamav-db
persistentVolumeClaim:
claimName: clamav-db
After freshclam runs, clamd picks up the new database automatically if
SelfCheck 3600 is set in clamd.conf (the official
image sets this by default).
Monitoring database age from Node.js
Add a health-check endpoint that reports the age of the ClamAV database. Alert if it is more than 48 hours old:
const fs = require('fs');
const path = require('path');
const DB_PATHS = {
linux: '/var/lib/clamav/main.cvd',
darwin: '/usr/local/share/clamav/main.cvd',
win32: 'C:\\ProgramData\\ClamAV\\main.cvd',
};
function getClamavDbAgeHours() {
const dbPath = DB_PATHS[process.platform];
if (!dbPath || !fs.existsSync(dbPath)) return null;
const stat = fs.statSync(dbPath);
const ageMs = Date.now() - stat.mtimeMs;
return Math.round(ageMs / (1000 * 60 * 60));
}
// Health check endpoint
app.get('/health/clamav', (req, res) => {
const ageHours = getClamavDbAgeHours();
if (ageHours === null) {
return res.status(503).json({ status: 'error', message: 'ClamAV database not found.' });
}
const stale = ageHours > 48;
res.status(stale ? 503 : 200).json({
status: stale ? 'stale' : 'ok',
dbAgeHours: ageHours,
message: stale
? `ClamAV database is ${ageHours} hours old — run freshclam.`
: `Database is current.`,
});
});
freshclam.conf tips
Key settings in /etc/clamav/freshclam.conf (Linux):
| Setting | Recommended value | Notes |
|---|---|---|
Checks |
24 |
Check for updates 24 times per day (every hour). Max allowed by mirrors. |
DatabaseMirror |
database.clamav.net |
Default mirror. Add a second mirror entry as fallback. |
NotifyClamd |
/etc/clamav/clamd.conf |
Tells clamd to reload the database when freshclam downloads an update. |
MaxAttempts |
5 |
Retry count on mirror failure. |
Next steps
- Setting up ClamAV in Docker Compose? See Running pompelmi with ClamAV in Docker Compose.
- Running clamd in Kubernetes? See Setting up pompelmi with ClamAV on Kubernetes.
- Installing ClamAV for the first time? See How to install ClamAV on macOS, Linux and Windows.