OWASP ZAP on AWS User Guide
Overview
This image runs OWASP ZAP (the Zed Attack Proxy), the world's most widely used open source web-application security scanner. ZAP finds vulnerabilities in web applications and APIs through automated spidering, passive scanning and active attack rules, and is a staple of DAST pipelines and penetration testing. The image is delivered as a ready-to-use scanning appliance: zap.service is enabled and starts on boot running ZAP as a headless daemon, so the moment an engineer connects over SSH the scanner is already running and its REST API is ready to drive.
This is a headless scanner service. There is no desktop GUI. ZAP runs as a long-lived daemon and you drive every capability — spiders, the AJAX spider, passive scanning, active scanning, alerts and reporting — through the authenticated ZAP REST API. The API is bound to loopback only (127.0.0.1:8080), so it answers on the instance but is never exposed to the network. Every call is authenticated with an API key that is generated fresh on the first boot of each instance, so no shared or build-time secret ever ships in the image. The instance security group opens port 22 only; you open whatever ports your own scan targets need.
The ZAP home directory — the session database, contexts, the local add-on marketplace cache and any persisted scan results — lives on a dedicated, independently resizable EBS data volume mounted at /var/lib/zaproxy. This keeps the session state off the operating system disk and lets you resize or snapshot the volume independently.
Prerequisites
Before you deploy this image you need:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network
- The AWS CLI (version 2) installed locally if you plan to deploy from the command line
Step 1: Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images choose AWS Marketplace AMIs and search for OWASP ZAP. Select the cloudimg listing and choose Select, then Continue on the subscription summary.
Pick an instance type of t3.medium or larger — ZAP is a Java application and benefits from the extra memory. Choose your EC2 key pair under Key pair (login). Under Network settings select your VPC and subnet, and either create or select a security group that opens port 22 from your management network. Leave the root volume at the default size or larger.
Select Launch instance. First boot initialisation takes a short while after the instance state becomes Running and the status checks pass: the per-instance ZAP API key is generated and applied, then the daemon restarts to pick it up. By the time you can connect, the scanner is running and the REST API answers with your unique key.
Step 2: Launch the Instance from the AWS CLI
The following block launches an instance from the cloudimg OWASP ZAP Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens port 22 from your management network.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type t3.medium \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=owasp-zap}]'
When the instance reaches the Running state and its status checks pass, note its public IP address or DNS name from the EC2 console or with aws ec2 describe-instances.
Step 3: Connect to Your Instance
Connect over SSH using your key pair and the login user for your operating system variant.
| OS variant | SSH login user |
|---|---|
| Ubuntu 24.04 | ubuntu |
ssh -i <key-name>.pem ubuntu@<public-ip>
A welcome banner prints the most useful commands. Read the full getting-started notes, including the per-instance ZAP API key and example API calls, with:
sudo cat /root/owasp-zap-info.txt
This file is readable only by root and records the API key that is unique to this instance, where ZAP is installed, where the session database lives, and example calls against the REST API.
Step 4: Verify the Service
ZAP runs as a system service that is enabled and started on boot. Confirm the daemon is active:
systemctl is-active zap
That prints active. For the full unit status, including the main PID, memory use and the command line the daemon is running, use:
systemctl status zap --no-pager
Every API call authenticates with the per-instance key, which is recorded in the root-only info file. Read it into a KEY variable at the start of each command block — the examples below do this so each block stands on its own:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
Confirm the running version over the authenticated REST API, which is pinned to ZAP 2.17.0 on this image:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
curl -s "http://127.0.0.1:8080/JSON/core/view/version/?apikey=${KEY}"
You should see {"version":"2.17.0"}. That proves the daemon is up and the API key is valid.

The REST API is bound to 127.0.0.1 and is not exposed to the network. A request with a wrong or missing key is rejected and never returns the version JSON. To reach the API from your workstation, tunnel the port over SSH from your local machine:
ssh -L 8080:127.0.0.1:8080 ubuntu@<public-ip>
Step 5: Run a Spider and Passive Scan
You drive scans entirely over the REST API. The example below targets a tiny demo site served on the instance itself so the guide is self-contained; point the url at your own application to scan it. Start the demo target in the background:
DEMO=$(mktemp -d); printf '<!doctype html><html><head><title>demo</title></head><body><h1>cloudimg demo</h1><a href="/about.html">about</a></body></html>' > "$DEMO/index.html"; printf '<!doctype html><html><body>about</body></html>' > "$DEMO/about.html"; setsid bash -c "cd '$DEMO' && exec python3 -m http.server 18080 --bind 127.0.0.1" </dev/null >/dev/null 2>&1 & sleep 2; curl -s -o /dev/null -w 'demo target: HTTP %{http_code}\n' http://127.0.0.1:18080/
Access the target through ZAP so the response is recorded into the session and the passive scanner runs over it, launch a spider crawl, then poll the returned scan id to completion and read back the URLs it discovered:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
curl -s -o /dev/null "http://127.0.0.1:8080/JSON/core/action/accessUrl/?apikey=${KEY}&url=http://127.0.0.1:18080/&followRedirects=true"
SID=$(curl -s "http://127.0.0.1:8080/JSON/spider/action/scan/?apikey=${KEY}&url=http://127.0.0.1:18080/&maxChildren=10&recurse=true" | python3 -c 'import sys,json; print(json.load(sys.stdin)["scan"])')
echo "spider scan id: ${SID}"
for i in $(seq 1 30); do S=$(curl -s "http://127.0.0.1:8080/JSON/spider/view/status/?apikey=${KEY}&scanId=${SID}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["status"])'); [ "$S" = "100" ] && break; sleep 1; done
curl -s "http://127.0.0.1:8080/JSON/spider/view/status/?apikey=${KEY}&scanId=${SID}"
curl -s "http://127.0.0.1:8080/JSON/spider/view/results/?apikey=${KEY}&scanId=${SID}"
The status reaches {"status":"100"} and the results list the discovered URLs. The passive scanner inspects every request and response that flows through ZAP. Watch its queue drain to zero, which means it has finished analysing the crawled pages:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
for i in $(seq 1 30); do R=$(curl -s "http://127.0.0.1:8080/JSON/pscan/view/recordsToScan/?apikey=${KEY}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["recordsToScan"])'); [ "$R" = "0" ] && break; sleep 1; done
curl -s "http://127.0.0.1:8080/JSON/pscan/view/recordsToScan/?apikey=${KEY}"

Step 6: Read the Alerts and Report
Once the spider and passive scan have run, read the findings ZAP surfaced. The summary view groups the alerts by risk level:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
curl -s "http://127.0.0.1:8080/JSON/alert/view/alertsSummary/?apikey=${KEY}&baseurl=http://127.0.0.1:18080"
Pull the individual alerts — each carries the rule name, risk and confidence, the affected URL and parameter, and remediation guidance:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
curl -s "http://127.0.0.1:8080/JSON/core/view/alerts/?apikey=${KEY}&baseurl=http://127.0.0.1:18080&start=0&count=5" | python3 -m json.tool

To launch a full active scan, which actively attacks the target with ZAP's scan rules, use /JSON/ascan/action/scan against an in-scope URL and poll /JSON/ascan/view/status; tune the strength and alert threshold through the scan policy first. The reports add-on generates HTML, JSON and Markdown reports over /JSON/reports/action/generate. Only run active scans against systems you are authorised to test.
Step 7: Automate and Integrate
The REST API exposes the full ZAP feature set, so you can wire ZAP into CI/CD for DAST. Define a context and scope, run the traditional and AJAX spiders, let the passive scanner flag issues as traffic flows, launch an active scan with a tuned policy, then pull the alerts and generate a report to fail a pipeline on findings above a threshold. You can also proxy a manual or automated test suite through ZAP — point your client at the proxy listener and ZAP records and passively scans everything it sees.
The ZAP automation framework lets you describe a whole scan as a single YAML plan and run it over the API. To expose the API beyond loopback, change the listening address in the ZAP network configuration and open the port in the instance security group yourself; the API stays private by default. Open whatever ports your scan targets require in the instance security group.
Step 8: The Data Volume
The ZAP home directory — the session database, contexts, the add-on marketplace cache and persisted scan results — lives on a dedicated EBS volume mounted at /var/lib/zaproxy. Confirm the mount with:
df -h /var/lib/zaproxy
Keeping the ZAP home on its own volume means your session state and scan history sit on durable storage you can resize or snapshot independently of the operating system disk. To grow the volume, expand the EBS volume in the AWS console, then grow the filesystem on the instance with sudo resize2fs on the underlying device.
Support
This image is published and supported by cloudimg. Support covers daemon configuration, the REST API, scan policies and attack strength, contexts and authentication handling, the AJAX spider, CI/CD integration for DAST, reporting, the automation framework, and upgrade planning. Contact cloudimg through the support channel listed on the AWS Marketplace listing.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.