Migrating from ClamAV
exav is designed as a drop-in: it uses ClamAV’s signature formats, prints
clamscan’s output format, and speaks the clamd wire protocol. In most setups
the switch is changing one command or one socket — your signature databases,
updater, and client tooling stay exactly as they are.
The flags are the part to check first. exav implements the ones day-to-day
commands use and refuses the ones it does not implement, so a command line
carrying an unsupported flag fails at startup rather than scanning under
settings you did not ask for. The
ClamAV flag matrix has every clamscan,
clamd and clamdscan flag with its exav status.
One binary, three roles
Section titled “One binary, three roles”ClamAV ships three programs; exav ships one that plays all three parts. The flags on the command line pick the part:
| ClamAV binary | exav invocation | Selected by |
|---|---|---|
clamscan (local scanner) |
exav PATH… |
paths, neither --listen nor --connect |
clamd (daemon) |
exav --listen ADDR |
--listen |
clamdscan (client) |
exav --connect ADDR PATH… |
--connect and paths |
The direction is the flag: --listen is a place exav accepts connections,
--connect a place it makes one. --connect plus paths means ask the daemon
— the client loads no database and sends the paths over the socket. Paths on
their own mean scan here, with a database this process loads itself.
--connect with no paths is neither role, and exav says so rather than idling:
exav --connect /run/clamav/clamd.ctl# exav: no input; provide PATH(s), `-` (stdin), or --listenexav never inspects the name it was invoked under, so a symlink called
clamdscan pointing at the binary runs a local scan like any other exav
call. Two wrapper scripts are what carry an existing command line across.
Wrapping clamscan and clamdscan
Section titled “Wrapping clamscan and clamdscan”Put these earlier on PATH than ClamAV’s own (/usr/local/bin before
/usr/bin on most systems), and scripts, cron jobs and CI steps that call
clamscan or clamdscan keep their command lines:
#!/bin/shexec exav --sig-dir /var/lib/clamav "$@"#!/bin/shexec exav --connect /run/clamav/clamd.ctl "$@"Each pins the one thing the caller’s command line does not carry, and passes everything else through:
--sig-dirnames the directoryfreshclampopulates. exav’s own default is/var/lib/exav, so without this a caller that relied onclamscan’s default database directory getsno signature database loaded — refusing to run. A caller that passes its own-doverrides the pinned directory; the two on one command line are not a conflict.--connecthas to be the address the daemon listens on — the--listenof your daemon or of the service unit.--connect HOST:PORTwraps a client for a daemon on another host.
clamscan’s exit codes mean the same thing here — 0 clean, 1 a detection,
2 the scanner failed — and there is a fourth, 3, for a file exav could not
fully examine. --partial-as ok folds it back into 0, which is what ClamAV
answers.
A clamscan line’s flags do not all survive: exav’s are its own, and a
clamscan flag exav does not have stops the run rather than being ignored,
so a wrapped command line never scans under settings nobody asked for. The
flag matrix is the per-flag mapping; the ones
that come up most are -i → --quiet, -r → nothing (directories recurse by
default), --allmatch → --all-matches, and the client’s --stream /
--fdpass → --send-as contents / --send-as fd.
The ClamAV flag matrix covers the rest of a
command line — every clamscan, clamd and clamdscan flag against exav’s.
It assumes you are already invoking exav; these wrappers are what get an
existing clamscan / clamdscan line there.
1. Keep your existing signatures
Section titled “1. Keep your existing signatures”exav reads ClamAV’s own DB files — point it at the directory freshclam (or
cvdupdate) already populates:
exav -d /var/lib/clamav /data # Debian/Ubuntu default DB dirNothing changes about updating signatures: keep running freshclam / cvd on
your normal schedule. For faster startup on big signature sets,
build a database once:
exav -d /var/lib/clamav --build-db /var/lib/clamav/exav.exavdb # after each freshclamexav -d /var/lib/clamav/exav.exavdb /data # sub-second cold start2. Replacing clamscan (one-shot scanning)
Section titled “2. Replacing clamscan (one-shot scanning)”Same output format (PATH: Signature FOUND / PATH: OK), and the same shape of
command line. The simplest swap:
exav -d /var/lib/clamav /data # what `clamscan -r /data` scannedFor an interactive shell, alias clamscan='exav --sig-dir /var/lib/clamav'. For
scripts and cron jobs, which do not read your shell aliases, use the
wrapper on PATH.
Flag spellings
Section titled “Flag spellings”exav’s flags are its own. A clamscan flag exav does not have is refused,
never ignored: the run stops rather than scanning under settings you did not ask
for. So a migrated command line needs its limit flags renamed, and the rename is
mechanical — each exav flag is named after the bound it sets:
clamscan spelling |
exav flag | Bounds |
|---|---|---|
--max-filesize |
--max-input-bytes |
The largest top-level input scanned |
--max-scansize |
--max-extracted-bytes |
What decompression may produce across one top-level file: deep-analysis size and the summed extracted bytes |
--max-recursion |
--max-unpack-depth |
Nesting depth, containers inside containers |
--max-files |
--max-members |
Members visited across the whole recursive walk |
Two more bounds have no clamscan counterpart at all: --max-object-bytes (the
most memory a single materialized object may use) and --max-matcher-bytes
(cumulative bytes fed to the matcher — a CPU/time bound, not a memory one).
Aliases were the alternative, and cost more than they save: two names for one bound is two things to document, and a command line that sets the same bound under both spellings has to be refused anyway. One spelling per bound, and a clear error naming the exav flag, is the shorter path through a migration:
exav --max-files 500 /data# exav: --max-files is a clamscan flag; use --max-members, members visited# across the whole recursive walk# exav: the full mapping is at https://exav.org/reference/clamav-flag-matrix/The one deliberate difference
Section titled “The one deliberate difference”| ClamAV | exav | |
|---|---|---|
| A file it couldn’t fully scan (size/ratio/recursion limit, unsupported codec, encrypted) | often reports OK, exit 0 |
status PARTIAL under LIMITS-EXCEEDED / UNSCANNABLE / PASSWORD-PROTECTED, exit 3 |
This makes exav safer (see Never silently clean),
and it is a new code rather than a reused one, so nothing a script already reads
changes meaning: 2 still means the scanner failed. What a CI job has to add is
3, and it should read it as “not a pass”. --partial-as ok restores ClamAV’s
answer if that is what the pipeline wants; --clamav-compat sets it for you.
By default exav runs at full capability. To match a stock ClamAV build’s
documented limits and extractor set for apples-to-apples differential testing,
use --clamav-compat (or set its individual flags). See
Differential testing and the
CLI reference.
3. Replacing clamd (the resident daemon)
Section titled “3. Replacing clamd (the resident daemon)”This is the high-value swap for servers and mail gateways: run exav’s daemon on
the same socket clamd uses, and clamdscan, milters (clamav-milter,
Amavis, Rspamd, MailScanner), and every clamd client library keep working
unchanged.
-
Find clamd’s socket (
LocalSocketinclamd.conf):Terminal window grep -i localsocket /etc/clamav/clamd.conf # e.g. /run/clamav/clamd.ctl -
Stop clamd so the socket is free:
Terminal window sudo systemctl stop clamav-daemon -
Run exav on that socket, reading the same DB dir:
Terminal window sudo -u clamav exav --listen /run/clamav/clamd.ctl -d /var/lib/clamavexav creates the socket 0600, so clients running as another user need the mode the
clamd.confgave them (LocalSocketMode, often660alongside aLocalSocketGroup). The mode rides on the address, since it is a property of that socket:Terminal window grep -i localsocketmode /etc/clamav/clamd.conf # e.g. 660sudo -u clamav exav --listen 'clamd:///run/clamav/clamd.ctl?mode=660' \-d /var/lib/clamav -
Verify from another shell — existing clamd clients work as-is:
Terminal window clamdscan --ping 1clamdscan /etc/hosts
As a managed service
Section titled “As a managed service”Install the provided unit (shipped in the .deb, or copy
packaging/exav-clamd.service):
sudo systemctl stop clamav-daemon && sudo systemctl disable clamav-daemonsudo systemctl enable --now exav-clamdclamdscan --ping 1The unit runs as the existing clamav user on the Debian socket path; edit
ExecStart / RuntimeDirectory for Fedora/RHEL or to match your clamd.conf.
See the daemon guide for the protocol coverage.
4. Encrypted archives & passwords
Section titled “4. Encrypted archives & passwords”Encrypted members are reported PASSWORD-PROTECTED (never a silent clean).
Supply passwords to decrypt and scan inside:
exav --passwords secret --passwords hunter2 /data # try a pool# or a ClamAV .pwdb password database in the signature dirZIP (ZipCrypto + WinZip AES), 7z AES-256, encrypted DMG, PDF, and the Office family (legacy XLS and OOXML) are decrypted; RAR AES and PKWARE Strong Encryption are detected but not decrypted. See Encryption support.
What to watch for
Section titled “What to watch for”-
Signature coverage is ClamAV’s — exav runs those signatures but adds none of its own. A handful (PCRE subsignatures, some bytecode) are skipped and counted, never silently ignored.
-
Large-DB memory is currently higher in exav than ClamAV, by enough to matter: loading a stock
freshclamdirectory (main+daily+bytecode) costs several GB whereclamscanon the same files costs a fraction of that. On a host or container without the headroom the loader is OOM-killed, and an OOM kill writes nothing to the log — the process is simply gone, which reads like a crash with no cause.docker inspect --format '{{.State.OOMKilled}}'confirms it.Compile the set once with
--build-dbon a machine with room, and give every scanner the.exavdb: it loads in under a second and a fraction of the memory. A daemon pays the load once for its whole lifetime, so this is mostly a problem for the one-shotclamscanswap, which pays it per invocation. Being worked on. -
exav is beta: treat non-detections with caution and keep ClamAV as a fallback until you’ve validated coverage on your own corpus (see Differential testing).