Guide
Website security hygiene basics for agencies
The checks an agency can run on a client site from the outside, with nothing more than a browser and a terminal, before the site goes live and after every deploy.
Last reviewed: 29 September 2026
1. What this covers, and what it does not
Every check below reads what the site already shows to any visitor: its responses, its headers, its certificate and its cookies. Nothing here logs in, guesses passwords, sends crafted input or tries to get past a control. That keeps the list safe to run on a client's live site without asking anyone first.
It also sets the limit of what the list can tell you. Good hygiene removes the easy, embarrassing problems, the ones anyone can see from outside. It says nothing about the application logic, the server behind it or the admin area, and passing every item is not a security test, a compliance statement or a certificate. If the client needs one of those, they need a qualified tester working under a written agreement.
2. HTTPS, redirects and the certificate
Every form of the address ends on one HTTPS URL, in one hop
Plain HTTP and the other host form (with or without www) should each answer 301 to the canonical HTTPS address directly, not through a chain.
curl -sI http://example.org/ | grep -iE "^(HTTP|location)"
curl -sI http://www.example.org/ | grep -iE "^(HTTP|location)"
curl -sI https://www.example.org/ | grep -iE "^(HTTP|location)"
The certificate covers every name and is not about to expire
HowRead the validity dates and the names on the certificate. Automatic renewal usually works; the failure is a name that was added later (a www, a shop subdomain) and is not covered, or a renewal that silently stopped. Put the expiry date in the client's handover notes either way.
echo | openssl s_client -connect example.org:443 -servername example.org 2>/dev/null \
| openssl x509 -noout -dates -ext subjectAltName
Old protocol versions are refused
TLS 1.0 and 1.1 should no longer be accepted. A request that allows only those versions should fail to connect.
curl -s -o /dev/null -w "%{http_code}\n" --tls-max 1.1 https://example.org/
A result of 000 means the connection was refused, which is what you want.
No mixed content
An HTTPS page that loads an image, script or font over plain HTTP gets a warning or a blocked request in the browser. Open the console on each page template and look for mixed content messages, and search the built HTML for src="http://.
3. Security headers
Headers are instructions to the visitor's browser. They are cheap to set and easy to check, which is why their absence is the first thing an outside reviewer notices.
curl -sI https://example.org/
Strict-Transport-Securitytells the browser to use HTTPS only. Look for amax-ageof at least one year (31536000). AddincludeSubDomainsonly once every subdomain serves HTTPS, or a forgotten one becomes unreachable.Content-Security-Policylists where scripts, styles, images and connections may come from. A policy that allows'unsafe-inline'scripts or*gives little protection. If the site has none, start withContent-Security-Policy-Report-Onlyand tighten it before enforcing.frame-ancestorsin the policy, orX-Frame-Options, stops other sites from loading the page in a frame to trick visitors into clicks.X-Content-Type-Options: nosniffstops the browser from guessing a file's type.Referrer-Policylimits what the next site learns about the page a visitor came from.strict-origin-when-cross-originis a sensible default.Permissions-Policyswitches off browser features the site does not use, such as the camera, microphone and location.
HowCheck more than the home page. Request a content page, the 404 page, a downloaded file and any form endpoint. Headers are often set for HTML pages only, and the error page is the one most often left without them.
4. Files that should not be public
Deployments leave things behind: version control folders, environment files with passwords in them, database dumps, backup archives and old copies of the site. Each of these should answer 404 or 403.
for p in /.git/config /.env /.env.local /backup.zip /site.zip /db.sql /wp-config.php.bak /.DS_Store; do
printf "%s %s\n" "$p" "$(curl -s -o /dev/null -w "%{http_code} %{redirect_url}" "https://example.org$p")"
done
Follow redirects to the end
A redirect is not an answer. If a sensitive path answers 301, 302 or 308, request the destination too, because a redirect to a folder that then lists its contents is still an exposure.
From a Sightline run on our own site
On 2 September 2026 the discovery step of a Sightline run on seekingdatalabs.com recorded this about a path under the version control folder:
seekingdatalabs.com/.git/index responded to an ordinary GET - redirect (HTTP 308), 0 bytes.
The run itself noted that finding a path is inventory, not a vulnerability, and that is the right reading: the redirect only adds a trailing slash. We followed it on 29 September 2026. The destination, /.git/, answers 404, and so does /.git/config. Nothing is exposed, but it took a second request to know that, which is the point of this check.
No folder lists its contents
Request the folders that hold uploads, images and scripts with a trailing slash. A page titled "Index of" means the server lists every file in the folder to anyone who asks.
robots.txt hides nothing
Read the site's robots.txt as a stranger would. Every path listed under Disallow is published to anyone who opens the file, so it should never be the only thing keeping an admin area or a private folder out of view.
Source maps and keys stay out of the bundle
Search the built JavaScript for .map references and for anything that looks like a secret: api_key, secret, token, password, or long random strings. A key that must live in the browser, such as a public maps key, should be restricted to the client's domain in the provider's settings.
5. What the site says about itself
Version numbers, internal hostnames and detailed error messages do not open anything by themselves, but they tell a stranger exactly what to look up. Remove what the visitor does not need.
- Version numbers in headers. Look at
Server,X-Powered-Byand similar headers. A product name is common; a product name with its exact version is unnecessary. - Version numbers in the page. Search the HTML head for a
generatormeta tag and the page source for version strings in file names and comments. - Error pages. Request a page that does not exist and a URL with an unexpected parameter. The answer should be the site's own error page, never a stack trace, a file path or a database message.
- Comments in the HTML. View source and search for
<!--. Notes to other developers, staging URLs and names of internal systems do not belong in a public page.
From a Sightline run on our own site
On 17 September 2026 a Sightline run on seekingdatalabs.com filed a low-severity finding on https://seekingdatalabs.com/terms/: the page head carried a generator meta tag naming the exact build tool and its version, which the finding called "unnecessary technical disclosure in a security-focused company's terms page".
It is right, and it was not limited to that page. When we re-checked on 29 September 2026 the same tag was still in the head of every page on the site, including this one. It gives an attacker nothing to use today; it is on our list because it is a one-line change that removes a free signal, which is the whole argument of this section.
6. Cookie flags
Each cookie the site sets carries attributes that decide who can read it and when it is sent. Check them for every cookie, not just the login one.
Secure: the cookie is sent over HTTPS only. Every cookie on an HTTPS site should have it.HttpOnly: page scripts cannot read the cookie. Session and login cookies must have it, so an injected script cannot copy them.SameSite:LaxorStrictstops the cookie from riding along on requests started by other sites.Noneis only for cookies that genuinely need to work across sites, and it requiresSecure.DomainandPath: a cookie scoped to the whole parent domain is sent to every subdomain, including ones run by other people or other systems. Scope it as narrowly as the site allows.- Lifetime: session cookies should expire when they are no longer needed, not in ten years.
HowIn the browser's developer tools, open the storage or application panel and read the cookie table, which shows every flag in its own column. Do it twice: once in a fresh private window before touching the consent banner, and once after logging in if the site has accounts. For cookies set by the server you can also read the raw header.
curl -sI https://example.org/ | grep -i "^set-cookie"
The first private window is also the consent check: before the visitor agrees, no analytics or advertising cookie should be there.
7. Third-party scripts
Every script loaded from another domain runs with the same rights as the site's own code. List them in the network panel, filtered to scripts from other hosts.
- Remove what nobody uses. Old tracking tags, chat widgets from a trial and testing tools from a campaign that ended are common.
- Pin what you load from a public CDN. A fixed version with an
integrityattribute means a changed file is refused rather than run. - Know who owns each one. The handover notes should say which account each tag belongs to, so the client can remove it later.
8. A way to report a problem
Someone who finds a problem should know where to send it. Publish a /.well-known/security.txt file with a contact address and an expiry date, and check that the address works.
curl -s https://example.org/.well-known/security.txt
While you are there, look up the domain's email records. A domain that sends mail (contact form notifications, newsletters) should publish SPF and DMARC records, so others cannot easily send mail that appears to come from it.
dig +short TXT example.org
dig +short TXT _dmarc.example.org
9. Keep the evidence
Save the raw output of every command above with the date and the URL: the headers, the status codes, the certificate dates, the cookie table. Run the same set after every deploy. Hygiene problems come back through ordinary changes, a new server rule, a plugin update or a new tracking tag, and a dated record of what the site returned last month is how you tell a regression from something that was never right.