Skip to content
websitecontrole.nl
Advice

Tuning the security of a Drupal site

Drupal gives you full control: you set repairs on the web server, through a module, or in the markup. Two headers, incidentally, core already sets itself. Below, per finding, where the fix lives, with code you can copy.

Response headers (HSTS, referrer, permissions)

Two of the headers the check expects are set by Drupal core already: X-Content-Type-Options in the shipped .htaccess, and X-Frame-Options: SAMEORIGIN on every response (since Drupal 8). So they may be green without any action, and you should not set them again (that gives a duplicate header). The rest (HSTS, Referrer-Policy, Permissions-Policy) you add yourself: in the .htaccess, in the nginx config, or through the contributed Security Kit module (seckit), which enables them from an admin screen. HSTS only takes effect over HTTPS.

.htaccess (Apache), only these (core already sets XFO and XCTO)
<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
</IfModule>

The Content-Security-Policy

A CSP is harder on Drupal because core and many modules inject inline scripts and styles. There is a contributed module, Content-Security-Policy (machine name csp), that generates the header and adds sources for your own libraries automatically; enabled, it provides a report-only header that logs violations without blocking. Security Kit can also set a CSP.

  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.

Cookies (Secure, HttpOnly, SameSite)

Drupal manages the session cookie through Symfony, set in sites/default/services.yml under session.storage.options. Since Drupal 10.1 SameSite defaults to Lax; you can choose Strict. HttpOnly is on by default, and Secure belongs there once the site runs fully over HTTPS. If no services.yml exists yet, copy default.services.yml. For the exact Secure and HttpOnly keys, see default.services.yml in your install.

sites/default/services.yml
parameters:
  session.storage.options:
    cookie_samesite: Lax   # 'Lax' (default since D10.1), 'Strict', or 'None'

The connection (forcing HTTPS)

Drupal's default .htaccess contains a ready-made, commented-out HTTPS redirect block. Remove the # to send all HTTP traffic to HTTPS. On nginx you configure the redirect in the server config. Behind a CDN or load balancer this is often better done at that level.

.htaccess (Apache), present but commented out in core
# Uncomment the next two lines to redirect to HTTPS.
RewriteCond %{ENV:HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

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