Tuning the security of a WordPress site
WordPress gives you full control over your headers and the shape of your page, so almost everything this check reports is yours to fix. Below, per kind of finding, where the fix lives, with code you can copy. WooCommerce is plain WordPress for these fixes: it is a plugin, so the header fix is the WordPress one.
Response headers (HSTS, clickjacking, MIME sniffing, referrer, permissions)
HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Permissions-Policy are set in one place for the whole site. Three routes, pick what fits you. The server config is the most robust, because then the headers also apply to files PHP never touches.
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;add_action('send_headers', function () {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
});Without touching code: a plugin
If you cannot or would rather not touch the server config or functions.php, a security-headers plugin sets the same headers through a settings screen. Examples are Really Simple Security and the HTTP Headers plugin; there are more. Pick one that is actively maintained. This is not a recommendation of a specific product, only of the approach.
The Content-Security-Policy
The CSP is the heaviest and the trickiest on WordPress, because themes and plugins often inject inline scripts a strict policy blocks. So roll it out in steps:
- Set the policy in report-only first (the Content-Security-Policy-Report-Only header), so it breaks nothing but still reports what it would block.
- Collect the reports with a tool like Sentry, Report URI or URIports, or read them in your browser console.
- Allow what is legitimately blocked, until no real violations come in.
- Rename the header to Content-Security-Policy to enforce the policy.
# Watch the reports first, then switch the header name to
# Content-Security-Policy once nothing legitimate is blocked.
Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"In the page (a hash on a script, trackers after consent)
An integrity hash on an external script hangs on the script tag, not in a header. In WordPress you add it through a filter on the enqueue. And trackers that may load only after consent are handled by a consent plugin or a tag manager that waits for the answer (Complianz or CookieYes, say), not by embedding the script directly.
add_filter('script_loader_tag', function ($tag, $handle, $src) {
if ($handle !== 'the-external-handle') {
return $tag;
}
return str_replace(
'></script>',
' integrity="sha384-REPLACE_WITH_THE_REAL_HASH" crossorigin="anonymous"></script>',
$tag
);
}, 10, 3);The connection (forcing HTTPS)
In Settings > General set both the WordPress Address and the Site Address to https, and redirect http to https on the server. The certificate itself is your host or CDN, often a free Let's Encrypt certificate and a toggle.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>DNS records (SPF, DMARC, DNSSEC, IPv6)
These sit apart from your site: they are records at your domain. Set them with your DNS provider or domain registrar, not in your CMS or on your host. SPF and DMARC govern who may send mail as your domain, DNSSEC signs your DNS, and IPv6 is the one where the host has to offer an AAAA record and a connection before you can add it.