Web Check 2.2.2
Overview
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. The cards cover 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.
This SecureAnyCloud image runs Web Check 2.2.2 on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud OWASP ZAP and Vulnerability Scanners listing on AWS Marketplace. You select it as the software version when you subscribe and launch.
What you get:
- 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/sac/data/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. In addition, 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, and nginx is also blocked from the metadata service. Requests for a host name the instance does not serve are dropped. Another website cannot call the analysis API with your browser's saved login. 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 analyze sites.
- A security group that allows TCP 22 (SSH), TCP 443 (the dashboard) and TCP 80 (redirects to HTTPS) from your own networks only
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 select your Region. Then choose Launch through EC2.
Choose m5.large, your key pair and a security group as described above. Web Check keeps no data, so the root volume is all it needs.
To launch with the AWS CLI instead, 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}]'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>Check the services
First boot takes a few seconds. Then confirm that the three units are active:
systemctl is-active web-check nginx web-check-firstbootactive active activeWeb 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.97Get your URL and password
Read the credentials file:
sudo cat /var/lib/sac/data/web-check-credentials.txtThe file holds the address Web Check is served on (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>Sign in from your browser
Open WEB_CHECK_URL in your browser. The certificate is self-signed and 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 analysis screen, with one box for the site to check and the list of every check it runs underneath.
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 in the report as each check 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. For the first target above, it lists five missing security headers.
Read the report cards
Scroll down for the detail behind the advisory.
- SSL Certificate 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.
- 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, and Threats checks reputation feeds. DNSSEC lists the zone's signing keys, DNS Server resolves the authoritative name servers, and Archive History summarizes 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, those cards say so, and the rest of the report is unaffected (Step 12).
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 the password never appears on a command line:
H=$(sudo grep '^WEB_CHECK_URL=' /var/lib/sac/data/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/sac/data/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 and allows 100 requests in ten minutes. One full analysis makes about 38 requests, so the limiter would lock you out of your own instance after two analyses. The login controls access instead. If you share the instance with a wider group, set the value to true and run sudo systemctl restart web-check.
Security checks
These checks confirm three things. Port 80 sends browsers to HTTPS. The health endpoint answers without a password, for load balancer checks. The dashboard and the API do not answer without a password.
H=$(sudo grep '^WEB_CHECK_URL=' /var/lib/sac/data/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: 401The password hash is readable only by root and nginx, and the credentials file only by root. The certificate is generated on this instance and names its own addresses:
sudo stat -c '%a %U:%G %n' /etc/nginx/web-check.htpasswd /var/lib/sac/data/web-check-credentials.txt
H=$(sudo grep '^WEB_CHECK_URL=' /var/lib/sac/data/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 subjectAltName640 root:www-data /etc/nginx/web-check.htpasswd 600 root:root /var/lib/sac/data/web-check-credentials.txt subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = sac 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:localhostnginx passes Web Check a fixed host name and the real client address from the connection, never values a client sends. It 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/ || trueproxy_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: 000000 means that nginx closed the connection without an answer.
Browsers resend a saved login on requests that other sites trigger. To guard against this, nginx refuses API calls that a browser marks as coming from another site. It also refuses any change request or WebSocket whose Origin is another site (HTTP 403). Finally, it removes the API's Access-Control-Allow-Origin: * header, so another site cannot read your results.
What the analysis server can reach
Web Check runs as the unprivileged webcheck user in a systemd sandbox. The kernel refuses connections from Web Check, and from 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 IPAddressDenyIPAddressDeny=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/32169.254.0.0/16 includes the EC2 instance metadata service (169.254.169.254), and fc00::/7 includes its IPv6 address. Because the block works 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/sac/data/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/sac/data/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." }Only that list refuses loopback (127.0.0.0/8). The network block does not, because nginx reaches Web Check over loopback.
Scanning private addresses. If you want Web Check to analyze 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. Then 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-checkYour own DNS name and certificate
Point a DNS record at the instance, then set the name Web Check is served on. The command below creates a new self-signed certificate for the name and updates nginx and the URL in the credentials file. The name persists 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 nginxPassword, 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 password (16 to 64 letters and digits) on stdin:
sudo web-check-set-password printf '%s\n' '<your-new-password>' | sudo web-check-set-password --stdinThe 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 key you want and add your value. 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 SecureAnyCloud publishes it.
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/sac-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/sac/data/web-check-credentials.txt (mode 0600)
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 license (a copy is at /usr/share/doc/web-check/LICENSE). There is no per-user or per-CPU license fee. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
Deploy on AWS
Launch Web Check 2.2.2 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com