Skip to content

About

ClamAV virus/malware scanner with REST API. This is a two in one docker image which runs the open source virus scanner ClamAV (https://www.clamav.net/), performs automatic virus definition updates as a background process and provides a REST API interface to interact with the ClamAV process.

Topics

Resources

Contributing

Stars

91 stars

Watchers

6 watching

Forks

Latest commit

 

History

507 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status Latest Release License: MIT

clamav-rest

ClamAV virus/malware scanner with REST API. This is a two in one docker image which runs the open source virus scanner ClamAV, performs automatic virus definition updates as a background process and provides a REST API interface to interact with the ClamAV process.

Table of Contents

Introduction

This is a two in one docker image which runs the open source virus scanner ClamAV (https://www.clamav.net/), performs automatic virus definition updates as a background process and provides a REST API interface to interact with the ClamAV process.

📢 New in December 2025: We've migrated to semantic versioning! Docker images are now tagged with version numbers like v1.2.3 instead of dates. Releases are automatically created when pull requests are merged, with versions determined by conventional commit messages. Check our Releases page for detailed changelogs.

Installation

Automated builds of the image are available on Docker Hub and are the recommended method of installation. Grab the lastest release:

docker pull ajilaag/clamav-rest

The following image tags are available:

  • latest - Most recent stable release
  • v1.2.3 - Specific semantic version (recommended for production)
  • v1.2 - Latest patch version of a minor release
  • v1 - Latest minor version of a major release
  • sha-... - Specific git commit (for testing/debugging)

Quick Start

See this docker-compose file for non-root read-only usage.

Run clamav-rest docker image:

docker run -p 9000:9000 -p 9443:9443 -itd --name clamav-rest ajilaag/clamav-rest

The REST endpoints are now available on port 9000 (for http) and 9443 (for https).

If at least one virus is found, the API returns a 406 - Not Acceptable response, a 200 - OK otherwise.

Verify that the service detects common test virus signatures:

HTTP:

$ curl -i -F "file=@eicar.com.txt" http://localhost:9000/v2/scan
HTTP/1.1 100 Continue

HTTP/1.1 406 Not Acceptable
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Aug 2017 20:22:34 GMT
Content-Length: 56

[{ "Status": "FOUND", "Description": "Eicar-Test-Signature","FileName":"eicar.com.txt"}]

HTTPS:

$ curl -i -k -F "file=@eicar.com.txt" https://localhost:9443/v2/scan
HTTP/1.1 100 Continue

HTTP/1.1 406 Not Acceptable
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Aug 2017 20:22:34 GMT
Content-Length: 56

[{ "Status": "FOUND", "Description": "Eicar-Test-Signature","FileName":"eicar.com.txt"}]

Observe that the service returns 200 for a clean file:

HTTP:

$ curl -i -F "file=@clamrest.go" http://localhost:9000/v2/scan

HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Aug 2017 20:23:16 GMT
Content-Length: 33

[{ "Status": "OK", "Description": "","FileName":"clamrest.go"}]

HTTPS:

$ curl -i -k -F "file=@clamrest.go" https://localhost:9443/v2/scan

HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Aug 2017 20:23:16 GMT
Content-Length: 33

[{ "Status": "OK", "Description": "","FileName":"clamrest.go"}]

Status Codes

  • 200 - OK: clean file = no KNOWN infections
  • 400 - ClamAV returned general error for file
  • 406 - Not Acceptable: payload is infected
  • 412 - Unable to parse the file provided. This also happens when you call scanFile and the uri parameter path doesn't point to a file on disk.
  • 413 - Request entity too large: the file exceeds the scannable limit. Set MAX_FILE_SIZE to scan larger files
  • 422 - Filename is missing in MimePart
  • 501 - Unknown request

Security considerations

In order to remain backward compatible, we allow all CORS related origins by setting ALLOW_ORIGINS to *. Please change is to a list of allowed origins or to blank for only allowing same origin requests for increased security. See the Configuration Section below for more details on ALLOW_ORIGINS.

Endpoints

Utility endpoints

Endpoint Description
/ Home endpoint, returns stats for the currently running process
/version Returns the clamav binary version and also the version of the virus signature databases and the signature last update date.
/metrics Prometheus endpoint for scraping metrics.

Scanning endpoints

Endpoint Description
/v2/scan Scanning endpoint, accepts a multipart/form-data request with one or more files and returns a json array with status, description and filename, along with the most severe http status code that was possible to determine. example response: [{"Status":"OK","Description":"","FileName":"checksums.txt"}]
/scanFile?path=/folder/file.txt A scanning endpoint that will scan a file using SCAN. A practical example would be to mount a share into the container where you put a file into a folder, call /scanFile and let it scan the content.example response: {"Status":"FOUND","Description":"Win.Test.EICAR_HDB-1","FileName":"/clamav/tmp/eicar.test"}
/scanPath?path=/folder A scanning endpoint that will scan a folder using ALLMATCHSCAN. A practical example would be to mount a share into the container where you dump files into a folder, call /scanPath and let it scan the whole directory content, then continue processing them.example response: [{"Raw":"/folder: OK","Description":"","Path":"/folder","Hash":"","Size":0,"Status":"OK"}]
/scanHandlerBody This endpoint scans the content in the HTTP POST request body. example response: {OK 200}
/scan [DEPRECATED] This endpoint scans in a similar manner to /v2/scan but does return one or more json objects without a valid json structure in between (no json array). It also does not include the filename as a json property. This endpoint is still present in the api for backwards compatibility for those who still use it, but it will also return headers indicating deprecation and pointing out the new, updated endpoint, /v2/scan. This endpoint does accept a multipart/form-data endpoint that by http standards can accept multiple files, and does scan them all, but the implementation of the endpoint indicates that it was originally (probably) meant to only scan one file at a time. Please don't rely on this endpoint to exist in the future. This project has the intention to sunset it to keep the project focus on a well maintainted set of features.example response: {"Status":"OK","Description":""}

Configuration

Environment Variables

Below is the complete list of available options that can be used to customize your installation.

Parameter Description
MAX_SCAN_SIZE Amount of data scanned for each file. Defaults to 100M
MAX_FILE_SIZE Do not scan files larger than this size. Defaults to 25M
MAX_RECURSION How many nested archives to scan. Defaults to 16
MAX_FILES Number of files to scan within an archive. Defaults to 10000
MAX_EMBEDDEDPE Maximum file size for embedded PE. Defaults to 10M
MAX_HTMLNORMALIZE Maximum size of HTML to normalize. Defaults to 10M
MAX_HTMLNOTAGS Maximum size of normlized HTML file to scan. Defaults to 2M
MAX_SCRIPTNORMALIZE Maximum size of a script to normalize. Defaults to 5M
MAX_ZIPTYPERCG Maximum size of ZIP to reanalyze type recognition. Defaults to 1M
MAX_PARTITIONS How many partitions per raw disk to scan. Defaults to 50
MAX_ICONSPE How many icons in PE to scan. Defaults to 100
MAX_RECONNECT_TIME Maximum timeout while waiting for ClamAV to start. Defaults to 30
PCRE_MATCHLIMIT Maximum PCRE match calls. Defaults to 100000
PCRE_RECMATCHLIMIT Maximum recursive match calls to PCRE. Defaults to 2000
SIGNATURE_CHECKS How many times per day to check for a new database signature. Must be between 1 and 50. Defaults to 2
PROXY_SERVER Specify a proxy for freshclam to utilize. Default disabled (optional)
PROXY_PORT The port for the proxy server. Default disabled (optional)
PROXY_USERNAME The username for the proxy server. Default disabled (optional)
PROXY_PASSWORD The password for the proxy server. Default disabled (optional)

TLS Certificates

TLS certificates are not embedded in the Docker image. To enable HTTPS on port 9443, mount your own certificate and key at runtime. If no certificates are provided, the HTTPS server is gracefully skipped and only HTTP (port 9000) is available.

Docker:

docker run -p 9000:9000 -p 9443:9443 \
  -v /path/to/server.crt:/etc/ssl/clamav-rest/server.crt:ro \
  -v /path/to/server.key:/etc/ssl/clamav-rest/server.key:ro \
  -itd --name clamav-rest ajilaag/clamav-rest

Kubernetes:

Create a TLS secret and mount it as a volume. See the commented-out examples in kubernetes_example/deployment.yaml:

kubectl create secret tls clamav-rest-tls \
  --cert=server.crt --key=server.key -n clamav-rest

You can also override the certificate paths using the SSL_CERT and SSL_KEY environment variables.

Custom freshclam.conf

For advanced freshclam configuration (e.g., custom database mirrors, private mirrors, or settings not exposed via environment variables), you can provide your own freshclam.conf file.

When a custom freshclam.conf is detected, the entrypoint skips all freshclam-related configuration. This means the following environment variables are ignored when a custom config is provided: SIGNATURE_CHECKS, PROXY_SERVER, PROXY_PORT, PROXY_USERNAME, PROXY_PASSWORD.

Your custom freshclam.conf must include these directives for the container to function correctly:

Foreground yes
DatabaseDirectory /clamav/data
NotifyClamd /clamav/etc/clamd.conf

Docker:

docker run -p 9000:9000 \
  -v /path/to/freshclam.conf:/clamav/etc/freshclam.conf:ro \
  -itd --name clamav-rest ajilaag/clamav-rest

Kubernetes:

Add the configuration to a ConfigMap and mount it as a volume. See the commented-out examples in kubernetes_example/configmap.yaml and kubernetes_example/deployment.yaml.

Airgapped Environments

This image can run in an airgapped environment, but there are a few important details to understand up front.

By default, the virus database inside the image is only as new as the time when that image was built. During the Docker image build, freshclam is run once and the downloaded signature database is baked into the image. If you start that image later in a disconnected environment, the initial database age is therefore roughly:

  • current time - image build time

For example, if the image was built 14 days ago and then transferred into an airgapped environment, the bundled signatures will also be about 14 days old before any further update process is possible.

At runtime the entrypoint starts freshclam as a daemon. In a disconnected environment this will not be able to reach the public ClamAV mirrors, so you should expect update failures in the logs unless you provide an internal update source.

Tips for running airgapped:

  • Preload the image before disconnecting from the internet, then start it with docker compose up -d --pull never so Docker does not try to fetch anything.
  • Keep /clamav/data on a persistent volume so imported database updates survive container recreation.
  • Verify the actual database version after startup with curl http://localhost:9000/version or clamscan --database=/clamav/data --version inside the container.
  • Test offline behavior on a connected machine by loading the image first, disconnecting the host, and then starting the stack locally.

There are three practical ways to update signatures in an airgapped environment:

  1. Rebuild or replace the image outside the airgapped network and transfer it in again. This refreshes the bundled database snapshot because freshclam runs during image build.

  2. Provide an internal mirror or other reachable update service inside the airgapped network. In that setup, mount a custom freshclam.conf that points freshclam to your internal mirror instead of the public internet.

  3. Import database files manually into /clamav/data. This is the most common option when there is no internal mirror. Transfer the relevant ClamAV database files into the mounted data directory, then restart the container or otherwise ensure clamd reloads the updated files.

If you are distributing the image into multiple offline environments, the safest operational model is usually:

  • build or pull the image on a connected system
  • record the image build date and signature version
  • transfer the image with docker save / docker load
  • mount /clamav/data persistently
  • update either by importing newer database files or by publishing a newer image snapshot

Networking

Port Description
3310 ClamD listening port (internal use only)
9000 HTTP REST listening port
9443 HTTPS REST listening port

Running on Kubernetes

The manifests in this section have been tested on a best-effort basis with kind. That gives a practical local validation path, but it also comes with limitations, most notably that CRD-based resources such as ServiceMonitor are not available unless you install the corresponding operators first.

Note

Optional Kubernetes features are described in the next section, Maintenance / Monitoring.

Sample manifests are provided in kubernetes_example/. The default runnable sample set deploys all resources into the clamav-rest namespace:

  • Namespace
  • ConfigMap
  • Deployment
  • Service
  • NetworkPolicy

A way to mount a data directory from a PVC has been added to the Deployment manifest. Uncomment it to use it.

Optional features are organized as separate kustomize packages and are not included by kubectl apply -k kubernetes_example/:

  • kubernetes_example/optional/signature-age-check/ provides the /version signature-age CronJob.
  • kubernetes_example/optional/prometheus/ provides the metrics Service, ingress policy, and ServiceMonitor. The ServiceMonitor requires Prometheus Operator-compatible CRDs.

Running the sample set on kind

The commands below assume you want a local test cluster named clamav-rest:

kind create cluster --name clamav-rest
kubectl cluster-info --context kind-clamav-rest
kubectl config use-context kind-clamav-rest

kubectl apply --dry-run=client -k kubernetes_example/

kubectl apply -k kubernetes_example/
kubectl -n clamav-rest rollout status deployment/clamav-rest-service --timeout=10m
kubectl -n clamav-rest get pods,svc,networkpolicy

On first startup the Pods may take several minutes to become Ready because ClamAV downloads and loads signature databases during boot.

Apply optional features separately as needed:

kubectl apply -k kubernetes_example/optional/signature-age-check/

# Requires Prometheus Operator-compatible CRDs.
kubectl apply -k kubernetes_example/optional/prometheus/

To clean up the local kind test environment:

kubectl delete namespace clamav-rest --ignore-not-found=true
kind delete cluster --name clamav-rest

Maintenance / Monitoring

Shell Access

For debugging and maintenance purposes you may want access the container's shell:

docker exec -it (whatever your container name is e.g. clamav-rest) /bin/sh

Checking the version with the clamscan command requires you to provide the custom database path. The default value is /clamav/data set in the /clamav/etc/clamd.conf file, and the clamav service was started with this /clamav/etc/clamd.conf referenced in entrypoint.sh.

clamscan --database=/clamav/data --version

Monitoring Signature Age

Using clamav-rest-sigmon

The sister project clamav-rest-sigmon is specifically built to monitor the signature age of clamav-rest. It can run as a sidecar, a standalone container, or a Kubernetes CronJob and obtains the loaded signature date from the clamav-rest API.

In server mode it exposes GET /health/signature-age, returning HTTP 200 while the signatures are within the configured maximum age and HTTP 420 when they are too old. The threshold defaults to 26 hours and can be configured with MAX_SIGNATURE_AGE_HOURS or overridden per request with the maxAgeHours query parameter. It also supports a one-shot mode suitable for CronJobs. See the project documentation for deployment and configuration details.

Monitoring Signature Age via /version

Approach: polling the /version endpoint and alerting on the age of Signature_date. The endpoint returns the ClamAV version together with the signature version and the date of the loaded signature database:

Tip

A runnable Kubernetes version of this approach is provided below and in kubernetes_example/optional/signature-age-check/.

{ "Clamav": "<engine version>", "Signature": "<signature version>", "Signature_date": "<signature date>" }

Suggested steps in detail:

  1. Call GET /version on a schedule from your host, a cron job, or any external monitoring tool.
  2. Parse Signature_date from the JSON response.
  3. Convert that date to a Unix timestamp in your preferred scripting/runtime environment.
  4. Compare it with the current time.
  5. Trigger an alert if the age exceeds your threshold, for example 24 hours, 48 hours, or 7 days.

Example shell pattern using curl and python3:

version_json=$(curl -fsS http://localhost:9000/version)
python3 -c '
import json
import sys
from datetime import datetime

payload = json.loads(sys.argv[1])
max_age_seconds = int(sys.argv[2])

signature_date = payload["Signature_date"]

# Adjust the format string if your ClamAV build emits a different date format.
signature_time = datetime.strptime(signature_date, "%a %b %d %H:%M:%S %Y")
age_seconds = int(datetime.now().timestamp() - signature_time.timestamp())

if age_seconds > max_age_seconds:
    print(f"ALERT: ClamAV signatures are {age_seconds} seconds old")
    sys.exit(1)

print(f"OK: ClamAV signatures are {age_seconds} seconds old")
' "$version_json" "$((48 * 60 * 60))"

For Kubernetes, a runnable example is provided in kubernetes_example/optional/signature-age-check/. Apply it with kubectl apply -k kubernetes_example/optional/signature-age-check/. It polls http://clamav-rest-service:9000/version from inside the clamav-rest namespace and exits non-zero when the configured age threshold is exceeded.

You can validate the CronJob manually without waiting for the schedule:

kubectl -n clamav-rest create job --from=cronjob/clamav-signature-age-check clamav-signature-age-check-manual
kubectl -n clamav-rest wait --for=condition=complete job/clamav-signature-age-check-manual --timeout=5m
kubectl -n clamav-rest logs job/clamav-signature-age-check-manual
kubectl -n clamav-rest get jobs,pods

To exercise the failure path, temporarily lower the threshold, run another manual Job, and then restore the default:

kubectl -n clamav-rest set env cronjob/clamav-signature-age-check MAX_AGE_SECONDS=1
kubectl -n clamav-rest create job --from=cronjob/clamav-signature-age-check clamav-signature-age-check-fail
kubectl -n clamav-rest wait --for=condition=failed job/clamav-signature-age-check-fail --timeout=5m
kubectl -n clamav-rest logs job/clamav-signature-age-check-fail
kubectl -n clamav-rest set env cronjob/clamav-signature-age-check MAX_AGE_SECONDS=172800

In Kubernetes, the usual pattern is to let the CronJob exit non-zero when the threshold is exceeded and then have your cluster monitoring stack alert on failed Jobs or failing Pods. Outside Kubernetes, you can wire the same logic into any alerting mechanism you already use.

Prometheus

Prometheus metrics were implemented, which can be retrieved from the /metrics endpoint:

HTTP:
curl http://localhost:9000/metrics

HTTPS:
curl https://localhost:9443/metrics

Description of the metrics is available at these endpoints as part of the metrics themselves.

Development

Source code can be found here: https://github.com/ajilach/clamav-rest

Building the golang binary locally

# For linux on amd64
GOOS=linux GOARCH=amd64 go build

# For macOS on arm64
GOOS=darwin GOARCH=arm64 go build

# For macOS on amd64
GOOS=darwin GOARCH=amd64 go build

# For Windows (using Git Bash or similar)
GOOS=windows GOARCH=amd64 go build

Containerizing the application

docker build . -t clamav-rest
docker run -p 9000:9000 -p 9443:9443 -itd --name clamav-rest clamav-rest

Note that the docker build command also takes care of compiling the source. Therefore you do not need to perform the manual build steps from above nor do you need a local go development environment.

Protocol Support

Go 1.24 added unencrypted "HTTP/2 with Prior Knowledge" support into the net/http standard library, which is useful for microservices behind firewalls and load balancers.

Simple checks on the docker container running locally:

$ curl -i -k --http1.1 -F "file=@clamrest.go" https://localhost:9443/v2/scan

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 28 Feb 2025 21:49:54 GMT
Content-Length: 59

[{"Status":"OK","Description":"","FileName":"clamrest.go"}]
$ curl -i -k --http2-prior-knowledge -F "file=@clamrest.go" http://localhost:9000/v2/scan

HTTP/2 200
content-type: application/json; charset=utf-8
content-length: 59
date: Fri, 28 Feb 2025 21:49:17 GMT

[{"Status":"OK","Description":"","FileName":"clamrest.go"}]
$ curl -i -k --http2 -F "file=@clamrest.go" https://localhost:9443/v2/scan

HTTP/2 200
content-type: application/json; charset=utf-8
content-length: 59
date: Fri, 28 Feb 2025 21:49:33 GMT

[{"Status":"OK","Description":"","FileName":"clamrest.go"}]

Running Tests

The test suite runs in a container using Dockerfile.test. The test cases are end-to-end tests that need the full realistic environment to run, with clamav and clamav-rest running. The only requirement for your local system is Docker or Podman.

Building with Dockerfile.test and running the container will start clamav and clamav-rest, then run the Go end-to-end tests within the container and then exit. The exit code of the container matches how many failed tests there are, with no failed tests, a successful exit code of zero is emitted.

Example on how to build and run the tests:

docker build -f Dockerfile.test -t clamav-rest-test .
docker run clamav-rest-test

Release Notes

For detailed release notes, changelogs, and version history, please see our Releases page.

We follow Semantic Versioning and use Conventional Commits to automatically generate changelogs.

NixOS Support

This project provides a default.nix derivation that allows you to build the clamav-rest binary using Nix:

nix-build

The resulting binary will be available in the ./result/bin/ directory.

Note: NixOS support is provided on a best effort basis. The derivation is maintained as a convenience for the Nix community, but may be dropped in future releases if it requires excessive maintenance or becomes incompatible with the project's development workflow.

Deprecations

/scan endpoint

As of release 20250109 the /scan endpoint is deprecated and /v2/scan is now the preferred endpoint to use.

Differences between /scan and /v2/scan

Since the endpoint can receive one or several files, the response has been updated to always be returned as a json array and the filename is now included as a property in the response, to make it easy to find out what file(s) contains virus.

Centos Dockerfile

The centos.Dockerfile has been last updated in the release 20250109 but will not be maintained anymore going forward. If there are community members using it, please consider contributing.

Contributing

We welcome and appreciate contributions from the community! 🎉

Please read our CONTRIBUTING.md for detailed guidelines on:

  • Using conventional commits for automatic versioning
  • Development workflow and testing
  • How our CI/CD pipeline works
  • Code standards and best practices

Thank you for helping improve the project!

History

This work is based on the awesome work done by o20ne/clamav-rest which is based on niilo/clamav-rest which in turn is based on the original code from osterzel/clamav-rest.

References

License

This project is licensed under the MIT License. See the LICENSE file for details.

About

ClamAV virus/malware scanner with REST API. This is a two in one docker image which runs the open source virus scanner ClamAV (https://www.clamav.net/), performs automatic virus definition updates as a background process and provides a REST API interface to interact with the ClamAV process.

Topics

Resources

Contributing

Stars

91 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages