Drupal security headers: when a site needs Security Kit
Security Kit is the Drupal.org contributed module that sets HTTP security headers for clickjacking, cross-site scripting and CSRF mitigations from inside the site. If a scanner has flagged missing headers, or a reviewer has asked how the site defends against framing, script injection or cross-site request forgery, this module is one answer, but not the only one. Headers can be set in three places: by Drupal itself through a module, by the web server (Apache or Nginx configuration), or by the CDN or reverse proxy in front of the site. Security Kit is a contributed module from Drupal.org, so it is code your team installs, updates and reviews like any other contributed project, and it is governed by the same advisory process as the rest of the site. The layer you choose decides who maintains the policy. A header set in Drupal travels with the application, follows the site across environments and is tested with the same release process as the code, but it only applies to responses Drupal generates, so static files and error pages served by the web server may behave differently. A header set at the web server or CDN covers every response and is easy to audit at the edge, but the Drupal team may not know the policy exists, and changes made there can drift from what the application expects. Before deciding, confirm which team owns each header today, where the scanner reads its results, and whether any content in the site, such as embedded video or third-party widgets, depends on framing or inline script behavior. DeployOpen plans, configures and verifies this work inside your own environment, including a check of the headers that actually reach a browser after every layer has applied its rules. DeployOpen does not issue audit reports, attestations or certifications. Independent auditors and certification bodies make those determinations, and our role is to prepare the configuration, the evidence and the records they will ask to see.
Which browser attacks security headers reduce
HTTP security headers tell the browser which risky behaviors to refuse, so they cut off whole classes of attack before they reach your Drupal code. Each header family covers a different browser risk, and none of them repairs a vulnerable module, form or template on its own.
Clickjacking through framing
Clickjacking tricks a signed-in user into clicking buttons on your site while it is hidden inside another page's frame. Frame-restricting headers, such as X-Frame-Options and the frame-ancestors directive in Content-Security-Policy, ask the browser to refuse embedding. Confirm which of these the current Security Kit release sets and whether your admin and login paths need an exception for trusted embeds.
Script injection and cross-site scripting
Headers reduce the damage of cross-site scripting but do not remove the injected code. A Content-Security-Policy restricts which scripts, styles and sources the browser will run, so injected inline or third-party script may be blocked. Your text formats, templates and custom modules still need correct output escaping. Confirm the policy Security Kit applies against the module's Drupal.org project page.
Cross-site request forgery
Cross-site request forgery makes a victim's browser send a state-changing request to your site using their existing session. Drupal's own form tokens are the primary defense, and some headers, such as the Referrer-Policy and Sec-Fetch based controls, limit what the browser reveals or sends across sites. Confirm which CSRF-related options the module exposes and test them against your forms.
Content type sniffing and downloads
Browsers may guess a file's type from its contents when the declared type is missing or wrong, which can turn an uploaded file into executable content. The X-Content-Type-Options header tells the browser to trust the declared type. This matters most for public file uploads and user-generated content, so check how your upload paths and private file directories are served.
Transport and referrer leakage
Strict-Transport-Security tells the browser to use HTTPS for your domain for a set period, which blocks downgrade attempts on later visits. Referrer-Policy controls how much of an internal URL is sent to external sites. Both depend on your TLS setup and redirects, so confirm the HSTS duration and subdomain scope with your security team before enabling them broadly.
What headers cannot fix
Headers do not patch vulnerable code, weak access control, insecure file handling or exposed admin paths. A known flaw in a contributed module still needs its update, and a broken permission still needs a fix in configuration or code. Treat headers as one layer alongside the update routine, the access model and security testing of your custom code.
Security Kit headers or headers at the web server or CDN
This table compares setting HTTP security headers in the Drupal Security Kit module, at the web server (Apache or Nginx), and at a CDN or edge layer. Read each row as a question you need answered before choosing, and use the takeaway that headers set in Drupal only cover responses Drupal generates.
| Decision point | Security Kit (Drupal) | Web server (Apache or Nginx) | CDN or edge rules |
|---|---|---|---|
| Who owns the change | Site builders and Drupal developers through the admin UI or configuration | Platform or infrastructure team with server configuration access | Platform, network or security team with access to the CDN console or API |
| Where the change is versioned | Drupal configuration exported to the codebase and reviewed like other config | Server configuration or infrastructure-as-code repository | Usually the provider console; confirm whether rules can be exported or managed as code |
| Coverage of non-Drupal responses | Limited to responses Drupal generates; static files and errors from other services may be missed | Can apply to every response, including static assets and proxied services | Can apply to every response passing the edge, but only for hosts and paths the rule matches |
| Behavior with page caching | Headers are generated by Drupal, so they must be checked after each cache is cleared or served from a reverse proxy | Usually applied after the cache, so headers stay consistent for cached pages | Rules can override or add headers on cached objects; confirm the provider's order of operations |
| Interaction with existing headers | May duplicate headers already sent by the server, which can cause conflicting values | Can conflict with headers the application already sends; use one owner per header | Can add or overwrite origin headers; confirm whether existing origin values are kept or replaced |
| Testing and rollback | Changes deploy with the site, so rollback follows the normal release process | Changes require a server reload or redeploy and a separate rollback plan | Changes can often be published or reverted without touching the origin; confirm propagation time with the provider |
| When it fits | Small or single-site estates where the team wants headers managed alongside Drupal configuration | Multi-application hosts or estates that need one consistent policy for all sites and static content | Estates already fronted by a CDN where the security team owns edge policy and wants changes outside the application |
| Main risk | Headers missing from responses served by a reverse proxy or from non-Drupal paths | Drift between server configs across environments and forgotten reloads | Edge rules that silently override origin headers or fall out of sync with application changes |
Setting up Security Kit headers in order
This sequence moves a Drupal site from an unknown header baseline to a documented, tested set of security headers, with each header assigned to one owning layer. The result is a policy record, a staging configuration that has been verified, and a production rollout that the team can repeat after upgrades.
Inventory headers at every layer
Before changing anything, record the security headers that already leave the site. Request a sample of pages from the browser developer tools and from a command-line request with curl, using the
-Iflag, against the origin server, the CDN edge and the public hostname. Include the home page, a logged-in editor page, a static file such as a CSS or image asset, and a 404 page. Note which layer adds each header, because the same header is often set by both the web server and the CDN. The output is a table of header name, value, layer and page, kept with the change ticket. Confirm with the web server and CDN administrators which configuration files or consoles hold each setting.Assign each header to one owning layer
For each header the team intends to keep, decide whether Security Kit or the web server or CDN will own it. Security Kit is a good fit for headers tied to Drupal's own responses, such as those that depend on the site's content or on Drupal-generated pages. Headers that apply to every response, including static files served without PHP, are often better owned at the web server or CDN. Where two layers set the same header, pick one owner and remove the other so responses carry a single value. Record the decision for each header in a shared register, with the owner, the reason and the person who approves changes. Confirm the owning team can change the setting without a Drupal release.
Install and configure Security Kit on staging
Install the contributed Security Kit module on a staging copy of the production site, using the same Drupal core version, PHP version and Composer-managed codebase as production. Enable the module, then open its settings and set the headers assigned to Drupal in the previous step. Keep the settings that Drupal owns in configuration so they are exported with the site. Do not enable headers on production yet. The check is that the staging configuration exports cleanly, the module appears in the status report without errors, and a cache rebuild completes. Record the exported configuration file names and the staging environment identifier in the change record.
Set the web server and CDN headers on staging
Apply the headers assigned to the web server or CDN to the same staging environment, using the configuration files or console settings identified in the inventory. Keep the values identical to the Drupal settings wherever a header appears in both layers during transition. Store the web server configuration in version control with the Drupal codebase so the two layers are reviewed together. The check is that a reload or deploy succeeds and that the configuration diff contains only the intended header lines. Record the commit or change identifier that holds the final configuration.
Clear caches on every layer
Clear the Drupal cache, then purge the CDN cache for the staging hostname and restart or reload the web server if the configuration requires it. Stale responses are the most common reason a header change appears to have no effect, because a cached page carries the old headers until it expires or is purged. Confirm the CDN purge completed in its console or logs, and note the time of each purge. The check is that the next request to a page and a static file reflects the new configuration, and that no response shows a header from the removed layer.
Verify responses across page types
Repeat the inventory requests from the first step against staging. Compare the header names and values from the browser and from a command-line request, since a browser extension or a local proxy can hide differences. Test the anonymous home page, an authenticated editor page, an administrative route, a form submission response, a 404 page, a 403 page and a static file. Look for missing headers, duplicated headers and values that differ between layers. The output is a pass or fail result per page type, recorded with the request time and client. Any failure returns the change to the register for correction.
Test embedded content and admin workflows
Confirm that the headers do not block features the site depends on, such as embedded video, third-party forms, analytics, the Drupal administrative interface and the file upload forms used by editors. Open each page in a browser with the developer console visible and look for blocked resources or framing errors. Ask the content owners to complete one representative editorial task on staging. The check is a list of blocked items, each either fixed in the configuration or accepted by the owner with a written reason. Keep the list with the policy record.
Record the policy and schedule the review
Write the final policy in the register: each header, its value, its owning layer, the configuration location, the approver and the verification evidence. Store the Drupal configuration and the web server or CDN configuration in version control, and link the register entry to the commit identifiers. Promote the same change to production through the normal release process, then repeat the verification on production using the same page list. Schedule a review of the policy after each Drupal core or Security Kit update, since upgrades can change defaults or add new response paths. Confirm the register is the place where the next reviewer will look first.
Why security headers conflict when set in two places
Security headers set in more than one layer can cancel each other out or produce a policy nobody intended. Security Kit can emit headers from Drupal, while the web server (Apache or Nginx) and the CDN may add the same headers on the way out. When two layers send the same header with different values, browsers may apply only one of them, or the stricter rule may be ignored depending on how the duplicates are ordered. Some layers also add headers the others never mention, so a header can disappear entirely if the layer that sets it is changed or removed. The result is often a site that looks fine in a staging check but fails silently in production.
This tends to surface after a CDN change, such as turning on a new edge rule or switching providers, or after a web server upgrade that changes default header behavior or the way add_header directives are inherited in Nginx. Neither change touches Drupal code, so the Security Kit settings look correct in the admin interface while the response the browser receives is different. Illustrative example: a team sets a frame-related header in Security Kit, then adds the same header at the CDN during a performance project. The browser receives two values, and the team only discovers the mismatch when a partner embed stops loading.
Name one owner for each header and record which layer is authoritative for it. Security Kit, the web server and the CDN should each have a documented list of headers they own, and no other layer should set those names. Check the live response with a header inspection tool or curl -I from outside your network after every deployment, every CDN rule change and every web server upgrade, and compare it with the expected list. Ask your CDN and hosting team to confirm how they handle headers they receive from the origin, because that behavior is provider-specific and should be verified in your own environment rather than assumed.
Checking Drupal security headers at the web server or CDN
Headers added in the web server configuration (Apache or Nginx in most Drupal estates) apply to responses the server returns, whether they come from Drupal or from static files. Start by confirming which layer owns each header in your build, because a header set in both places can appear twice or be overwritten depending on the directive used. Request representative URLs directly from the origin, bypassing the CDN, so you see exactly what the server sends.
- HTML pages: Request the home page, a content page, a node edit form and a search results page, and record every security header on each.
- Static assets: Check CSS, JavaScript, images and fonts under the public files and theme paths, since web server rules often apply to these separately from Drupal.
- Error pages: Request a missing path and a forbidden path to confirm that headers still appear on 403 and 404 responses.
- Anonymous and logged-in requests: Compare both, because some server-side rules are scoped by path or cookie.
- Intended policy: Compare results against a written header baseline that lists name, value and owner for each header.
Confirm exact header names, directive syntax and how your server handles duplicate or conditional headers against its own documentation before you change anything.
A CDN or reverse proxy can add, strip or rewrite headers after the origin responds, and it may cache a response that was produced with headers from a different request. Test through the public hostname, then compare with the origin response, so you can tell which headers the edge adds, removes or leaves unchanged. Cached responses also need checking, because a header change may not reach users until cached copies expire or are purged.
- Edge versus origin: Compare the same URL requested through the CDN and directly from the origin to find headers added or removed at the edge.
- Cache state: Repeat requests and note whether the response is served from cache, then confirm that cached copies carry the current headers.
- Purge behavior: After a header change, purge the affected paths and recheck, since a purge may cover only some URL patterns.
- Compressed and alternate responses: Test with the encodings and device types your users send, because some edge rules vary by request.
- Vendor features: Confirm with your CDN provider which header rules are configured at the edge, who can change them and how changes are logged.
Record the CDN configuration version alongside each test result so an auditor can match the evidence to the rule set that was live at the time.
When Security Kit sets headers, Drupal generates them for responses it renders, so the checks focus on whether the module's settings reach every response type. Pages served from Drupal's page cache or by a reverse proxy may carry headers generated at a different time, so test both the first request and a cached request. Reviewing the module's configuration against the response is the fastest way to find a mismatch.
- Rendered routes: Check content pages, user pages, admin routes and any custom controllers, since custom responses may bypass the default output.
- Authenticated sessions: Request as an editor or administrator as well as an anonymous visitor, and note any differences.
- Non-HTML responses: Test JSON:API, feeds or file downloads if your site serves them, and confirm which headers apply.
- Cache contexts: Verify that cached pages for different roles or languages do not carry headers meant for another context.
- Module settings review: Compare the active Security Kit configuration with your written baseline, including any options you have deliberately left off.
Confirm current Security Kit setting names and behavior on your Drupal version in the module's documentation, and document any header that a web server or CDN also sets so the owner is clear.
Security and operations considerations for header policies
Should we test a Drupal header policy before enforcing it?
Yes. Roll the policy out in report-only mode first, or stage it on a non-production copy, and collect violation reports from real browsing before you enforce anything. Headers such as a Content-Security-Policy break scripts, styles and embeds that teams forgot about, and the failures often appear only on admin pages or in logged-in workflows. Walk through the main content types, the editor screens, search, forms and any checkout or login path, and record each blocked resource. Enforce only after the list of violations is either fixed or deliberately allowed. Ask your team to repeat this check after every significant change to the theme, modules or third-party scripts.
How do we handle third-party embeds and admin tools that need header exceptions?
Treat each exception as a named decision with an owner, a reason and a review date. Start by listing every embed and tool the site loads: video players, maps, analytics, chat widgets, consent banners, and admin utilities used by editors. For each one, record the exact origins or directives it needs and whether it is needed on all pages or only on some. Scope exceptions narrowly, so an admin-only tool does not weaken public pages, and avoid broad wildcards where a specific host will do. Where a vendor cannot be scoped tightly, document the residual risk and the compensating control. Keep this list in version control next to the policy so that removing a vendor also removes its allowance.
How do header settings survive Drupal core and module updates?
Keep the header configuration in code and in exported configuration, not in ad hoc settings edited in the admin interface on one server. Security Kit settings and any CSP rules should be exported with the rest of the site configuration, so they move through the same review and deployment path as code. After each core or contributed module update, compare the live response headers with the expected set, because an update can change defaults, add new modules that set their own headers, or revert a configuration import. Include header checks in the release checklist and in the regression tests your team already runs. Read the release notes for Security Kit and related modules before upgrading, since the Drupal core release schedule places security releases on a fixed Wednesday window and urgent updates may arrive with little lead time.
How do we detect header drift after CDN or web server changes?
Monitor the headers that clients actually receive, at the edge, not only the values Drupal sends. A CDN, load balancer or reverse proxy can strip, override or duplicate headers, and a web server change can alter the order or presence of directives. Set up a scheduled check that requests key pages from outside your network and compares the returned headers against the expected policy, and alert on any difference. Include the paths that behave differently, such as cached pages, login redirects and file downloads, because they may be served by different layers. Ask your hosting or CDN team which layer owns each header, and document it, so that a change to one layer does not silently override another.
Where should we record the header policy so reviewers can check it?
Record the policy as configuration and as a short document that explains each directive. The configuration file or exported settings show what the site does, while the document explains why each value exists, which feature needs it, who approved it, and when it was last reviewed. Store both in the same repository as the site code so that changes go through pull requests and leave an audit trail. Include the expected response headers for representative pages, the list of exceptions with owners, and the steps to roll back a change. This record gives auditors and security reviewers something concrete to examine, and it also gives your own team a way to tell whether a header change was intentional. DeployOpen can prepare this documentation as part of a scoped engagement, but the controls remain your responsibility to operate.
Can a set of security headers be treated as a complete defense?
No. Security headers reduce the browser-side impact of some attacks, such as clickjacking, certain cross-site scripting paths and some framing or content-type abuse, but they do not fix vulnerable code, weak access control or unpatched modules. A Content-Security-Policy that blocks inline scripts still cannot protect a page from a flaw in a trusted script it allows, and a frame restriction does nothing against an API that accepts unauthenticated requests. Keep headers as one layer alongside timely security updates, access control, input handling and security testing. When you present the policy internally, describe what each header addresses and what it does not, so that nobody assumes a header covers a gap elsewhere.
Who should own header policy changes, and what do they cost to maintain?
Ownership should sit with a named team that can approve changes, usually a combination of the Drupal development lead, the platform or hosting team that controls the web server and CDN, and a security reviewer. Ongoing cost comes from reviewing exceptions when vendors change, testing after updates, and watching for drift, so it recurs with every release cycle rather than being a one-time task. The main drivers are how many third-party tools the site loads, how often the site is updated, and whether headers are set in several layers. Ask any partner for a quote that separates the initial policy build and testing from the recurring review and monitoring, and confirm who performs each task.
Limits of Security Kit headers and what to confirm
Security Kit headers reduce specific browser-side risks, but they sit on top of code, configuration and hosting that they do not control. The cards below set out what headers cannot fix and what your team should confirm about the module, the Drupal release and the edge layer before sign-off.
Headers do not fix vulnerable code
Response headers instruct browsers how to behave, but they do not patch a flaw in a custom module, a theme or a contributed project. A cross-site scripting bug in a block or a broken access check in a view still exists after headers are set. Confirm that your update routine tracks Drupal Security Team advisories for core and every contributed module, and that headers are treated as one layer in your defense rather than a substitute for patching.
Content Security Policy is a separate rollout
A Content-Security-Policy header is more involved than the other headers because it can block scripts, styles and embeds that the site depends on. Analytics tags, chat widgets, maps and admin tools often need explicit allowances. Plan the policy in report-only mode first, review the reports with the content and front-end teams, and confirm with your Security Kit or CSP configuration owner who maintains the allowances over time.
Server and CDN behavior varies
Headers may be set by Drupal, by the web server such as Apache or Nginx, or by a CDN or load balancer in front of the site. Duplicate or conflicting values can appear when more than one layer sets the same header, and some providers strip or override headers on cached responses. Confirm which layer owns each header, test responses through the full path your users take, and check cached and error pages as well as standard pages.
Drupal and PHP support must match
Each Drupal major version has its own PHP minimum, so the release you choose sets the runtime your hosting must provide. Confirm the Drupal version your site runs and its end-of-life date before you invest in header work, because headers configured on an unsupported release still inherit that release's unpatched risk. Check the PHP version on every environment, including staging and disaster recovery.
Module release and compatibility
Security Kit is a contributed module, so its current release, supported Drupal core versions and maintenance status can change. Read the project page on Drupal.org for the release you plan to install, confirm it supports your core version, and record the version in your build. Re-check compatibility before each Drupal core upgrade, since a module that works today may need a newer release for the next major version.
Questions buyers ask about Security Kit headers
How much effort does a Security Kit header project take?
Effort depends on how many sites, web server layers and CDN layers set headers, and how many embeds and admin tools must keep working. A single Drupal site with headers set in one place is a small change. A multisite estate with Nginx, a CDN and third-party embeds needs a mapping exercise, a report-only rollout and a sign-off cycle for each layer. No duration is given here. Ask any partner to show the effort split across discovery, configuration, testing and handover, and to name which items are one-time and which recur on each Drupal release.
What drives the cost of hardening Drupal headers?
The main cost drivers are the number of sites and hosting layers, the number of third-party scripts, frames and embeds that need allowances, and how many people must approve policy decisions. Recurring costs come from re-testing after Drupal security releases, which are published on the third Wednesday of the month, and from reviewing new embeds added by content teams. Ask for a written quote that lists each site, each layer that sets headers, the testing method, the number of review rounds and any ongoing retainer for policy upkeep.
What does our team need to provide before work starts?
You need administrative access to the web server configuration, the CDN or edge rules, and the Drupal configuration for the Security Kit module. Staging environments that match production headers are important, because a header that works on staging can break a live embed. You also need named owners who can decide on frame embedding, inline script policy and third-party domains. Without those decisions, the work stalls at the point where a header would block a feature. Confirm these owners and access paths in writing before a quote is accepted.
Can our own platform team set up Security Kit headers?
Yes, if your team has Drupal experience, control of the web server and CDN, and time to test. Drupal teams that already run updates and configuration exports usually handle the Security Kit module without outside help. The harder part is the policy work: deciding which origins may frame the site, which scripts may run, and how a header change is rolled back. Teams without a clear owner for those decisions, or without a staging copy, often choose outside help for the first rollout and then run the maintenance themselves.
Do security headers alone satisfy a vulnerability scanner or an auditor?
No. Headers reduce the exposure to clickjacking, cross-site scripting and some CSRF paths, and a scanner will often report their absence. However, a scanner passing does not prove the site is secure, and an auditor will want to see the headers, the reasoning behind each value, the test evidence and the change records. Headers also do not fix vulnerable code, weak passwords or outdated contributed modules. Treat headers as one control within a wider program, and ask any assessor which evidence they expect before you scope the work.
Where should headers be set, in Drupal, the web server or the CDN?
Set each header in the layer that controls the response, and avoid setting the same header in two layers with different values. Security Kit can set headers from Drupal, but responses served by a CDN or a static asset path may bypass Drupal entirely. Check the final response headers from outside your network after each change, because duplicated or conflicting headers are a common fault. Confirm with your Drupal and web server vendors which layer owns each header.
What should we ask for in a written quote for this work?
Ask for the sites and layers in scope, the list of headers to set and the reason for each value, the method for testing headers from outside the network, and the number of staging and production change windows. Ask who approves policy decisions, how rollback works, what documentation you receive and how the work handles future Drupal core and contributed module updates. Ask whether ongoing retainers cover re-testing after security releases. Because DeployOpen works to a written scope, a clear list of these items is what allows a fixed-scope quote.
How DeployOpen works on this
DeployOpen scopes Drupal security headers as a written piece of work covering Security Kit settings, the headers your web server or CDN adds, and how both are verified before go-live. Engagements are fixed-scope, so the header policy, test targets and handover documents are agreed before any configuration changes.
How we scope
- Inventory every site, domain and environment that will receive the header policy, including staging and admin hosts.
- Agree which headers Security Kit sets in Drupal and which ones the web server or CDN owns, so no header is set twice.
- Record embed, analytics and admin tool requirements that could conflict with clickjacking or XSS restrictions before configuration begins.
How we build
- Configure Security Kit settings in a staging copy, with changes tracked in version control and deployed through your normal release process.
- Check each response header with scripted requests from outside and inside the network, comparing results against the agreed policy.
- Test the admin interface, login pages and key content flows to confirm that the restrictions do not break editing or authentication.
How we hand over
- Deliver a written header map listing each header, its value, the layer that sets it and the reason it is set there.
- Provide the verification scripts and expected output so your team can rerun the header checks after each release or infrastructure change.
- Document the rollback steps, the owner for each header decision and the procedure for approving changes to the policy.
How we support
- Ongoing support, header rechecks after Drupal or Security Kit updates, and continuous testing are available as retainers.
- Review header changes when new contributed modules, embeds or CDN settings are added to the site.
- Confirm with your team how often headers are rechecked and who receives alerts when a header drifts from the agreed values.

