How to Check Website Security: A Business Checklist

Your customer just emailed asking why Chrome flagged your site as "Not Secure." Your Stripe webhook is failing because someone rotated a key and didn't tell you. The WordPress plugin you installed 14 months ago has an unpatched RCE from July. If any of that sounds familiar, this is the audit you should have run last quarter.
This is the checklist I actually use when a small business owner asks me to look at their site. It's ordered by blast radius: the things at the top will end your business if they go wrong. The things at the bottom are hygiene. Do them in order, automate what you can, and don't skip the boring parts — the boring parts are where breaches live.
Start with what's public: SSL, DNS, and what attackers see first
Before you touch anything inside your stack, look at what an attacker sees from the outside. This takes 15 minutes and catches roughly half of real-world SMB security problems.
Run your domain through three checks:
# SSL/TLS configuration grade
# Use https://www.ssllabs.com/ssltest/ — target grade A or A+
# Check certificate expiry from the command line
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
| openssl x509 -noout -dates
# DNS records — look for stale CNAMEs pointing to services you no longer use
dig example.com ANY +noall +answer
dig www.example.com CNAME +short
What you're looking for:
- SSL Labs grade below A. Anything B or lower means you're serving weak ciphers or an old TLS version. Fix immediately — most modern hosts do this in the control panel with one toggle.
- Certificate expiring in under 30 days with no auto-renewal configured. If you're on Let's Encrypt via a managed host, this should be automatic. If it's not, you have a countdown timer to an outage.
- Dangling CNAMEs pointing to Heroku apps, S3 buckets, or SaaS tools you canceled. This is the #1 source of subdomain takeovers. If
blog.yourcompany.comstill points to a Ghost instance you shut down, someone can claim that Ghost subdomain and serve content from your domain.
Also check your domain's SPF, DKIM, and DMARC records. If DMARC is missing or set to p=none, anyone can spoof email from your domain. That's how invoice fraud starts.
dig TXT example.com +short | grep spf
dig TXT _dmarc.example.com +short
Priority: fix same-day if TLS grade is below A, cert expires under 30 days, or DMARC is absent.
Patch your CMS, plugins, and dependencies — this is where you actually get breached
The overwhelming majority of small-business site compromises aren't sophisticated. They're an unpatched WordPress plugin, an out-of-date Magento install, or an npm dependency with a known CVE. The fix is boring: keep things current, and know what "current" means.
Run an inventory. You cannot patch what you don't know exists.
For a WordPress site:
# Using WP-CLI on the server
wp core version
wp plugin list --status=active --format=table
wp plugin list --update=available
wp theme list --update=available
For a Node/React app:
npm audit --production
npm outdated
For Python:
pip list --outdated
pip-audit # scans installed packages against the PyPI advisory database
Rules I follow with clients:
| Component | Update cadence | Action if a CVE drops |
|---|---|---|
| CMS core (WordPress, Ghost, etc.) | Auto-update minor versions | Patch within 24h for critical |
| Active plugins/themes | Weekly review | Patch within 48h; remove if unmaintained >12 months |
| App dependencies | Monthly npm audit / pip-audit |
Critical CVEs patched same week |
| Server OS packages | unattended-upgrades for security patches |
Reboot within maintenance window |
The rule most people miss: delete plugins you don't use. Deactivated plugins are still on disk. Their code still gets loaded in some attack paths. If you're not using it, remove it.
Priority: fix within 48 hours for any dependency with a known critical CVE. Automate the scanning weekly.
Scan for malware and unexpected changes
If your site was already compromised, patching won't help — the attacker has a shell somewhere. You need to know whether that shell exists before you harden anything else.
Free scanners that work:
- Sucuri SiteCheck (free web scan) — quick external check for known malware signatures
- Google Safe Browsing status — check at
https://transparencyreport.google.com/safe-browsing/search - VirusTotal URL scan — cross-references multiple engines
These catch known-bad signatures but miss custom shells. For the real check, you need file integrity monitoring on the server.
# One-shot: hash every file in your web root, compare later
find /var/www/html -type f -exec sha256sum {} \; > /root/baseline-$(date +%F).txt
# Compare a week later
find /var/www/html -type f -exec sha256sum {} \; > /root/current.txt
diff /root/baseline-*.txt /root/current.txt
For WordPress specifically:
wp core verify-checksums
wp plugin verify-checksums --all
Any output from those commands means files have been modified from what the official release contains. On a healthy site, both commands produce zero output.
What to look for in your web root:
- Recently modified PHP files in
/wp-content/uploads/(uploads should be images and PDFs, not code) - Base64-encoded strings inside PHP files (
eval(base64_decode(...))is a red flag) - Cron jobs you didn't create (
crontab -land check/etc/cron.d/) - New admin users you don't recognize (
wp user list --role=administrator)
Priority: if a scan comes back positive, take the site offline immediately and restore from a known-clean backup. Trying to clean a compromised site in place while it's live almost always fails.
Test your backups before you need them
Backups you haven't restored aren't backups — they're wishful thinking. I've seen four SMB clients in the last two years with "daily backups" that turned out to be either corrupt, incomplete (database only, no uploads), or stored on the same server that got wiped.
The rule is 3-2-1: three copies, two different media, one offsite. For a typical SMB site, that looks like:
- Live site (copy 1)
- Nightly backup on the hosting provider (copy 2, same infrastructure)
- Weekly backup pushed to S3, Backblaze B2, or Google Cloud Storage in a different region (copy 3, offsite)
The offsite copy must use a separate set of credentials. If your hosting account gets compromised, the attacker should not be able to delete your backups.
A restore drill you should run quarterly:
# Spin up a scratch environment
# Pull last night's backup
# Restore database + files
# Load the homepage, log in, place a test order
# Time the whole thing — this is your RTO
# For a small WooCommerce site, a full restore should be under 60 minutes.
# If it takes 6 hours, you have a business continuity problem, not a backup problem.
Two numbers every business owner should be able to answer:
- RPO (Recovery Point Objective): how much data can you lose? If backups run at 2am, worst case is 24 hours of orders/leads gone.
- RTO (Recovery Time Objective): how long until you're back online? For a store doing meaningful daily revenue, anything over 4 hours is expensive.
Priority: run a full restore drill this month if you've never done one. Automate the offsite push weekly at minimum.
Lock down access: passwords, MFA, and the principle of least privilege
Most breaches I clean up start with a credential, not a code exploit. Someone reused a password, an ex-contractor still had admin, or an API key was in a public GitHub repo.
The access audit, in order:
1. List every human who can log in.
wp user list --format=table
# For your host, cloud console, DNS provider, email, Stripe, everything.
For each account, ask: does this person still work here? Do they need this level of access? If either answer is no, downgrade or remove today.
2. Enforce MFA on every account that supports it. No exceptions for "the owner" or "the developer who only logs in twice a year." Those are the accounts attackers target.
3. Rotate any credential that touched a departed contractor. Assume they still have it. This includes: hosting login, database password, SMTP credentials, Stripe restricted keys, third-party API keys.
4. Audit your GitHub/GitLab for leaked secrets.
# gitleaks scans a repo for credentials
gitleaks detect --source . --verbose
# Or trufflehog
trufflehog git file://. --only-verified
If gitleaks finds something, rotate the credential first, then rewrite git history second. The credential is compromised the moment it's public — history rewrites don't unpublish it.
5. Use scoped API keys. Your Stripe integration on the marketing site should use a restricted key with charge:write and nothing else. If that key leaks, the blast radius is bounded.
Priority: enable MFA everywhere today. Audit user list and remove stale accounts this week.
Add security headers and form protection
Security headers are the cheapest hardening you can do — a few lines in your nginx or Apache config, or a plugin config, and you close off entire classes of attacks. Test what you're serving now:
curl -sI https://example.com | grep -iE "strict-transport|content-security|x-frame|x-content|referrer|permissions"
Or use https://securityheaders.com/ for a graded report.
The baseline set for a small business site:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# CSP is powerful but requires per-site tuning — start in report-only mode
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' https://js.stripe.com;" always;
A note on CSP: don't paste a strict policy blindly. It will break your analytics, your embedded videos, and your payment iframe. Start with Content-Security-Policy-Report-Only, watch the reports for a week, then enforce.
For forms — the other public attack surface — three checks:
- Rate-limit submissions. A contact form with no rate limit will get 400 spam submissions the first week it's crawled. Use Cloudflare Turnstile, hCaptcha, or a honeypot field. Skip Google reCAPTCHA v3 if you care about privacy compliance in the EU/UK.
- Validate on the server, not just the browser. JavaScript validation is UX. Attackers post directly to your endpoint.
- Never echo form input back into a page without escaping. This is XSS 101 and still ships in production regularly.
Priority: security headers this week. Form rate limiting immediately if you're getting spam.
Automate what you just did — because you won't do it manually
Everything above is a one-time audit. The problem is that a site is secure on Monday and insecure on Friday because a plugin auto-updated and pulled in a bad version, or a new employee got admin access, or your cert renewal hook silently failed.
The checks that must run continuously:
| Check | Cadence | Fails how |
|---|---|---|
| SSL cert expiry | Daily | Alert 30/14/7 days out |
| Uptime + status code | Every 60s | Alert on 5xx or timeout |
| Malware / file integrity | Daily | Diff against known-good manifest |
| Dependency CVEs | Weekly | Automated PR to update |
| Backup completion | Every backup run | Alert if size drops >20% |
| Backup restore test | Quarterly | Human-in-the-loop |
| Security header drift | Weekly | Compare to expected config |
| Admin user list diff | Weekly | Alert on new admin accounts |
| DNS record diff | Daily | Alert on any change |
You can wire most of this together with UptimeRobot or BetterStack for uptime, Dependabot for dependency PRs, and a small cron job that emails you the diff of wp user list and dig output week over week. The scripts are 20 lines each. The value is that you find out about drift on Tuesday morning instead of when a customer emails you about it on Saturday night.
How BizFlowAI approaches this
We build monitoring workflows for SMB clients where these checks aren't a quarterly audit — they're an always-on operations layer. A typical setup: nightly cron pulls the current state (installed plugin versions, admin user list, DNS records, security headers, cert expiry, backup manifest size), diffs against last night's snapshot, and posts anything meaningful to a Slack channel or email digest. Critical events (a new admin account, a failed backup, a cert expiring in under 14 days) page immediately. Everything else lands in a weekly summary the owner reads on Monday morning.
The reason this works isn't the tooling — it's that "check your site is secure" stops being a task someone has to remember. It becomes a system that surfaces the three things this week that actually need a human decision, and stays quiet about the 40 that didn't change. That's the same pattern we apply to invoice reconciliation, lead triage, and support inbox routing: automate the observation, keep the human in the loop on the decision.
Work with BizFlowAI
If you'd rather have this built for you, that's what we do: production AI automation for solo founders and small teams — agents, integrations, and document pipelines that actually ship.
Book a free discovery call — 30 minutes, we map the highest-ROI automation in your workflow. No pitch deck, just engineering.
More guides like this on the BizFlowAI blog.
Frequently asked questions
How do I check if my website's SSL certificate is secure?
Run your domain through SSL Labs (ssllabs.com/ssltest) and aim for an A or A+ grade; anything B or lower means weak ciphers or outdated TLS. You can also check expiry from the command line with openssl s_client connected to your domain on port 443. Certificates expiring in under 30 days without auto-renewal need immediate attention. Most managed hosts fix TLS grade with a single toggle in the control panel.
What is a dangling CNAME and why is it dangerous?
A dangling CNAME is a DNS record on your domain pointing to a third-party service (like Heroku, S3, or Ghost) that you no longer use. Attackers can claim the abandoned service name and serve their own content from your subdomain, enabling phishing and cookie theft. It's the leading cause of subdomain takeovers. Audit your DNS records regularly with dig and remove CNAMEs for any canceled SaaS tools.
How often should I update WordPress plugins and dependencies?
Enable auto-updates for WordPress core minor versions and review active plugins weekly. Patch any critical CVE within 48 hours, and remove plugins unmaintained for more than 12 months. For app dependencies, run npm audit or pip-audit monthly and patch critical vulnerabilities the same week. Delete deactivated plugins entirely, since their code can still be loaded in some attack paths.
What is the 3-2-1 backup rule for websites?
The 3-2-1 rule means keeping three copies of your data on two different types of media with one copy offsite. For a typical small business site, that's the live site, a nightly backup on your host, and a weekly backup pushed to a separate cloud provider like S3 or Backblaze B2 in a different region. The offsite copy must use separate credentials so a hosting compromise can't delete your backups. Test a full restore quarterly to verify it actually works.
How do I detect if my website has been hacked?
Start with free external scanners like Sucuri SiteCheck, Google Safe Browsing status, and VirusTotal to catch known malware signatures. On the server, run file integrity checks by hashing your web root with sha256sum and comparing over time, or use wp core verify-checksums for WordPress. Look for recently modified PHP files in uploads directories, base64-encoded eval statements, unfamiliar cron jobs, and admin users you didn't create. If anything positive turns up, take the site offline and restore from a known-clean backup rather than cleaning in place.