A secure corporate website meets at least seven conditions: it serves everything over HTTPS with a valid certificate and forced redirection; it sends HTTP headers that limit what the browser can load and do (CSP, HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy); it validates forms on the server and protects them against spam; it has tested backups; it keeps all software up to date; it collects only the personal data that is strictly necessary; and it publishes a security.txt file so that anyone who finds a flaw knows who to tell. All of it can be checked from the browser and the terminal in under an hour.
Most attacks on company websites are not targeted. They are programs that crawl the internet looking for an outdated plugin, an unprotected form or an admin panel with a weak password. This is the list I go through on every website I deliver or audit, whether it is WordPress or custom-built.
1. HTTPS across the whole site
HTTPS encrypts the connection between the visitor and the server. Without it, anyone on the same network can read what is sent in a form and alter what is displayed. Browsers flag any HTTP site as "not secure".
The minimum:
- A valid certificate that renews automatically. Let's Encrypt provides it for free and most hosting providers integrate it.
- A 301 redirect from every HTTP URL to HTTPS, including the versions with and without
www. - No resource loaded over HTTP inside an HTTPS page (mixed content). An image or a script over HTTP breaks the padlock and the browser may block it.
http://tudominio.es and confirm it redirects to https://. Check the browser console for mixed content warnings. Run the domain through Qualys SSL Labs or the Mozilla Observatory.2. HTTP security headers
These are instructions the server sends to the browser with every response, limiting what the page can do. They cost a few lines of configuration and close off whole families of attacks. The five that matter:
| Header | What it does | Reasonable starting value |
|---|---|---|
Content-Security-Policy | Defines which origins may load scripts, styles, images and frames. It is the main defence against script injection (XSS). | default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none', extended per resource type according to what the site loads |
Strict-Transport-Security | Forces the browser to use HTTPS on subsequent visits, even if the user types http://. | max-age=31536000; includeSubDomains (add preload only if you are going to submit the domain to the preload list) |
X-Content-Type-Options | Stops the browser from "guessing" a file's type and executing as a script something that is not one. | nosniff |
Referrer-Policy | Controls how much of the originating URL is sent when an external link is clicked. | strict-origin-when-cross-origin |
Permissions-Policy | Disables browser features the site does not use: camera, microphone, geolocation. | camera=(), microphone=(), geolocation=() |
Two warnings about CSP. First: a strict policy can break things (the third-party chat widget, analytics, external fonts). Start in report-only mode with Content-Security-Policy-Report-Only, see what it would block and adjust before enforcing it. Second: avoid 'unsafe-inline' in script-src if you can; with it the policy loses almost all its value against XSS. If your site uses a content management system with inline scripts, a nonce or a hash per script is the alternative.
About HSTS: once received, the browser will not load the site over HTTP again for the stated period. If the certificate fails, the site stays unreachable until it is fixed.
curl -I https://tudominio.es shows the headers. You can also see them in the Network tab of the developer tools. The Mozilla Observatory and the OWASP Secure Headers tool score them and explain what is missing.3. Forms: validate on the server, stop the spam
The contact form is the part of the website that accepts data from strangers. Two rules:
- Always validate on the server. Browser-side validation is a convenience for the user, not security: anyone can skip it by sending the request directly. The server must check the type, length and format of every field, reject anything it does not expect and escape everything that will be displayed or stored. The OWASP guidance on input validation is the reference.
- Protect against automated submissions. Three layers that work together: a hidden field (honeypot) that humans never see and bots fill in; a limit on submissions per IP address and per minute; and, if spam persists, a lightweight challenge (Turnstile, reCAPTCHA) as a last resort, because it adds friction and, with external services, sends visitor data to a third party that has to be covered in the privacy policy.
If the form accepts file attachments, limit types and size and never serve them from the same path they are uploaded to.
4. Backups that have been tested
A backup that has never been restored is a hypothesis. The minimum:
- Automatic daily backup of files and database, with at least 30 days of retention.
- At least one copy off the production server (another provider or independent storage). If the server goes down or gets encrypted, the copy that was on it goes down too.
- A test restore every quarter, in a separate environment, timed. How long it takes is your real recovery time.
- A manual backup before every major update.
5. Updates and attack surface
Most intrusions into company websites exploit vulnerabilities that are already known and already patched in software nobody updated. In WordPress, the bulk of them are in plugins and themes, not in core. In a custom-built site, in the libraries and the server's operating system.
- Update core, plugins, theme and dependencies at least once a month, with a backup beforehand and a check afterwards.
- Remove what you do not use: deactivated plugins, old themes, accounts of people who have left, installation files. What does not exist cannot be attacked.
- Protect the admin panel: long, unique passwords, a second authentication factor, a limit on login attempts and, if possible, access restricted by IP or VPN.
- Review who has access to the hosting, the domain and the panel, and with what permissions.
6. Minimum personal data
Security and data protection meet here. The GDPR requires collecting only the data necessary for the purpose (Article 5) and protecting it with appropriate technical measures (Article 32). For a corporate website that translates into:
- Forms that ask for the essentials. If name and email are enough to answer an enquiry, do not make phone, company and job title mandatory.
- Clear information at the point of collection: who processes the data, for what purpose, for how long and how to exercise your rights. Linked from the form itself.
- Defined and enforced retention periods: contact messages from four years ago should not still be in the website's database.
- Cookies and third-party scripts only with prior consent when they are not technically necessary, following the AEPD's cookie guidance.
- A procedure for notifying the AEPD within 72 hours if there is a breach affecting personal data (Article 33). Know who to call before you need to.
7. security.txt
If someone finds a security flaw on your website, how do they tell you? The security.txt standard (RFC 9116) solves this with a text file at /.well-known/security.txt that states a contact, an expiry date and, optionally, a preferred language and a disclosure policy. Minimal example:
Contact: mailto:[email protected]Expires: 2027-10-29T00:00:00.000ZPreferred-Languages: es, en
The Expires field is mandatory and should not be more than a year away. Put a date in the calendar to renew it.
Checklist table
Print it or copy it into a shared sheet. Review it when the site is delivered and every three months after that.
| Check | How to verify | Frequency |
|---|---|---|
| HTTP redirects to HTTPS, with and without www | Open all four variants in the browser | Quarterly |
| Valid certificate with automatic renewal | Browser padlock; expiry date | Monthly |
| No mixed content | Browser console on the main pages | After every change |
Strict-Transport-Security header | curl -I | Quarterly |
Content-Security-Policy header without 'unsafe-inline' for scripts | curl -I and browser console | After every change |
X-Content-Type-Options, Referrer-Policy, Permissions-Policy headers | curl -I | Quarterly |
| Forms validate on the server | Submit invalid data bypassing browser validation | After every change |
| Anti-spam active (honeypot and rate limit) | Review configuration and volume of spam received | Monthly |
| Daily off-site backup with 30 days of retention | List the backups; check the date of the latest | Monthly |
| Restore tested and timed | Restore in a separate environment | Quarterly |
| Software up to date | Updates panel; dependency list | Monthly |
| Unused plugins, themes and users removed | Review the lists | Quarterly |
| Second factor on the admin panel | Try to log in without it | Quarterly |
| Forms ask only for what is needed and link to the privacy information | Review each form | After every change |
| Cookie consent before non-essential scripts | Network tab before accepting | Quarterly |
/.well-known/security.txt present and not expired | Open the URL; check Expires | Yearly |
The security of a corporate website is not bought once. It is reviewed every quarter.
If you would like me to review yours against this list, the web development service includes an initial technical audit, and the recurring maintenance covers the monthly and quarterly checks. You can also write to me with the results of your own review.
- MDN · Content-Security-Policy
- MDN · Strict-Transport-Security
- MDN · X-Content-Type-Options
- MDN · Referrer-Policy
- MDN · Permissions-Policy
- OWASP · Secure Headers Project
- OWASP · Input Validation Cheat Sheet
- web.dev · Why HTTPS matters
- Let's Encrypt · Free TLS certificates
- Cloudflare Docs · HTTP Strict Transport Security
- RFC Editor · RFC 9116, security.txt
- EUR-Lex · Regulation (EU) 2016/679, GDPR, Articles 5, 32 and 33
- AEPD · Guide on the use of cookies
- INCIBE · Protect your business