WordPress powers ~43% of the web — and adds zero HTTP security headers by default. It also broadcasts its version via a <meta name="generator"> tag on every page, giving attackers a one-step path to known CVEs. This guide walks through fixing every WordPress-related finding from our Security Audit: removing the version meta, adding the six security headers, hardening wp-config.php, and dealing with cookies. Works on WordPress 6.x and any hosting environment.
By default, WordPress adds <meta name="generator" content="WordPress 6.4.2"> to every page's HTML head. That single line tells anyone — including automated CVE scanners — exactly which version of WordPress you're running. Remove it.
functions.php file. If you use a third-party theme, edit the child theme's functions.php — never the parent — or the change is wiped on theme updates.
// Remove WordPress version from generator meta
remove_action('wp_head', 'wp_generator');
// Also strip version from CSS and JS file URLs
function aw_remove_version_from_assets( $src ) {
if ( strpos( $src, 'ver=' ) ) {
$src = remove_query_arg( 'ver', $src );
}
return $src;
}
add_filter( 'style_loader_src', 'aw_remove_version_from_assets', 9999 );
add_filter( 'script_loader_src', 'aw_remove_version_from_assets', 9999 );
// Remove version from RSS feeds
add_filter( 'the_generator', '__return_empty_string' );
Save. View page source on the live site — you should no longer see the WordPress version anywhere.
Six headers cover the audit's HTTP Security Headers section: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Permissions-Policy. The best place to add them depends on your hosting.
Adding headers at the nginx or Apache layer is faster than at the PHP layer. See the Plesk fix guide or the upcoming nginx/Apache/cPanel guides for server-level instructions. WordPress works correctly behind any of these.
On managed WordPress hosting where you can't touch nginx or Apache, add headers via the send_headers action:
add_action( 'send_headers', function() {
header( "Strict-Transport-Security: max-age=31536000; includeSubDomains" );
header( "X-Frame-Options: SAMEORIGIN" );
header( "X-Content-Type-Options: nosniff" );
header( "Referrer-Policy: strict-origin-when-cross-origin" );
header( "Permissions-Policy: geolocation=(), microphone=(), camera=()" );
header( "Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-ancestors 'self'" );
});
'unsafe-inline' in script-src and style-src is needed because WordPress core and most plugins emit inline JS/CSS. Removing it will break the admin and most page builders. This is a real trade-off — WordPress was not designed CSP-first.Several security wins happen in wp-config.php — the WordPress configuration file in your site's document root. Edit via SFTP or your hosting file manager.
define( 'DISALLOW_FILE_EDIT', true );You and your team will need to edit files via SFTP from now on. That is the point.
define( 'FORCE_SSL_ADMIN', true );If your site is behind a reverse proxy (Cloudflare, load balancer), also add:
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
$_SERVER['HTTPS'] = 'on';
}
Without this, WordPress thinks it's serving over HTTP and may set cookies without the Secure flag.
AUTH_KEY/AUTH_SALT lines used to sign cookies and hash passwords. If they are still the defaults from when you installed WordPress years ago, rotate them. Generate new values at api.wordpress.org/secret-key/1.1/salt/ and replace the eight lines in wp-config.php. All users will be logged out and need to log in again — that's expected and is the point.
WordPress sets a handful of session cookies (wordpress_, wordpress_logged_in_, wp-settings-). The audit checks for Secure, HttpOnly and SameSite flags. WordPress sets HttpOnly correctly by default. Secure depends on your HTTPS configuration. SameSite is the modern gap.
Add this to functions.php to enforce all three on WordPress's session cookies:
add_action( 'init', function() {
if ( ! headers_sent() ) {
$secure = is_ssl();
$httponly = true;
$samesite = 'Lax';
session_set_cookie_params([
'lifetime' => 0,
'path' => COOKIEPATH ?: '/',
'domain' => COOKIE_DOMAIN,
'secure' => $secure,
'httponly' => $httponly,
'samesite' => $samesite,
]);
}
});
For cookies set by plugins, you may need to also add header-level overrides via your web server, since WordPress can't retroactively modify cookies third-party code has already sent.
WordPress ships with several files that broadcast its presence even if you remove the generator meta. They aren't strictly required and can be deleted or 404'd.
| Path | What it leaks | Action |
|---|---|---|
/readme.html | WordPress version, install date | Delete from your site root |
/license.txt | Confirms WordPress | Delete from your site root |
/wp-config-sample.php | Confirms WordPress | Delete from your site root |
/xmlrpc.php | WordPress XML-RPC endpoint, attack target | Block via .htaccess if not in use |
Block XML-RPC if you don't use Jetpack, mobile WordPress apps or any service that requires it:
// In .htaccess (Apache) — return 403 for /xmlrpc.php
<Files xmlrpc.php>
Order Deny,Allow
Deny from all
</Files>
Removing the generator meta hides the version from casual fingerprinting, but the actual CVE risk only goes away when you patch. The audit's CVE check via NVD will not flag WordPress on a clean site without an exposed version. But that doesn't mean you can skip updates — set WordPress core, plugins and themes to auto-update major releases. Add to wp-config.php:
define( 'WP_AUTO_UPDATE_CORE', true );
After applying these fixes, run the Security Audit again to confirm your score has improved. Each fix should move you closer to 100/100.
Run free security audit →