Web Check 2.2.2 on AWS User Guide
Overview
This guide covers launching and using the Web Check 2.2.2 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg OWASP ZAP and Vulnerability Scanners listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Web Check is an open source OSINT and security analysis dashboard for websites. You enter a domain or URL and it gathers what is publicly observable about that site into one report of around 35 cards: DNS and TXT records, the SSL certificate and TLS connection, HTTP security headers, HSTS, open ports, DNSSEC, redirects, cookies, the detected technology stack, WHOIS data, mail configuration, crawl rules, a trace route, a page screenshot and more. Every card is also a JSON API endpoint, so you can drive it from scripts.
The cloudimg image adds:
- HTTPS from the first boot. nginx serves Web Check on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- A login unique to your instance, for the dashboard and every API route. Web Check has no login of its own, so nginx asks for the user
adminand a password generated on first boot. The password is written to/var/lib/cloudimg/web-check-credentials.txt(root only) and stored for nginx as a bcrypt hash; it is never put on a command line. No password is baked into the image, and nginx refuses to start until first boot has created one. - An egress wall around the analysis server. Web Check fetches whatever URL it is given, by design. The
web-checkservice, and the headless Chrome it starts for screenshots, cannot connect to the EC2 instance metadata service, to any link local address (169.254.0.0/16) or to the private ranges 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 and 100.64.0.0/10 (and IPv6 fc00::/7 and fe80::/10), whatever host name or redirect leads there. The kernel enforces this through systemd, not the application. On top of that, Web Check refuses to start an analysis of a literal private address, loopback orlocalhost. - Loopback only. The Web Check Node.js server listens on
127.0.0.1:3000behind nginx and runs as its own unprivileged user in a systemd sandbox. The only open ports are 22, 80 and 443. - Hardened defaults. The AMI requires IMDSv2; nginx is also blocked from the metadata service; requests for a host name the instance does not serve are dropped; another web site cannot call the analysis API with your browser's saved login; and nginx passes Web Check the real client address, not one a client claims.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Web Check 2.2.2 built from the official source (tag 2.2.2) at /opt/web-check/src, Node.js 22 from the NodeSource repository, Chrome for Testing 148 (the build Web Check's Puppeteer pins) at /opt/web-check/puppeteer, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. No third party API keys are included.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet. Web Check needs outbound HTTP, HTTPS and DNS to analyse sites.
- A security group that allows TCP 22 (SSH), TCP 443 (the dashboard) and TCP 80 (redirects to HTTPS) from your own networks only.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Web Check 2.2.2 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above. Web Check keeps no data, so the root volume is all it needs.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=web-check}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Web Check 2.2.2 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot takes a few seconds. Then the three units are active:
systemctl is-active web-check nginx web-check-firstboot
active
active
active
Web Check (a Node.js process) listens on loopback only; nginx serves ports 80 and 443:
sudo ss -tlnp | awk 'NR==1 || /node|nginx|sshd/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer
LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:22 users:(("sshd"),("systemd"))
LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN 127.0.0.1:3000 users:(("node"))
LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:22 users:(("sshd"),("systemd"))
LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx"))
jq -r '.name + " " + .version' /opt/web-check/src/package.json
node --version
ls /opt/web-check/puppeteer/chrome/
web-check 2.2.2
v22.23.3
linux-148.0.7778.97
Step 4: Get your URL and password
sudo cat /var/lib/cloudimg/web-check-credentials.txt
The file holds the address Web Check is served as (your instance's public DNS name by default), the user and the password generated on this instance:
WEB_CHECK_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
WEB_CHECK_USERNAME=admin
WEB_CHECK_PASSWORD=<24 letters and digits>
Step 5: Sign in from your browser
Open WEB_CHECK_URL in your browser. The certificate is self signed (generated on your instance), so accept the browser warning once, or install your own certificate (Step 11). Your browser then asks for a user name and password: enter admin and the password from Step 4. Web Check opens on its analyse screen, with one box for the site to check and the list of every check it runs underneath.

Step 6: Run your first analysis
Type a domain into the box (example.com is a good first target) and choose Analyze!. Web Check runs about 38 checks in parallel and fills the report in as each answers, usually within ten to thirty seconds.
The report opens with an Advisory panel that groups the findings by severity: Issues, Warnings, Informational and Passes. It is the quickest way to see what a site gets wrong; here it lists five missing security headers.

Step 7: Read the report cards
Scroll down for the detail behind the advisory. The SSL Certificate card shows the subject, issuer, curve, expiry, renewal date, serial number and fingerprint. Headers lists the live response headers exactly as the site returned them, DNS Records shows the A, AAAA, MX, NS, SOA and TXT records, Tech Stack names the detected technologies, HTTP Security grades the site against the standard protective headers, and TLS Connection reports the negotiated protocol and cipher suite.

Further down, Trace Route shows the network path from your instance to the site, Firewall identifies a web application firewall in front of it, Threats checks reputation feeds, DNSSEC lists the zone's signing keys, DNS Server resolves the authoritative name servers and Archive History summarises the site's history in the Internet Archive.

A few cards (Shodan, Google Safe Browsing threat detail, global ranking, subdomain search and page quality) use third party services that need a free API key of your own. Without a key they say so and the rest of the report is unaffected (Step 12).
Step 8: Use the analysis API
Every check is a JSON endpoint under /api/<check>?url=<site>, protected by the same login. This helper reads the password from the credentials file and hands it to curl on stdin, so it never appears on a command line:
H=$(sudo grep '^WEB_CHECK_URL=' /var/lib/cloudimg/web-check-credentials.txt | sed -E 's#^WEB_CHECK_URL=https://([^/]+)/.*#\1#')
wc_api() { sudo sed -n 's/^WEB_CHECK_PASSWORD=\(.*\)$/user = "admin:\1"/p' /var/lib/cloudimg/web-check-credentials.txt | curl -sk -K - --resolve "$H:443:127.0.0.1" "https://$H/api/$1"; }
wc_api 'dns?url=https://example.com' | jq '{A, NS}'
wc_api 'ssl?url=https://example.com' | jq '{subject, issuer, valid_to}'
{
"A": [
"104.20.23.154",
"172.66.147.243"
],
"NS": [
"hera.ns.cloudflare.com",
"elliott.ns.cloudflare.com"
]
}
{
"subject": {
"CN": "example.com"
},
"issuer": {
"C": "US",
"O": "SSL Corporation",
"CN": "Cloudflare TLS Issuing ECC CA 3"
},
"valid_to": "Dec 25 22:56:35 2026 GMT"
}
Request /api?url=<site> with no check name to run every check at once and get the whole report as one JSON document. From another machine, use https://<your host>/api/... with curl -u admin (curl then prompts for the password).
The upstream project's request rate limiter is off on this image (API_ENABLE_RATE_LIMIT=false in /etc/web-check/web-check.env). It was written for the author's free public instance, allows 100 requests in ten minutes, and one full analysis makes about 38 requests, so it would lock you out of your own instance after two analyses. Access is controlled by the login instead. If you share the instance with a wider group, set it to true and run sudo systemctl restart web-check.
Step 9: Security checks
Port 80 sends browsers to HTTPS, the health endpoint answers without a password (for load balancer checks), and the dashboard and the API do not:
H=$(sudo grep '^WEB_CHECK_URL=' /var/lib/cloudimg/web-check-credentials.txt | sed -E 's#^WEB_CHECK_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/health"
curl -sk -o /dev/null -w 'dashboard without a password: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'API without a password: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/dns?url=https://example.com"
port 80: 301 -> https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
ok
dashboard without a password: 401
API without a password: 401
The password hash is readable only by root and nginx, and the credentials file only by root. The certificate was generated on this instance and names its own addresses:
sudo stat -c '%a %U:%G %n' /etc/nginx/web-check.htpasswd /var/lib/cloudimg/web-check-credentials.txt
H=$(sudo grep '^WEB_CHECK_URL=' /var/lib/cloudimg/web-check-credentials.txt | sed -E 's#^WEB_CHECK_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
640 root:www-data /etc/nginx/web-check.htpasswd
600 root:root /var/lib/cloudimg/web-check-credentials.txt
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
nginx passes Web Check a fixed host name and the real client address from the connection, never values a client sends, and closes the connection for any host name the instance does not serve:
grep -E 'proxy_set_header (Host|X-Forwarded-For|X-Real-IP)' /etc/nginx/web-check-proxy.conf
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: example.com' http://127.0.0.1/ || true
proxy_set_header Host 127.0.0.1:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
unknown host name: 000
(000 means nginx closed the connection without an answer.) Browsers resend a saved login on requests that other sites trigger, so nginx refuses API calls that a browser marks as coming from another site, refuses any change request or WebSocket whose Origin is another site (HTTP 403), and removes the API's Access-Control-Allow-Origin: * header, so another site cannot read your results.
Step 10: What the analysis server can reach
Web Check runs as the unprivileged webcheck user in a systemd sandbox. The kernel refuses its connections, and those of the headless Chrome it starts, to the addresses in IPAddressDeny; nginx is blocked from the metadata service too:
systemctl show web-check -p User -p ProtectSystem -p NoNewPrivileges -p IPAddressDeny
systemctl show nginx -p IPAddressDeny
IPAddressDeny=169.254.0.0/16 100.64.0.0/10 fc00::/7 192.168.0.0/16 168.63.129.16/32 10.0.0.0/8 fe80::/10 172.16.0.0/12
User=webcheck
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32
169.254.0.0/16 includes the EC2 instance metadata service (169.254.169.254) and fc00::/7 includes its IPv6 address. Because the block is at the network layer, it also stops a public host name that resolves to a private address, and a site that redirects to one.
Web Check's own list of hosts it refuses to scan is a second, simpler layer. It matches only the address or name you type:
H=$(sudo grep '^WEB_CHECK_URL=' /var/lib/cloudimg/web-check-credentials.txt | sed -E 's#^WEB_CHECK_URL=https://([^/]+)/.*#\1#')
wc_api() { sudo sed -n 's/^WEB_CHECK_PASSWORD=\(.*\)$/user = "admin:\1"/p' /var/lib/cloudimg/web-check-credentials.txt | curl -sk -K - --resolve "$H:443:127.0.0.1" "https://$H/api/$1"; }
grep '^API_BLOCKED_HOSTS=' /etc/web-check/web-check.env
wc_api 'headers?url=http://169.254.169.254/latest/meta-data/' | jq .
API_BLOCKED_HOSTS=169.254.0.0/16,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10,localhost
{
"skipped": "Scanning this host is not permitted on this instance."
}
Loopback (127.0.0.0/8) is refused by that list only, not by the network block, because nginx reaches Web Check over loopback.
Scanning private addresses. If you want Web Check to analyse hosts inside your own VPC, remove those ranges deliberately and only on an instance that you keep private. Override the network block with a drop-in that resets the list to the metadata addresses only, and remove the private ranges from API_BLOCKED_HOSTS in /etc/web-check/web-check.env:
sudo mkdir -p /etc/systemd/system/web-check.service.d
printf '[Service]\nIPAddressDeny=\nIPAddressDeny=169.254.0.0/16 168.63.129.16/32 fd00:ec2::254/128\n' \
| sudo tee /etc/systemd/system/web-check.service.d/60-allow-private.conf
sudo systemctl daemon-reload && sudo systemctl restart web-check
Step 11: Your own DNS name and certificate
Point a DNS record at the instance, then set the name Web Check is served as. This mints a new self signed certificate for the name, updates nginx and the URL in the credentials file, and keeps the name across reboots:
sudo web-check-set-hostname <your-domain>
To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running web-check-set-hostname again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/web-check.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/web-check.key
sudo nginx -t && sudo systemctl reload nginx
Step 12: Password, API keys and upgrades
web-check-set-password sets a new password for admin, records it in the credentials file and reloads nginx. Run it without arguments for a new random password, or pass your own (16 to 64 letters and digits) on stdin:
sudo web-check-set-password
printf '%s\n' '<your-new-password>' | sudo web-check-set-password --stdin
The cards that use third party services read their keys from /etc/web-check/web-check.env. The key names are listed there, commented out:
grep -E '^#\s+[A-Z_]+=$' /etc/web-check/web-check.env
# GOOGLE_CLOUD_API_KEY=
# SHODAN_API_KEY=
# SECURITY_TRAILS_API_KEY=
# URL_SCAN_API_KEY=
# TRANCO_USERNAME=
# TRANCO_API_KEY=
# CLOUDMERSIVE_API_KEY=
# WHO_API_KEY=
# TORRENT_IP_API_KEY=
Uncomment the one you want, add your key, then run sudo systemctl restart web-check.
Web Check keeps no database and no report history, so there is nothing to back up. The image ships the version named in the listing; to move to a newer release, launch the newer version from the listing when cloudimg publishes it.
Step 13: Server components
| Component | Path |
|---|---|
| Web Check source and build | /opt/web-check/src |
| Headless Chrome | /opt/web-check/puppeteer |
| Runtime settings | /etc/web-check/web-check.env |
| Service unit and egress drop-ins | /etc/systemd/system/web-check.service, /etc/systemd/system/web-check.service.d/ |
| nginx site | /etc/nginx/sites-available/cloudimg-web-check |
| Password hash (bcrypt) | /etc/nginx/web-check.htpasswd (root:www-data, 0640) |
| Certificate and key | /etc/nginx/ssl/ |
| First boot script | /usr/local/sbin/web-check-firstboot.sh |
| Host name and password helpers | /usr/local/sbin/web-check-set-hostname, /usr/local/sbin/web-check-set-password |
| Credentials | /var/lib/cloudimg/web-check-credentials.txt (mode 0600) |
Step 14: Security recommendations
- Allow ports 22, 80 and 443 in the security group from your own networks only.
- Keep the password secret, and rotate it if it may have been shared (Step 12).
- Use your own DNS name and a certificate your browsers trust (Step 11).
- Keep the egress block unless you need to scan private hosts, and then only on a private instance (Step 10).
- Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else, including Node.js updates from the NodeSource repository.
Support and licensing
Web Check is open source under the MIT licence (a copy is at /usr/share/doc/web-check/LICENSE); there is no per user or per CPU licence fee. The AMI price covers cloudimg's hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA