Content Security Policy (CSP) is a powerful security feature that most WordPress site owners overlook. Learn how to implement CSP using WP Engine's Web Rules to protect your site from malicious scripts and cross-site scripting attacks.
- Implementing a Content Security Policy (CSP) adds a crucial security layer to WordPress sites, preventing cross-site scripting and malicious code injection.
- CSP acts like a security guard for your site, specifying which files and scripts browsers can safely load, thereby blocking harmful scripts.
- WP Engine's Web Rules settings offer a straightforward way to configure CSP, with varying levels of security from quick implementations to advanced nonce-based setups.
- Testing and careful planning are essential, starting with policies in a staging environment and refining rules to balance security and functionality.
Your WordPress site’s security setup might be incomplete, even with quality hosting. While WP Engine provides solid platform protection, the majority of websites remain vulnerable to cross-site scripting attacks that can slip through standard defenses.
The often-overlooked solution? Content security policy configuration.
Most site owners never explore WP Engine’s Web Rules settings, missing an opportunity to add an important security layer. Without proper CSP headers, malicious scripts can potentially execute and compromise your visitors’ data.
Implementing content security policy creates an additional defense that helps block these attacks before they impact your site.
Ready to strengthen your security setup? We’re going to delve deep into details about the whole process of implementing CSP header.
Understanding Content Security Policy fundamentals
Content Security Policy is like a security guard for your website. It tells your visitor’s browser which files and scripts are safe to load.
Key security benefits
CSP blocks harmful scripts from running on your site. Furthermore, it prevents hackers from stealing visitor data and stops malicious code injection. And as a bonus, it can make your site load faster by blocking unwanted content. Learn more about the benefits of a faster website.
Core directives
Each directive controls a specific type of content:
- default-src: Sets basic rules for all content types (JavaScript, images, CSS, fonts)
- script-src: Controls which JavaScript files can run
- style-src: Manages CSS stylesheets and design files
- img-src: Decides which images can load
- font-src: Controls web font sources
- connect-src: Manages data connections and API calls
- frame-src: Controls embedded content like videos
- object-src: Handles plugins and embedded objects
Each directive works as a filter that checks content before allowing it to load.
Step-by-step CSP implementation in WP Engine
Step 1: Planning your CSP strategy
Before adding any rules, check what your site uses. Look for plugins that load external scripts, themes with custom fonts, and any third-party tools like analytics or chat widgets. Make a list of all outside resources your site needs to work properly.
Step 2: Testing and validation
Before implementing CSP, set up your testing tools:
Browser developer tools
Open your browser’s developer tools and check the Console tab. When CSP blocks something, you’ll see violation messages that explain what was blocked and why. These messages help you understand what needs fixing.
You can also measure rendering time using Chrome dev tools to ensure CSP implementation doesn’t negatively impact performance.
CSP testing tools
Use these free online tools to check your CSP:
- Mozilla Observatory (https://observatory.mozilla.org/) – Tests your site’s security headers and gives you a grade
- CSP Evaluator (https://csp-evaluator.withgoogle.com/) – Google’s tool that checks for CSP bypasses and weaknesses
- Security Headers (https://securityheaders.com/) – Quick security header scan
Screaming Frog SEO Spider testing
Use Screaming Frog’s SEO Spider to crawl your site and check for CSP-related issues. This helps catch problems across your entire site that you might miss during manual testing.
You’ll be able to find details about which page is missing the security header under the Issues tab on the right sidebar, and if you select any of the issues detected, the specific pages for that issue will appear on the left side.
Report-only mode
Start with Content-Security-Policy-Report-Only instead of Content-Security-Policy. This mode reports violations without blocking content, letting you test safely before going live.
WP Engine testing tips
Always test CSP changes on your staging site first. Check Web Rules order carefully since rules at the top take priority. You should also monitor your site’s loading speed after adding CSP rules.
Here is also a checklist of what you should check:
- Check console errors in browser developer tools for CSP violations
- Test visual elements like image galleries, sliders, and video embeds
- Verify functional features work properly:
- Contact forms and newsletter signups
- Pop-ups and modal windows
- Shopping cart and checkout processes
- Login and registration forms
- Test third-party integrations (analytics, chat widgets, social media feeds)
Step 3: Choose your implementation level
Select the approach that best fits your technical comfort level and security requirements. Each level offers different trade-offs between security, flexibility, and ease of implementation:
Level 1 (Quick Implementation) provides basic protection with minimal effort but uses permissive settings like ‘unsafe-inline’ and ‘unsafe-eval’ that significantly reduce security benefits. While this approach prevents your site from breaking, it leaves you vulnerable to many of the attacks CSP is designed to prevent.
Level 2 (Manual Configuration) offers much better security by explicitly defining allowed sources, but creates a rigid policy that can break when you add new plugins, themes, or third-party services. Every time you update your site’s functionality, you’ll need to manually update your CSP policy to accommodate new resources.
Level 3 (Nonce-Based Implementation) provides the most robust security while maintaining flexibility. By using dynamically generated nonces, you can allow legitimate inline scripts while blocking malicious ones. This approach requires more technical setup but offers the best balance of security and functionality for WordPress sites.
Level 1: Quick implementation (Beginner)
This is the fastest way to add CSP protection without breaking your site. In your WP Engine dashboard, go to Web Rules and click the Header rules tab. Click “Add header rule” and set up:
- Action: Set
- Name: Content-Security-Policy
- Value: default-src ‘self’ ‘unsafe-inline’ ‘unsafe-eval’; img-src ‘self’ data:; font-src ‘self’ data:; connect-src ‘self’;
This policy allows most WordPress functionality while providing basic protection. The ‘unsafe-inline’ and ‘unsafe-eval’ settings reduce security but prevent most site breakage.
Level 2: Manual configuration (Intermediate)
For better security, manually identify and whitelist your site’s resources.
- Start with a restrictive policy: default-src ‘self’;
- Test your site and check console errors
- Add specific domains for each broken resource
- Gradually build a comprehensive policy like:
default-src 'none';
script-src 'self' https://www.googletagmanager.com https://js.hs-scripts.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://secure.gravatar.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.hubapi.com;
frame-src 'self' https://www.youtube.com;
object-src 'none';
You can also use the CSP Generator extension (csper.io) to help analyze your site and automatically generate appropriate CSP policies based on the resources it detects. However you need to keep in mind that you’ll need to visit and analyze different pages so it will create a CSP header for the whole site. If anything on the site breaks after adding the CSP header, you should check errors displayed in the Console tab of your browsers developer tools and add the missing values to the header to account for the resources that were denied, which caused the breakage on your site.
Level 3: Nonce-based implementation (Advanced)
The most secure approach uses nonces to allow specific inline scripts. Add this code to your theme’s functions.php and edit the CSP header value like you would in the 2. Level with manual configuration we described above while leaving the dynamic nonce value definition as it is:
function generate_csp_nonce() {
static $nonce = null;
if ($nonce === null) {
$nonce = wp_create_nonce('script_nonce_' . time());
}
return $nonce;
}
// Send CSP header early in the request
add_action('send_headers', function() {
if (!is_admin()) {
$nonce = generate_csp_nonce();
header("Content-Security-Policy: default-src 'self' example.com *.example.com; style-src 'self' example.com *.example.com 'unsafe-inline' fonts.googleapis.com; script-src 'self' example.com *.example.com 'nonce-{$nonce}' www.googletagmanager.com {$unsafe_eval}; img-src 'self' example.com *.example.com data:; font-src 'self' example.com *.example.com fonts.gstatic.com; connect-src 'self' example.com *.example.com; frame-src 'self' example.com *.example.com; object-src 'none';");
}
});
// Start output buffering to add nonces to scripts
add_action('init', function() {
if (!is_admin()) {
ob_start(function($html) {
$nonce = generate_csp_nonce();
$html = preg_replace_callback(
'/<script\b(?![^>]*\bnonce\s*=)([^>]*)>/i',
function($matches) use ($nonce) {
return '<script nonce="' . esc_attr($nonce) . '"' . $matches[1] . '>';
},
$html
);
return $html;
});
}
}, 1);
// Add nonce to enqueued scripts
add_filter('script_loader_tag', function($tag, $handle, $src) {
$nonce = generate_csp_nonce();
if (strpos($tag, 'nonce=') === false) {
$tag = str_replace('<script', '<script nonce="' . esc_attr($nonce) . '"', $tag);
}
return $tag;
}, 10, 3);
// Handle inline scripts
add_filter('wp_inline_script_attributes', function($attributes) {
$nonce = generate_csp_nonce();
$attributes['nonce'] = $nonce;
return $attributes;
});
// Process output buffer
add_action('shutdown', function() {
while (ob_get_level()) {
ob_end_flush();
}
}, 999);
This method is mostly intended to crack down on inline script execution. Therefore you should also add another header rule in the WP Engine to account for wp-content and wp-includes files for which the header we defined with code above may not be sent when they’re accessed directly.
This is where the conditions to refine rules come in handy. They let you apply CSP rules to specific parts of your site. Furthermore, all of them must be true for the rule to be sent. This helps target certain pages or urls without affecting your entire site.
Real-world condition example
Here’s how to target WordPress core directories:
- Type: URI (web address)
- Operator: Regex matches
- Value: ^/(wp-content|wp-includes).*
This rule only applies to WordPress content and includes folders.
Common WordPress patterns
- ^/wp-admin.* targets the admin area only
- ^/wp-login.php affects just the login page
- ^/(wp-content|wp-includes).* covers WordPress core files
Available condition types
- URI conditions: Target specific web addresses
- IP address conditions: Control access by location
- User agent conditions: Target specific devices or browsers
- Header conditions: Advanced targeting options
Best practices
Rules are read top to bottom – order matters. Put exception rules before general rules and always test conditions carefully before going live.
Conclusion
Implementing content security policy strengthens your site’s defense against common attacks like cross-site scripting and code injection. While the process requires careful planning and testing, WP Engine’s Web Rules settings make CSP configuration straightforward and manageable.
Start with basic policies, test thoroughly on staging, and gradually refine your rules using conditions when needed. Remember that some WordPress functionality may require security compromises like ‘unsafe-inline’, but the overall protection CSP provides far outweighs these trade-offs.