Site security is multi layered and you should always look for ways to improve your sites. Security Headers is probably one of the most overlooked/ignored security steps that’s also one of the easiest. If you’re unsure what a security header is, check out this article first: Security Headers Explained in Simple Terms to understand the security headers and options I’m sharing with you.
Before you get started, go to SecurityHeaders.com and scan your site. There’s no shame if you get an “F” because I’ll help you to get an A+ in just a few minutes.
If you are using WordPress, the quickest way to implement this is literally by Installing and Activating the Headers Security Advanced & HSTS WP (not my plugin) from within the plugin area of your dashboard. It will automatically implement the most commonly used security headers for you once you activate it.
If you prefer to not use another plugin, or if you’re not using WordPress, read on because I’ll explain how to add them to your htaccess file directly or by code snippet. One thing to note, if you’re using WordPress and delete/rewrite your htaccess using permalinks, it will remove the security headers and you will have to re-add them. However the code snippet will rewrite back to the htaccess file.
This first option is adding directly to your htaccess file. This is best for static sites that will virtually never change the htaccess file. Simply copy/past this full text below and add it to the very end of your htaccess file, then save. Verify your site is accessible and then rerun the scan on the SiteHeaders.com site to verify the newly added security headers. Click here to skip straight to the code snippet version.
# BEGIN Header Security & HSTS
<IfModule mod_headers.c>
Header set Access-Control-Allow-Methods "GET,POST"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Content-Security-Policy "upgrade-insecure-requests; frame-ancestors 'self'"
Header set Cross-Origin-Embedder-Policy "unsafe-none; report-to='default'"
Header set Cross-Origin-Embedder-Policy-Report-Only "unsafe-none; report-to='default'"
Header set Cross-Origin-Opener-Policy "unsafe-none"
Header set Cross-Origin-Opener-Policy-Report-Only "unsafe-none; report-to='default'"
Header set Cross-Origin-Resource-Policy "cross-origin"
Header set Permissions-Policy "accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(self), encrypted-media=(), fullscreen=*, geolocation=(self), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=*, picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), gamepad=(), serial=()"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Strict-Transport-Security "max-age=63072000"
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-Permitted-Cross-Domain-Policies "none"
</IfModule>
# END Header Security & HSTS
Code Snippet
Here’s the third and final option on adding security headers to your site. Simply copy/paste the following php into your Code Snippet plugin of choice, I prefer WPCodeBox. Then verify your site is functional and rerun the scan again to verify your security headers.
I have only tested this code on Apache servers, so I can’t promise it will work on others. My recommendation is to be ready just in case it throws a fatal error.
This code snippet will attempt to add the security headers for you to your htaccess file. Please tweak the security options to your liking before using this, but as is, will implement basic “must have” options. This snippet will do the following:
- Check for existing headers by marker id (Begin/End Header Security & HSTS) and add them if missing.
- Compare the existing headers against any changes you’ve made and update if necessary.
- Remove any blank line breaks (aka whitespace) within the full header code to make it as clean as possible.
- Will log any errors (if it’s not able to access or write to the htaccess file) to your server logs.
Please note that the code snippet is a work in progress and is a use at your own risk at this time.
<?php
// This Code Snippet by JeffBrigman.com
$htaccessPath = ABSPATH . '.htaccess';
$beginMarker = "# BEGIN Header Security & HSTS";
$endMarker = "# END Header Security & HSTS";
// Security Headers, edit only between the IFModule tags
$securityHeaders = <<<EOD
$beginMarker
<IfModule mod_headers.c>
Header set Access-Control-Allow-Methods "GET,POST"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Content-Security-Policy "upgrade-insecure-requests; frame-ancestors 'self'"
Header set Cross-Origin-Embedder-Policy "unsafe-none; report-to='default'"
Header set Cross-Origin-Embedder-Policy-Report-Only "unsafe-none; report-to='default'"
Header set Cross-Origin-Opener-Policy "unsafe-none"
Header set Cross-Origin-Opener-Policy-Report-Only "unsafe-none; report-to='default'"
Header set Cross-Origin-Resource-Policy "cross-origin"
Header set Permissions-Policy "accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(self), encrypted-media=(), fullscreen=*, geolocation=(self), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=*, picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), gamepad=(), serial=()"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Strict-Transport-Security "max-age=63072000"
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-Permitted-Cross-Domain-Policies "none"
</IfModule>
$endMarker
EOD;
// Ensure consistent newline characters across environments
$securityHeaders = str_replace("\r\n", "\n", $securityHeaders);
$securityHeaders = str_replace("\r", "\n", $securityHeaders);
if (file_exists($htaccessPath) && is_writable($htaccessPath)) {
$htaccessContent = file_get_contents($htaccessPath);
// Normalize line endings in the existing content for consistency
$htaccessContent = str_replace("\r\n", "\n", $htaccessContent);
$htaccessContent = str_replace("\r", "\n", $htaccessContent);
$pattern = '/' . preg_quote($beginMarker, '/') . '.*?' . preg_quote($endMarker, '/') . '/s';
// Determine if the security headers block already exists
if (preg_match($pattern, $htaccessContent)) {
// Replace the existing security headers block
$updatedContent = preg_replace($pattern, $securityHeaders, $htaccessContent, 1);
} else {
// Append the new security headers block, ensuring not to introduce extra new lines
$updatedContent = rtrim($htaccessContent) . "\n\n" . $securityHeaders;
}
// Write the updated content back to the .htaccess file
if (file_put_contents($htaccessPath, $updatedContent)) {
error_log("Security headers have been successfully updated.");
} else {
error_log("Failed to update the .htaccess file.");
}
} else {
error_log(".htaccess file does not exist or is not writable.");
}
In the event you missed the link at the very beginning explaining Security Headers and specifically what’s going on with the ones used here, here’s that link again. Please read over it to understand what these security headers are doing: Security Headers Explained in Simple Terms