Web Check 2.3.7
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: 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 guide covers launching and using the Web Check 2.3.7 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud OWASP ZAP and Vulnerability Scanners listing on AWS Marketplace. You pick 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 the first boot of your instance. 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. By design, Web Check fetches whatever URL it is given. 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. nginx is also blocked from the metadata service, and requests for a host name the instance does not serve are dropped. Another web site cannot call the analysis API with the saved login of your browser. nginx passes Web Check the real client address, not one a client claims.
- Bounded scans. Every check has a 60-second deadline, including the outbound request that the status and screenshot checks make. The kernel caps the memory of the service, so a hostile or broken site cannot hold your instance up.
- A fully patched Ubuntu 24.04 LTS base at build time, with unattended security upgrades switched on.
This version includes a security fix. Web Check 2.3.7 includes the upstream fix for GHSA-7fc3-pm8h-hh96 (fixed in 2.3.2). In earlier releases, the status and HSTS checks fetched the scanned site with no time limit. A site that answered slowly or streamed data forever could hold the request open and exhaust the memory of the server.
SecureAnyCloud tests this on every build. Six parallel scans of a site that streams forever, and an HSTS scan of a site that never answers, are all cut off within the deadline while the service stays up. The 2.3 releases also update the bundled dependencies: the production dependency tree goes from 58 known npm advisories in 2.2.2 to 3. They add new checks: Trackers and Third Parties, Mixed Content and SRI, CAA records, data breaches (Have I Been Pwned) and social presence.
What is on the image: Web Check 2.3.7 built from the official source (tag 2.3.7) at /opt/web-check/src, Node.js 22 from the NodeSource repository, Chrome for Testing 148 (the build that Puppeteer in Web Check 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
Before you launch, make sure you have:
- 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.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Web Check 2.3.7 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.3.7 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot takes a few seconds. When it finishes, 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:22 users:(("sshd"),("systemd")) LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) LISTEN 127.0.0.1:3000 users:(("node")) LISTEN [::]:22 users:(("sshd"),("systemd")) LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:443 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.3.7 v22.23.3 linux-148.0.7778.97Get your URL and password
sudo cat /var/lib/sac/data/web-check-credentials.txtThe file holds the address that serves Web Check (the public DNS name of your instance 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).
When your browser 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. The arrow beside Analyze lets you run one category of checks only: Security, SEO, Server, Domain, Email, Performance or Privacy.
Run your first analysis
Type a domain into the box and choose Analyze. The domain example.com is a good first target. Web Check runs over 40 lookups in parallel and fills in the report as each one 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 missing security headers and old TLS versions that the server still accepts.
Read the report cards
Scroll down for the detail behind the advisory. Each card grades what it found with a green tick, a warning or a cross. These cards are the main ones to read:
- SSL Certificate shows the subject, issuer, key, expiry and renewal.
- TLS Connection reports the negotiated protocol, the cipher suite and which TLS versions the server still accepts.
- Mixed Content and SRI flags insecure resources on an HTTPS page.
- CAA Records shows which certificate authorities may issue for the domain.
- Data Breaches checks Have I Been Pwned.
- Trace Route shows the network path from your instance to the site.
- Firewall identifies a web application firewall in front of the site.
Further down, Archive History summarises the history of the site in the Internet Archive. Server Status splits the response time into DNS, connect, TLS, server and download. Block Detection shows whether common filtering DNS services block the domain. Trackers and Third Parties loads the page in the headless Chrome and lists the trackers it contacts.
A few cards use third-party services that need a free API key of your own: Shodan, Google Safe Browsing threat detail, global ranking, subdomain search and page quality. Without a key, they 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}' wc_api 'status?url=https://example.com' | jq '{isUp, responseCode}'{ "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" } { "isUp": true, "responseCode": 200 }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 request rate limiter of the upstream project is off on this image (API_ENABLE_RATE_LIMIT=false in /etc/web-check/web-check.env). It was written for the free public instance of the author 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 option to true and run sudo systemctl restart web-check.
Security checks
Port 80 sends browsers to HTTPS. The nginx health endpoint /health answers without a password, for load balancer checks. The dashboard, the API and the /healthz status page of Web Check do not:
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"
curl -sk -o /dev/null -w 'healthz without a password: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/healthz"port 80: 301 -> https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ ok dashboard without a password: 401 API without a password: 401 healthz without a password: 401Only root and nginx can read the password hash, and only root can read the credentials file. 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/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 that 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: 000The code 000 means nginx closed the connection without an answer.
Browsers resend a saved login on requests that other sites trigger. For that reason, 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), and it removes the Access-Control-Allow-Origin: * header of the API, 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 its connections to the addresses in IPAddressDeny, and so it refuses those of the headless Chrome that Web Check starts for the screenshot and trackers checks. nginx is blocked from the metadata service too. The memory of the service is capped at 75% of the RAM of the instance:
systemctl show web-check -p User -p ProtectSystem -p NoNewPrivileges -p IPAddressDeny
systemctl show nginx -p IPAddressDeny grep -E '^MemoryMax=' /etc/systemd/system/web-check.serviceIPAddressDeny=169.254.0.0/16 10.0.0.0/8 fe80::/10 fc00::/7 100.64.0.0/10 192.168.0.0/16 172.16.0.0/12 168.63.129.16/32 User=webcheck ProtectSystem=strict NoNewPrivileges=yes IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32 MemoryMax=75%The range 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.
The list of hosts that Web Check 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.
Each check stops after PUBLIC_API_TIMEOUT_LIMIT milliseconds (60 seconds). The status and screenshot checks apply the same deadline to their own request to the site, so a site that never finishes answering is cut off instead of holding the server:
grep '^PUBLIC_API_TIMEOUT_LIMIT=' /etc/web-check/web-check.envPUBLIC_API_TIMEOUT_LIMIT=60000Scanning private addresses. To let Web Check 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. 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 that serves Web Check. This creates 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 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 one you want and 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 data breaches and social presence checks send the domain you scan to Have I Been Pwned and to public social sites. The other third-party checks run only when you add their key.
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 that your browsers trust (Step 11).
- Keep the egress block unless you need to scan private hosts, and then lift it 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 the hardening, first-boot setup and support from SecureAnyCloud.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
Deploy on AWS
Launch Web Check 2.3.7 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