Security Headers for Magento 2: Complete Setup Guide
I scanned 38 Tyrolean sites from a hand-built Magento prospect list for basic security headers, 33 of them confirmed Magento shops. The average shop was missing 4 out of 7 critical protections. Two shops had zero.
One shop had its .env file publicly accessible. HTTP 200, downloadable by anyone with a browser. That file typically contains database passwords, API keys, and encryption keys. It’s not a sophisticated hack. It’s the digital equivalent of leaving your safe open with a sign that says “passwords inside.”
What are security headers?
Security headers are instructions your website sends to visitors’ browsers. They tell the browser: lock the door, don’t load things from unknown addresses, don’t let this page be embedded in a fake site.
They take a few lines to configure. They protect against entire categories of attacks. And the vast majority of shops simply don’t have them.
Here’s what the audit of those 38 sites found.
The 7 headers and what happens without them
Strict-Transport-Security (HSTS) forces all connections to use HTTPS. Without it, someone on public WiFi at a café or hotel can intercept the session between your customer and your shop, including login credentials and session tokens. Missing on 81% of shops.
Content-Security-Policy (CSP) controls which scripts are allowed to run on your pages. Without it, an attacker who finds any vulnerability can inject whatever they want: credit card skimmers, payment redirects, malware. This is how Magecart attacks work, and they’ve been hitting e-commerce shops for years. Missing on 73% of shops.
X-Frame-Options prevents your site from being loaded inside an invisible frame on another page. Without it, attackers can overlay fake buttons on top of your real checkout, a technique called clickjacking. Someone clicks what looks like “Place Order” and sends payment somewhere else entirely. Only 11% of shops were missing this one, the best score in the audit.
X-Content-Type-Options stops browsers from guessing file types. Without it, a file that looks like an image could execute as JavaScript if the browser decides to interpret it that way. Missing on 16% of shops.
Referrer-Policy controls what URL information leaks when your customers click external links. Without it, order confirmation page URLs, internal search queries, and customer account pages get sent as referrer data to every external script on your site: analytics providers, ad networks, third-party tools. Your customers’ activity becomes visible to parties it was never meant to reach. Missing on 92% of shops.
Permissions-Policy controls which browser features scripts on your site can access: camera, microphone, geolocation, payment APIs. Without it, malicious scripts embedded through any vulnerability can request access to device features. Missing on 97% of shops. Worst score in the audit.
X-XSS-Protection is a legacy header that enables the browser’s built-in cross-site scripting filter. Modern browsers have largely moved on from it, but it still provides a layer of defence in older ones. Missing on 19% of shops.
The broader picture
97% missing at least one critical header is a striking number, but the distribution tells the real story.
Seven shops were missing 5 or more headers out of 7. Two were missing all 7, no protections whatsoever. Of the three Magento 1 shops I found (software that’s been EOL since 2020, covered in detail here), one had all 7 missing alongside an unprotected admin panel.
The shops scoring well on headers weren’t doing anything exotic. They just had a server configuration that someone had thought about at some point.
How to check your own shop
The quickest way: open your browser, go to your shop, press F12, click the Network tab, reload the page, click the first request in the list, and look at the Response Headers section. The headers above should be in that list.
A faster option: securityheaders.com will scan your domain and give you a letter grade with a breakdown of what’s missing and what it means.
If you’re seeing an F or a D, that’s not unusual based on what we found. It’s also not something you have to accept.
How to fix it
Six of the seven are a configuration change. Here they are, with values that work for a Magento shop.
nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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=(), payment=(self)" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
Two details in there matter more than the values.
always sends the header on error responses as well. Without it, your 404 and 500 pages go out unprotected, and an attacker gets to choose which page you see.
nginx does not inherit add_header. If any more specific location block sets a header of its own, every add_header from the parent block stops applying inside it. So after you deploy this, check a page served by a location block, not just the home page.
Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
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=(), payment=(self)"
payment=(self) rather than payment=(): an empty value switches off the Payment Request API for your own checkout too, and some payment methods use it.
Block the .env file while you are in there
location ~ /\.(env|git) {
deny all;
return 404;
}
If you are on Plesk, two warnings
Paste these into the domain’s Additional nginx directives field, and keep them in server context only. An http-context directive in that box applies to every domain on the box, not just yours.
And keep a copy in version control. Anything typed into a hosting panel has no history, no review and no backup. I lost five working redirect rules that way: a later paste over the same field deleted them, and the links kept pointing at a 404 for ten weeks before anyone noticed.
CSP is a project, not a line
Content-Security-Policy is the one to leave out of the batch above. A policy strict enough to be worth having blocks inline scripts, and a Magento frontend is full of them. Set it to Content-Security-Policy-Report-Only first, collect violations for a week, and only then enforce. Hyvä makes this far more achievable than Luma does, because it ships almost no inline JavaScript. My open-source cookie consent module was written to work under CSP strict mode for that reason.
Rescan after deploying. The six above should take you from an F to an A.
The .env story
That shop with the publicly accessible .env file: to be clear about what that means, this file is not supposed to be served over HTTP at all. It should be blocked at the server level. When it isn’t, anyone who requests it gets the file contents back: database hostname, username, password, API credentials, encryption keys.
With that file, an attacker has everything needed to access the database directly, bypass authentication, and extract every customer record in the system.
It was 2,078 bytes. Downloadable in milliseconds.
This isn’t a Magento-specific problem; it affects any PHP application where the web server isn’t configured to block it. But it’s a reminder that security headers are one part of a broader picture. The goal isn’t to tick seven boxes; it’s to close the gaps that attackers actually use.
securityheaders.com will give you your score in under a minute. If you want a broader look, including common misconfigurations beyond the headers, feel free to get in touch.
The next article looks at what the same audit found on the performance side, and why most Magento shops in Tirol are leaving conversion rate on the table.