How to secure your website
Patch what you run, close what is exposed, and harden HTTPS, cookies and browser headers, one checked step at a time.
Most websites are not broken into through clever attacks. They are broken into through an old version with a published exploit, an admin page that answers anyone, a cookie a script can read, or a header nobody set. Each of these is quick to fix once you know it is there. The hard part is that they are spread across your code, your web server, your CDN and your hosting settings.
This journey puts them in order. You start with what you run and whether it is patched, close the pages that should not be public, and then work outward through HTTPS, cookies and the headers that switch on browser protections. The last two steps cover what cannot be seen from outside: how people sign in, and what sits behind the site.
Most steps have an auditor that checks the result on your monitored URL. A few complete by hand, either because nothing outside your network can verify them or because a correct setup can still leave a finding you have to judge yourself. Each of those steps says so.
Running Liferay? How to secure your Liferay site covers the same ground with Liferay settings and checks your version against Liferay's advisories. Related journeys: How to secure your email and How to keep your website online.
Steps
Open a step to read it1
Know what you runYou cannot patch what you have not written down. A typical site runs a framework or CMS, plugins or packages, a language runtime, a web server and often a CDN, and each of them publishes security fixes on its own schedule.
Do this
List the software behind the site with the version in production: CMS or framework, plugins and themes, runtime (Node, PHP, Java, Python), web server, database
Open the project that owns this URL, find Software Stack and add the main platform with its version. 37 Audits uses it to pick platform-specific checks
Note where each one publishes security advisories (GitHub security advisories, a mailing list, a release page) and subscribe
Done when the list exists and the project's Software Stack names the platform and version that run in production. Update both after every upgrade.
2
Patch known vulnerabilitiesEvery published vulnerability comes with the versions it affects, and attackers read the same lists you do. A known vulnerability in a version you run is the easiest way into the site, and automated scanners try them within days of publication.
Do this
Turn on automated dependency alerts: Dependabot or Renovate on GitHub,
npm audit,composer audit,pip-audit, or your CMS's update screenFor each alert on something that runs in production, read which version fixes it and update. If you cannot update now, apply the workaround the advisory describes
Remove plugins, themes and packages you do not use. They still count
Look up your platform and server versions on endoflife.date and plan an upgrade for anything out of support: it gets no fixes at all
Done when nothing in production has an open advisory you have not either fixed or consciously accepted. On Liferay, the CVE Auditor checks this automatically against Liferay's advisories; for other platforms this step completes by hand.
3
Close the admin and debug pagesVerifies with Protected URL AuditorAdmin consoles, setup wizards, debug pages, API explorers and forgotten backup folders are meant for you, not for the internet. When they answer anonymous visitors they give an attacker a sign-in form to brute force, or worse, a page that needs no sign-in at all.
The Protected URL Auditor requests common paths without signing in:
/admin,/administrator,/dashboard,/manage,/config,/setup,/install,/debug,/dev,/test,/backup,/logs,/internal,/apiand others. A path is protected when it answers 401, 403 or 404, or redirects to a sign-in page.Do this
Run the auditor and look at every path it reports as open
Remove what should not exist in production: installers, test pages, debug toolbars, old backups
Put admin areas behind sign-in, and if only your team uses them, also restrict them by IP address or VPN at the web server or CDN
Turn off debug mode and verbose error pages in production
Add paths of your own, such as
/wp-adminor a custom console, in the auditor'spathspreference on the URL's Configuration tab
Done when the auditor reports every path as protected.
4
Stop advertising server versionsVerifies with Server Fingerprint AuditorKnowing the exact version of your web server or framework lets an attacker pick the right exploit without probing. These versions travel in response headers that no visitor needs.
The Server Fingerprint Auditor reads
Server,X-Powered-ByandX-AspNet-Version. It fails when one of them carries a version number, and warns when they are present without one.Do this
Nginx:
server_tokens off. Apache:ServerTokens ProdandServerSignature OffExpress:
app.disable('x-powered-by'). PHP:expose_php = Off. ASP.NET: removeX-AspNet-VersionandX-Powered-Byinweb.configCheck error pages too: a default 404 or 500 page often prints the version
Done when no header carries a version number. Many hosts and CDNs (Cloudflare, Vercel, Netlify) always send a bare
Serverheader you cannot remove, which leaves a warning, so this step completes by hand.5
Redirect HTTP to HTTPSVerifies with HTTP Redirect AuditorAnyone who types your address without
https://starts on plain HTTP. Until the redirect happens, the request can be read or changed on the way, including a session cookie that is not marked secure.The HTTP Redirect Auditor requests the
http://version of your URL. It fails when there is no redirect or when it goes somewhere that is not HTTPS, and warns on a temporary 302.Do this
At the web server, load balancer or CDN, answer every request on port 80 with a 301 to the same path on HTTPS. On Cloudflare this is Always Use HTTPS; most hosting platforms do it by default
If TLS ends at a proxy, make sure the application knows the original request was HTTPS (
X-Forwarded-Proto), or it will buildhttp://links and redirect loops
Done when the auditor reports a permanent redirect to HTTPS.
6
Serve a valid certificate on modern TLSVerifies with Certificate AuditorVerifies with TLS Version AuditorThe Certificate Auditor validates the chain the way a browser does: expired, revoked, self-signed, wrong hostname, untrusted root or missing intermediate all fail. The TLS Version Auditor tries each protocol version and fails when TLS 1.0 or 1.1 is still accepted.
Do this
Install the full chain, intermediates included. A missing intermediate works in some browsers and fails in API clients and mobile apps
Check that the certificate names every hostname the site answers on,
wwwincludedAutomate renewal with ACME (Let's Encrypt, ZeroSSL) or your host's managed certificates
Turn off TLS 1.0 and 1.1 and turn on TLS 1.3. With 1.2 alone the auditor warns and this step will not complete by itself. On a CDN this is a setting (Cloudflare: Minimum TLS Version and TLS 1.3)
Done when both auditors pass.
7
Turn on HSTSVerifies with HSTS Preload AuditorThe redirect from step 5 protects a visitor only after the first plain request. HSTS tells the browser to skip that request and use HTTPS from the start, for as long as you say.
The HSTS Preload Auditor expects the header in the form browsers accept for their preload list: a
max-ageof at least one year,includeSubDomainsandpreload.Do this
First confirm that every subdomain of your domain serves HTTPS.
includeSubDomainsapplies to all of them, including old staging servers and internal toolsOn HTTPS responses, send
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadIf you are unsure, start with a short
max-ageand withoutpreload, and raise it once nothing breaksOptionally submit the domain at hstspreload.org so browsers ship with it
Done when the auditor passes. Browsers remember this header for the whole
max-age, so removing it later does not undo it quickly.8
Remove mixed contentVerifies with Mixed Content AuditorA page served over HTTPS that loads an image or script over
http://is only partly protected. Browsers block the scripts and warn about the rest.The usual sources are content, not code: images pasted into posts with
http://links, hardcoded URLs in templates, old embeds and third-party widgets.The Mixed Content Auditor loads the page in a browser and fails for every request or element that uses
http://.Do this
Run the auditor and open the findings to see each URL
Fix the URL where it lives: the post, the template, the theme, the widget configuration
Use relative or
https://URLs in new content. A database search and replace ofhttp://your-domainfixes most CMS content in one go
Done when the auditor finds no
http://resource. Run it on your busiest pages too, not only the home page.9
Secure your cookiesVerifies with Cookie AuditorA session cookie is the signed-in user. If a script can read it, or it travels over plain HTTP, the session can be stolen.
The Cookie Auditor checks every cookie set by the monitored URL. A cookie without
HttpOnlyfails, and so doesSameSite=NonewithoutSecure. A missingSecureorSameSiteis a warning.Do this
Set session and authentication cookies with
Secure; HttpOnly; SameSite=Lax(orStrictif nothing cross-site needs them). Most frameworks have one setting for thisGive cookies the shortest lifetime that works, and a narrow
PathandDomainReview the other cookies in the findings. A cookie a front-end script must read cannot be
HttpOnly; make sure it holds nothing sensitive
Done when the auditor passes, or every remaining finding is a cookie you looked at and decided to accept, in which case mark the step complete by hand.
10
Send the browser security headersVerifies with X-Frame-Options AuditorVerifies with X-Content-Type-Options AuditorVerifies with Referrer Policy AuditorVerifies with Permissions Policy AuditorFour response headers switch on protections that browsers leave off by default. They are attached together here because you set them in the same place: the web server, the CDN, or your framework's headers config (
next.config.js,vercel.json,_headerson Netlify and Cloudflare Pages).Do this
X-Frame-Options: SAMEORIGINstops other sites from framing yours (clickjacking). If you embed your own pages in an iframe elsewhere, allow that origin in CSPframe-ancestorsinsteadX-Content-Type-Options: nosniffstops browsers from guessing file types, which matters for anything users uploadReferrer-Policy: strict-origin-when-cross-originkeeps full page URLs, with tokens or search terms in them, from leaking to other sitesPermissions-Policy: camera=(), microphone=(), geolocation=(), payment=()turns off device features the site does not use. Allow only the ones a page needsSet them on every response, error pages and static files included
Done when all four auditors pass.
11
Add a Content Security PolicyVerifies with Content Security Policy AuditorA Content Security Policy tells the browser which sources of script, style, images and frames the page may use. It is the strongest defence against injected script, and the hardest header to get right, because every analytics tag, chat widget and font host has to be listed.
The CSP Auditor fails when the header is missing, and warns when the policy is report-only or allows
unsafe-inlineorunsafe-eval.Do this
Start with
Content-Security-Policy-Report-Onlyand areport-toendpoint, and watch what real pages would breakList the origins you depend on: CDN, analytics, tag manager, payments, maps, video
Replace inline scripts with files, or use nonces or hashes. Most modern frameworks can add a nonce per request
Include
frame-ancestors 'self',object-src 'none'andbase-uri 'self'Switch to the enforcing header once the reports are quiet
Done when the enforcing header is live and the pages you care about still work. Many sites cannot drop
unsafe-inlineright away, especially with a tag manager, and the auditor keeps warning about it, so this step completes by hand. A policy withunsafe-inlinestill limits where scripts and frames can come from, which is better than no policy.12
Harden sign-inNo auditor can see these settings from outside. They decide what happens when someone guesses, buys or phishes a password.
Do this
Remove default accounts and default passwords from the CMS, admin panels and databases
Require a second factor for every administrator, and offer it to users. Passkeys are the strongest and easiest option today
Rate-limit sign-in, password reset and signup, and lock or slow down after repeated failures. A CDN or WAF rule on these paths is often enough
Hash passwords with a slow algorithm (Argon2, bcrypt, scrypt, PBKDF2). If you use an identity provider, it does this for you
Expire sessions after a time that fits the data behind the login, and end them on sign-out and password change
Do not reveal whether an email address has an account on sign-in or reset pages
Done when admins sign in with a second factor and sign-in is rate-limited.
13
Lock down what sits behind the siteA secure front door does not help if a side door is open. Databases, search engines, caches and admin ports are often reachable from the internet by accident, and leaked secrets give direct access without touching the site at all.
Do this
Confirm that database, cache and search ports (5432, 3306, 6379, 9200, 27017 and others) are not reachable from outside. Test from a machine off your network, or with a port scan of your public IP
Make sure
/.git/,/.env,/backup.zip,/phpinfo.phpand similar files are not servedKeep secrets out of the repository and out of front-end bundles. Rotate any key that was ever committed
Give each service its own credentials with the least access it needs, and store them in your host's secret manager
Back up regularly and keep one copy where the site's own credentials cannot delete it
Publish
/.well-known/security.txtwith aContact:address so researchers can report problems to you
Done when a scan from outside shows only 80 and 443, and no secret lives in the code.
Create an account, add your site as a monitored URL and work through these steps on it.