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.

The 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
Do not run freshclam more than once per hour per server. The ClamAV mirror network rate-limits excessive requests and may block your IP. The 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.`,
  });
});
Wire this endpoint into your monitoring system (Datadog, Prometheus, Uptime Robot). A stale database alarm is a critical signal — it means your scanner is running blind against recent threats.

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