From scan to remediation: a walkthrough on a real domain
Reports are easy to write in the abstract. So we picked a real production domain — our own
parent site unveiltech.com — pointed UnveilScan at it, and walked through
every Extended finding in order, applying the fix live. Total elapsed time: about two
hours. Score moved from 64 / D to 92 / A.
Below is the play-by-play. Every snippet you see was actually pasted into the production Apache config (yes, it's a 50-vhost Apache 2.4.38 / Debian Buster server — typical mid-size SaaS infrastructure, not a fresh slate).
Initial state: 64 / D
| Category | Subscore |
|---|---|
| DNS | 85 |
| TLS | 40 |
| WEB | 50 |
| 80 |
Pattern: classic Apache server that nobody touched in two years. TLS and WEB drag the weighted average. Let's go.
Finding 1 — CRITICAL TLS 1.0 + 1.1 enabled
Same one we wrote about in the Google article. Apache 2.4.38 ships with permissive defaults that allow legacy versions.
# /etc/apache2/mods-available/ssl.conf
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLHonorCipherOrder off
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES:!DES:!CAMELLIA
$ apachectl configtest
Syntax OK
$ systemctl reload apache2
+18 global points (TLS 40 → 70).
Finding 2 — HIGH Mixed content on HTTPS pages
Found 87 files referencing http:// resources. The CSP wouldn't have caught
this on its own; the dedicated mixed_content checker flagged it.
Two-line bash to upgrade-and-replace, run from the document root:
grep -rl "http://www.unveiltech.com" . | xargs sed -i 's|http://www.unveiltech.com|https://www.unveiltech.com|g'
Plus a header to catch the long tail at the browser level:
# /etc/apache2/conf-enabled/security.conf
Header always set Content-Security-Policy "upgrade-insecure-requests"
+9 global points (WEB 50 → 80).
Finding 3 — HIGH Apache version disclosed in headers
Server: Apache/2.4.38 (Debian). Tells attackers which Apache CVEs to try.
Especially fun on Buster, which has a long list.
# /etc/apache2/conf-enabled/security.conf
ServerTokens Prod
ServerSignature Off
Now responds with Server: Apache only. The CVE the scanner cross-referenced
via OSV.dev (we use the techcve checker) drops because the version is no
longer leaked.
+9 global points.
Finding 4 — MEDIUM HSTS missing
# Inside the <VirtualHost *:443>, after the SSL directives:
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
max-age is in seconds: 31,536,000 = 1 year. includeSubDomains
extends the policy to every subdomain, which is what you want unless you have a specific
reason to exclude one.
Don't add preload until you're sure: getting on the preload list takes 6 weeks
and getting off takes a year.
+4.5 global points.
Finding 5 — MEDIUM CSP missing
Server didn't ship a Content-Security-Policy header at all. We started with a permissive policy (don't break anything), then tightened.
# Day 1 — observe and report
Header always set Content-Security-Policy "default-src 'self' https:; \
script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; \
img-src 'self' data: https:; \
style-src 'self' 'unsafe-inline'; \
frame-ancestors 'none'; \
base-uri 'self'; \
upgrade-insecure-requests; \
report-uri /csp-report"
Then we set up a /csp-report handler that logs violations, watched the logs
for a day, refined. Final policy ditches 'unsafe-inline' on script-src in
favour of SHA-256 hashes for the few inline scripts the page actually contains
(full CSP article for that journey).
+4.5 global points.
Findings 6-9 — MEDIUM+LOW miscellaneous headers
One block fixes four findings:
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "no-referrer"
Header always set Permissions-Policy "interest-cohort=()"
Header always set Cross-Origin-Opener-Policy "same-origin"
These are cookie-cutter — apply to every server you operate, no exceptions. X-Frame-Options
DENY is redundant with frame-ancestors 'none' in CSP but covers older
browsers. interest-cohort=() opts out of FLoC.
+6 global points.
Finding 10 — MEDIUM No security.txt
RFC 9116 file. Two minutes of work:
$ cat > /var/www/.well-known/security.txt <<'EOF'
Contact: mailto:security@unveiltech.com
Expires: 2027-04-29T00:00:00Z
Preferred-Languages: en, fr
Canonical: https://www.unveiltech.com/.well-known/security.txt
Policy: https://www.unveiltech.com/security-policy
EOF
Don't forget the MIME type — Apache's default text/plain for .txt
matches the RFC. Some scanners (including ours) flag it as malformed if the response
Content-Type is text/html because of a SPA fallback. Check yours.
+1.5 global points.
Findings 11-12 — MEDIUM DMARC weak / SPF over 10 lookups
DMARC was at p=none; pct=100. We bumped to p=quarantine after
a week of monitoring (RUA inbox showed everything aligning).
SPF was at 12 DNS lookups (RFC 7208 §4.6.4 hard limit is 10). We replaced two
include: directives that we no longer used and dropped to 7.
# Final SPF
"v=spf1 ip4:5.196.10.27 include:_spf.google.com include:sendgrid.net -all"
+6 global points.
Finding 13 — LOW Cookies missing flags
The Apache config sets cookies via mod_session. We just added flags:
Header edit Set-Cookie ^(.*)$ "$1; Secure; HttpOnly; SameSite=Lax"
(In a fresh project, set these from your application code — but for monkey-patching an old Apache, header edit works.)
+2 global points.
Final state: 92 / A
| Category | Before | After | Δ |
|---|---|---|---|
| DNS | 85 | 90 | +5 |
| TLS | 40 | 100 | +60 |
| WEB | 50 | 90 | +40 |
| 80 | 95 | +15 | |
| Global | 64 / D | 92 / A | +28 |
What's left? A handful of LOW findings (DKIM key rotation overdue, NS not geographically diverse) we'll get to in the next maintenance window. The score won't reach 100 because two checkers flagged us with errors (geographic ASN diversity is hard to fix without a second hosting provider). But 92 / A clears every compliance threshold we care about.
Lessons
- Order matters. Fix the CRITICAL first, then HIGH, then MEDIUM. Each fix exposes the next layer cleanly.
- Trust the snippets. UnveilScan's per-finding snippets are real config — copy-pasted into nginx/Apache they work first try, with the right server-specific syntax. We tested every snippet against actual deployments before shipping.
- Re-scan after each batch. Use the per-checker re-check button to verify a fix without running the whole 87-checker Extended again. Saves time and gives instant feedback.
- Some findings are environmental. "ASN single-provider" can't be fixed without a second hosting contract. Suppress those with a reason and move on; the scorer respects suppressions.
Run the same playbook on your domain
Free Basic scan now, Extended for full leak / CVE / takeover audit.
Run a scan