Skip to content
websitecontrole.nl
Advice

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.

.htaccess (Apache)
<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>
nginx (server block)
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;
functions.php (or a small must-use plugin)
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:

  1. 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.
  2. Collect the reports with a tool like Sentry, Report URI or URIports, or read them in your browser console.
  3. Allow what is legitimately blocked, until no real violations come in.
  4. Rename the header to Content-Security-Policy to enforce the policy.
Start in report-only (.htaccess)
# 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'"

Cookies (Secure, HttpOnly, SameSite)

WordPress already sets its own login cookies with Secure (on HTTPS) and HttpOnly. When the check reports an insecure cookie, it usually comes from a plugin or custom code. Serve the whole site over HTTPS (WordPress then sets Secure itself), and give your own cookies the flags explicitly.

A custom cookie, set safely
setcookie('my_cookie', $value, [
    'expires'  => time() + 3600,
    'path'     => '/',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);

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.

Redirect http to https (.htaccess)
<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.

Go deeper

Advice for another platform