← Stack Guides

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.
Server-level beats PHP-level. When possible, add headers in .htaccess or your web server config. Headers set at the server level apply to every response, including cached pages, error pages, and static files that bypass WordPress entirely.

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:

  1. Connect to your site via FTP, cPanel File Manager, or SSH.
  2. Open wp-config.php in the WordPress root directory.
  3. Add the lines above before the final comment block.
  4. Save and upload.

Run your site through ClickJack Test to confirm the headers are present.

Caching caveat. If your site uses a page caching plugin (WP Super Cache, W3 Total Cache, WP Rocket) or a CDN with HTML caching, cached pages are served as static files that bypass PHP. The headers set in wp-config.php will not appear on cached pages. Use the .htaccess method or server config instead.

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.

Wordfence does not set security headers. Wordfence is a firewall and malware scanner. It does not add X-Frame-Options, CSP, or other HTTP security headers. If you rely on Wordfence alone, your site likely has no clickjacking protection. Add headers using one of the methods above.

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.com

Look 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.