F5 Nginx ICAP module
Table of contents
- What this is
- Get the module
- Configure the module in nginx
- Configure the ICAP server
- Run it with your own nginx
- Run it with the Docker stack (local)
- Run it on AWS (Terraform)
- Further reading
1. What this is
ngx_http_detect_icap_module is an nginx dynamic module that turns nginx
into an inline malware-scanning gateway. It intercepts uploads and downloads
passing through nginx, sends them to an ICAP (RFC 3507) server for a
verdict, and blocks anything malicious — before an upload reaches your
backend, and before a download reaches the client.
client ──upload──▶ NGINX (ngx_http_detect_icap_module)
│ REQMOD sub-request (RFC 3507)
▼
ICAP Server (e.g. Detect) ──verdict──▶ 204 clean / 200+403 block
│
clean ────────┴──────▶ proxy_pass ──▶ backend
malware ─────────────▶ 403 to client, backend never sees the bytes
Two independent modes, both per-location: REQMOD scans upload bodies
(POST/PUT/etc.) before they reach the backend; RESPMOD scans
download bodies (GET) before they reach the client. Works with NGINX
Community and Plus (built --with-compat). Both modes fail closed.
2. Get the module
Download the package matching your target from
github.com/reversinglabs/rl-nginx-icap-module/releases
— pick the release built against the exact nginx core your target runs
(open-source nginx's own version, or the nginx-X.Y.Z your NGINX Plus
reports via nginx -V). A mismatch fails to load
(module ... is not binary compatible).
sudo apt install ./nginx-icap-module_<version>_amd64.deb
Installs the .so to /usr/lib/nginx/modules/. You still need to load it
yourself — see §3.
Using the Docker stack instead (§5)?
Don't install it into a host nginx — copy the downloaded .deb into this
repo's module/ folder and symlink it to nginx-icap-module.deb (see
docker/README.md Setup §1); the Docker image installs it at build time.
3. Configure the module in nginx
load_module modules/ngx_http_detect_icap_module.so; # before http {}
http {
server {
listen 8080;
location / {
detect_icap_pass http://127.0.0.1:1344/detect; # ICAP server URL — enables scanning
detect_icap_methods POST PUT; # REQMOD: which methods to scan
detect_icap_resp_mode on; # RESPMOD: also scan responses
detect_icap_read_timeout 30s; # how long to wait for a verdict
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
}
}
}
Full directive reference: docs/MODULE.md.
4. Configure the ICAP server
The bundled reference server (icap-server/main.py) needs no config to run
plaintext on :1344 (matches detect_icap_pass http://host:1344/detect;
above). It blocks any body containing the EICAR test string or the literal
string malware-demo-signature; edit SIGNATURES/scan_payload() in that
file to change what it flags.
ICAPS (TLS) — off by default; set ICAPS_CERTFILE/ICAPS_KEYFILE to
enable a second listener on :11344 (override with ICAPS_PORT):
openssl req -x509 -newkey rsa:2048 -keyout /tmp/icap-key.pem \
-out /tmp/icap-cert.pem -days 1 -nodes -subj "/CN=icap"
ICAPS_CERTFILE=/tmp/icap-cert.pem ICAPS_KEYFILE=/tmp/icap-key.pem \
python3 icap-server/main.py
Point nginx at it with detect_icap_pass https://host:11344/detect; (and
detect_icap_ssl_verify off; for this throwaway self-signed cert only).
Real Detect/Spectra deployment instead of the reference server — just
point detect_icap_pass at it; no server-side config here. For the Docker
stack, symlink .env-rl-dev (or .env-rl-dev-tls for ICAPS) instead of
.env-local — see §6.
5. Run it with your own nginx
# ICAP server (bundled reference, or point detect_icap_pass at a real one)
python3 icap-server/main.py
# backend
pip install -r backend/requirements.txt
python3 backend/main.py 8000 files
# nginx, using the config from §3 saved to e.g. my-nginx.conf
nginx -c "$(pwd)/my-nginx.conf"
curl -X POST --data-binary "hello" http://localhost:8080/up # -> 200
5. Run it with the Docker stack (local)
Full reference (profiles, TLS, troubleshooting): docker/README.md.
mkdir -p module
cp /path/to/nginx-icap-module_*_amd64.deb module/
ln -sf "$(basename /path/to/nginx-icap-module_*_amd64.deb)" module/nginx-icap-module.deb
cd docker
ln -sf .env-local .env
# ce = NGINX Community; swap for plus to run NGINX Plus instead (needs
# NGINX_PLUS_SW_ROOT/NGINX_PLUS_DEB/NGINX_PLUS_LICENSE set in .env-local)
COMPOSE_PROFILES=ce,local-icap,test docker compose \
-f docker-compose.yml -f docker-compose-certgen.yml up -d --build
docker compose exec tester sh -c \
"curl -s -w '\nHTTP:%{http_code}\n' --cacert /certs/nginx-rproxy.crt -X POST --data-binary hello https://localhost/up"
Teardown:
COMPOSE_PROFILES=ce,local-icap,test docker compose \
-f docker-compose.yml -f docker-compose-certgen.yml down
6. Run it on AWS (Terraform)
Full reference: terraform/ec2-nginx/README.md. Prerequisites: AWS
credentials, a pre-allocated Elastic IP, a DNS A record pointing at it
(needed for the Let's Encrypt HTTP-01 challenge), and — if using the plus
profile — an S3 bucket with your NGINX Plus package/license under
versions//license/ prefixes (nginx_plus_s3_bucket in terraform.tfvars).
cd terraform/ec2-nginx
cp terraform.tfvars.example terraform.tfvars
# edit: ssh_cidr_blocks, elastic_ip_allocation_id (see above)
terraform init
terraform apply
curl "$(terraform output -raw nginx_url)"
Teardown: terraform destroy.
7. Further reading
README.md— project overview and all quickstart variantsdocker/README.md— full Docker stack referencedocs/MODULE.md/docs/ARCHITECTURE.md— directive reference and system design (in the siblingnginx-icap-module-srcrepo)docs/TESTING.md/certification/README.md— build/certification details (also innginx-icap-module-src— this repo only runs the module, it doesn't build or certify it)terraform/ec2-nginx/README.md— full Terraform reference