WordPress Clickjacking Protection
Setting X-Frame-Options and CSP frame-ancestors on a WordPress site.
Which method should you use?
WordPress gives you four ways to add clickjacking headers. Pick the one you can access:
- wp-config.php -- one line, no theme or plugin needed. Simple, but it sets headers via PHP. Does not work with page caching unless the cache respects PHP headers.
- Theme functions.php -- more control, uses WordPress core hooks. Best placed in a child theme so updates do not overwrite your changes.
- .htaccess (Apache) / server config -- sets headers before WordPress loads. Immune to caching issues. The most reliable method if your host runs Apache and allows .htaccess overrides.
- Security plugin -- no code needed. Several free plugins add clickjacking headers with a toggle.
Method 1: wp-config.php (one line)
Add this line to your wp-config.php file, just before the "That's all, stop editing!" comment:
// Clickjacking protection
header('X-Frame-Options: DENY');
header("Content-Security-Policy: frame-ancestors 'none'");
/* That's all, stop editing! Happy publishing. */Use SAMEORIGIN and 'self' if you embed your own pages in iframes.
Steps:
- Connect to your site via FTP, cPanel File Manager, or SSH.
- Open
wp-config.phpin the WordPress root directory. - Add the lines above before the final comment block.
- Save and upload.
Run your site through ClickJack Test to confirm the headers are present.
Method 2: Theme functions.php (via wp_headers filter)
WordPress has a built-in filter for modifying response headers. Add this to your theme's functions.php file:
/**
* Add clickjacking protection headers.
* Use a child theme so this survives parent theme updates.
*/
function cjt_add_security_headers($headers) {
$headers['X-Frame-Options'] = 'DENY';
$headers['Content-Security-Policy'] = "frame-ancestors 'none'";
return $headers;
}
add_filter('wp_headers', 'cjt_add_security_headers');If you already use a child theme, paste the snippet into its functions.php. If you do not, create one -- child themes take five minutes and prevent your changes from disappearing when the parent theme updates.
WordPress applies this filter during WP::send_headers(), which runs on every page request. The same caching caveat applies as with wp-config.php: if a page cache plugin serves a static HTML file, WordPress never runs, so the filter never fires.
Method 3: .htaccess (Apache)
If your host runs Apache and allows .htaccess overrides, this is the most reliable method. Headers are set at the server level before WordPress loads, so they apply to cached pages, error pages, and static files.
Add this to the .htaccess file in your WordPress root directory:
# Clickjacking protection -- add inside the WordPress .htaccess file
<IfModule mod_headers.c>
Header always set Content-Security-Policy "frame-ancestors 'none'"
Header always set X-Frame-Options "DENY"
</IfModule>The IfModule block prevents a 500 error if mod_headers is not loaded. On most hosts it is. The always keyword ensures headers appear on error responses (404, 500) as well as success pages.
Put these lines above the WordPress rewrite block (the # BEGIN WordPress section). WordPress may overwrite anything inside that block during updates.
Method 4: Security plugins
Several free plugins add clickjacking headers with a toggle. Useful if you want a UI and do not want to touch code.
Really Simple Security (free)
Formerly Really Simple SSL. Under WordPress Hardening > Security Headers, toggle on X-Frame-Options. The free version sets SAMEORIGIN. The Pro version also generates a full Content-Security-Policy header with frame-ancestors.
Since version 9.0.0 (September 2024), Really Simple Security prefers CSP frame-ancestors over X-Frame-Options. Its CSP generator builds a policy based on your installed plugins and theme.
Sucuri
If you use the Sucuri Website Firewall (WAF), security headers are enabled by default. Check under Firewall > Security > Additional Security Headers. This adds X-Frame-Options, X-XSS-Protection, and X-Content-Type-Options.
The free Sucuri Security plugin (auditing and malware scanning) does not add security headers. Headers come from the WAF product, which is a paid service.
Shield Security (free)
Shield Security has a dedicated HTTP Headers section under Security Zones > HTTP Headers. The Basic HTTP Headers toggle adds X-Frame-Options and several other security headers in one click.
What about NGINX?
If your WordPress host runs NGINX, you cannot use .htaccess. Add headers in your NGINX server block instead. See the NGINX guide for the full walkthrough.
Managed WordPress hosts (Kinsta, WP Engine, Pressable) use NGINX and may not give you access to the server config. In that case, use the wp-config.php or functions.php method, or ask support to add the headers for you.
A note on the meta tag that does not work
You may see advice to add this to your WordPress header:
<meta http-equiv="X-Frame-Options" content="DENY">This does nothing. Browsers do not honor X-Frame-Options as a meta tag. Only an HTTP response header works. If a security scanner recommended this, the scanner is wrong.
Verify it is working
After adding the headers, check that they appear:
curl -I https://yoursite.comLook for X-Frame-Options: DENY or Content-Security-Policy: frame-ancestors 'none' in the output.
Also check a cached page if you use a caching plugin. If the header is missing on cached responses, the PHP methods (wp-config.php, functions.php) are not enough -- switch to .htaccess or server config.
Then run it through ClickJack Test to confirm.