Most of a site's basic security posture is visible from outside: HTTP response headers and a few DNS records. You don't need access to the codebase to check them, and you don't need a scanner to read them. You need curl, dig, and twenty minutes.
This isn't a penetration test. It won't find an injection bug in your checkout. It tells you whether the cheap, standard protections are switched on and configured sensibly, and those are the protections most often missing.
Set two variables and keep a notes file open:
SITE=https://example.com
DOMAIN=example.com
curl -sI "$SITE" > headers.txt
Minutes 0–3: HTTPS and HSTS
First check that plain HTTP redirects to HTTPS:
curl -sI http://$DOMAIN | grep -iE "^(HTTP|location)"
You want a 301 or 308 to the https:// URL. Then look for HSTS:
grep -i strict-transport-security headers.txt
Strict-Transport-Security tells browsers to use HTTPS for your host for max-age seconds, even if someone types http:// or clicks an old link. Browsers ignore it when it arrives over plain HTTP, so it only counts on HTTPS responses.
A reasonable target is:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Be careful with two parts of it:
-
includeSubDomainsapplies to every subdomain. If a legacystatus.ormail.host only serves HTTP, browsers will refuse to reach it. Check your subdomains before adding it. -
preloadasks to be included in the HSTS preload list built into browsers. hstspreload.org requires a valid certificate, an HTTP→HTTPS redirect on the same host, HTTPS on all subdomains, and a header withmax-ageof at least 31536000 plus bothincludeSubDomainsandpreload. Getting removed from the list is slow, so treat preload as a one-way door.
Minutes 3–9: Content-Security-Policy
grep -iE "content-security-policy" headers.txt
CSP is the most valuable header and the hardest to get right. It tells the browser which sources of script, style, frames, and so on are allowed, and that limits the damage when an XSS bug lets an attacker inject markup.
What to look for when a policy exists:
-
'unsafe-inline'inscript-srcwithout a nonce or hash. That undoes most of CSP's protection against XSS. -
Wildcards like
script-src https:or*, which allow script from anywhere. -
No
object-src 'none'and nobase-uri.<base>tag injection can redirect relative script URLs. -
No
frame-ancestors. This is the modern way to stop other sites framing yours (clickjacking).
If there's no policy at all, don't write a strict one and ship it in one go. Something will break. Roll it out in report-only mode:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; report-to csp
Collect the violation reports for a week, fix the legitimate sources, then switch the header name to Content-Security-Policy. Nonce-based policies need a fresh random nonce on every response, so pages served entirely from a static cache need hashes instead.
Two gotchas: frame-ancestors and reporting directives don't work in a <meta http-equiv> CSP, only in a real header. And a policy that's only report-only gives no protection at all.
Minutes 9–12: the small headers
These take a line each and are easy to check:
grep -iE "x-content-type-options|referrer-policy|permissions-policy|x-frame-options|x-xss-protection" headers.txt
-
X-Content-Type-Options: nosniffstops browsers guessing a response's MIME type. Turn it on everywhere. -
Referrer-Policy: strict-origin-when-cross-originsends only your origin, not full URLs with paths and query strings, to other sites. Modern browsers already default to this, but setting it explicitly protects you from older clients and accidental overrides. -
Permissions-Policyturns off browser features you don't use, for examplecamera=(), microphone=(), geolocation=(). -
X-Frame-Options: DENYorSAMEORIGINis the legacy clickjacking control. Keep it for older browsers. When CSPframe-ancestorsis present, modern browsers use that instead. -
X-XSS-Protectionis deprecated. The old browser XSS filters it controlled have been removed, and in some cases they caused leaks of their own. Omit it or set it to0.
Minutes 12–14: cookies
Look at every Set-Cookie header, especially after logging in:
curl -sI "$SITE/login" | grep -i set-cookie
Session cookies should have:
-
Secure, so they're only sent over HTTPS. -
HttpOnly, so page JavaScript can't read them. This is your second line of defence if XSS happens. -
SameSite=LaxorStrict, which helps against cross-site request forgery.
The __Host- prefix is worth knowing: a cookie named __Host-session is only accepted if it's Secure, has Path=/, and has no Domain attribute. That stops a compromised subdomain from setting it.
Minutes 14–18: email authentication (SPF, DKIM, DMARC)
These are DNS records, not HTTP headers, but they belong in a website audit. If your domain can be spoofed, attackers can send convincing phishing "from" your brand.
dig +short TXT $DOMAIN | grep -i spf
dig +short TXT _dmarc.$DOMAIN
SPF lists the servers allowed to send mail for the domain:
v=spf1 include:_spf.google.com include:sendgrid.net ~all
Things that go wrong: two SPF records (you're only allowed one, and two makes SPF fail), and going over the 10 DNS-lookup limit that SPF sets. Every include, a, mx, exists, and redirect counts, including nested ones. Too many includes cause a permerror.
DKIM signs outgoing mail. Your email provider gives you a selector and a public key to publish at selector._domainkey.example.com. You can't find the selector by guessing, so check your provider's settings or the DKIM-Signature header (s=) of a message you sent.
DMARC tells receivers what to do when SPF or DKIM fail alignment, and where to send reports:
v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com
Start at p=none to collect reports, then move to p=quarantine and finally p=reject once every legitimate sender passes. Since February 2024, Gmail and Yahoo require bulk senders to have DMARC in place, so this isn't optional if you send marketing email at volume.
Domains that never send mail should say so explicitly, because parked domains get spoofed too:
example.com. TXT "v=spf1 -all"
_dmarc.example.com. TXT "v=DMARC1; p=reject;"
Minutes 18–19: CAA
dig +short CAA $DOMAIN
A CAA record lists which certificate authorities may issue certificates for your domain. CAs have been required to check it before issuing since 2017. Without it, any publicly trusted CA may issue for you. With it:
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 issuewild ";"
example.com. CAA 0 iodef "mailto:security@example.com"
That allows Let's Encrypt, forbids wildcard certificates, and says where to report violations. CAs look up CAA records starting at the exact hostname and climb the DNS tree until they find one, so a record on the apex covers subdomains that don't have their own.
The common mistake is forgetting a CA you actually use. If your CDN or hosting provider issues certificates for you, include its CA(s), or the next automatic renewal will fail. Check the provider's docs before you publish the record.
Minute 19–20: security.txt and writing it up
curl -s "$SITE/.well-known/security.txt"
RFC 9116 defines security.txt as the place researchers look for how to report a vulnerability. Contact and Expires are required:
Contact: mailto:security@example.com
Expires: 2027-10-01T00:00:00.000Z
Policy: https://example.com/security-policy
Then write the findings down in priority order, and separate vulnerabilities from observations. A missing CAA record isn't the same as a session cookie without HttpOnly. I'd order the fixes like this:
- No HTTPS redirect or HSTS on login and app pages.
- Session cookies missing
SecureorHttpOnly. - No DMARC, or SPF broken by duplicates or the lookup limit.
- No CSP, or a CSP with
'unsafe-inline'scripts. Start report-only. - Missing small headers.
- CAA and security.txt.
For each finding, record the exact header or record you saw, the line you want, and how you'll verify it (the curl or dig command above). That last part is what turns an audit into a pull request.
If you'd rather not run the HTTP side of this by hand on every site you look after, Siteory's security audit checks the header, CSP, and cookie rules automatically. It labels each result as a finding or an observation and gives you the fix and a re-check. The DNS checks (SPF, DMARC, CAA) are still a dig away, and doing the whole thing by hand once is the best way to learn what each control actually does.
Top comments (0)