2026年11月12日

2026年11月12日

Hide the WordPress Version: Generator Tag, ver= and readme.html

This article is a localized edition of the Japanese original. Read the Japanese original

Out of the box, WordPress writes its version number into your page HTML and feeds. Attackers can use it as a hint to check whether you run a version with known vulnerabilities. This article shows where the version appears and how to hide it. The information is current as of October 2026.

The first thing to understand is that hiding the version is a supplementary measure. The version can also be guessed from things like the contents of CSS and JavaScript files, so hiding it does not guarantee nobody can identify it. What helps most is keeping WordPress core, plugins and themes up to date.

Where the version appears

Location What is shown Fix
The HTML <head> A tag such as <meta name="generator" content="WordPress 6.9.4"> The the_generator filter
RSS and Atom feeds A line such as <generator>https://wordpress.org/?v=6.9.4</generator> Also the the_generator filter
CSS and JavaScript URLs A ?ver= such as style.min.css?ver=6.9.4 The style_loader_src and script_loader_src filters
HTTP headers X-Powered-By: PHP/8.3.x (the PHP version, not the WordPress version) expose_php in php.ini
readme.html That the site runs WordPress (the current readme.html does not contain the version number) Deny access to it

The site information returned by the REST API at /wp-json/ (name, description, URL, time zone, namespaces and so on) does not include the WordPress version. You do not need to trim REST API output to hide the version.

Step 1: Check what is currently exposed

Replace the URL with your own site and run these on your computer or server. If you use a caching plugin or a CDN, clear the cache after making changes before you check again.

# generator tag in the HTML
curl -s https://example.com/ | grep -i 'name="generator"'

# generator in the feed
curl -s https://example.com/feed/ | grep -i '<generator>'

# list of ver= values on CSS and JS
curl -s https://example.com/ | grep -oE 'ver=[0-9][0-9.]*' | sort | uniq -c

# X-Powered-By HTTP header
curl -sI https://example.com/ | grep -i 'x-powered-by'

# whether readme.html is readable (200 means it is)
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/readme.html

If the ver= list contains the same value as the WordPress version shown under "Dashboard" → "Updates", those CSS and JS files reveal your WordPress version. When WordPress core or a theme loads CSS or JS without specifying a version, the WordPress version goes into ver=.

Step 2: Decide where to put the code

Put the code in this article in one of these places.

  • An mu-plugin (recommended): a PHP file placed in the wp-content/mu-plugins/ folder (create it if it does not exist) loads without being activated, and it keeps working when you switch themes
  • A child theme's functions.php: code in the parent theme's functions.php is lost when the theme updates

For an mu-plugin, save all the code from the next step in a single file with a name such as wp-content/mu-plugins/hide-wp-version.php.

Step 3: Hide the version in the generator tag and ver=

<?php
/**
 * Plugin Name: Hide WordPress Version
 * Description: Hides the WordPress version in the generator tag and in ver= on CSS and JS.
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// Empty the HTML <meta name="generator"> and the feed <generator>.
add_filter( 'the_generator', '__return_empty_string' );

/**
 * Replace ver= on CSS and JS with a different value only when it equals the WordPress version.
 * Because ver= is replaced rather than removed, browser caches still refresh after a WordPress update.
 */
function wpdr_replace_wp_version_query( $src ) {
	if ( ! is_string( $src ) || '' === $src ) {
		return $src;
	}

	$query = wp_parse_url( $src, PHP_URL_QUERY );
	if ( empty( $query ) ) {
		return $src;
	}

	parse_str( $query, $args );
	$wp_version = get_bloginfo( 'version' );

	if ( isset( $args['ver'] ) && $args['ver'] === $wp_version ) {
		$src = add_query_arg( 'ver', substr( wp_hash( $wp_version ), 0, 10 ), $src );
	}

	return $src;
}
add_filter( 'style_loader_src', 'wpdr_replace_wp_version_query', 20 );
add_filter( 'script_loader_src', 'wpdr_replace_wp_version_query', 20 );

Key points:

  • The the_generator filter affects both the HTML generator tag and the feed generator. remove_action( 'wp_head', 'wp_generator' ); removes only the HTML tag and does not affect feeds
  • If you remove every ?ver=, browsers may keep using old CSS and JS after you update plugins or themes. The code above replaces the value only when it equals the WordPress version, using a different string derived from the version. The value changes when you update WordPress, so caches still refresh
  • It does not change ver= values that plugins or themes set to their own version numbers

After saving, use the commands from Step 1 to confirm that no generator appears and that the WordPress version no longer shows up in the ver= list. If you use a caching plugin, clear the cache before checking.

Step 4: Stop the X-Powered-By header

X-Powered-By is a header added by PHP, not WordPress, and it reveals the PHP version. To stop it, set expose_php to Off in the PHP configuration file (php.ini). This setting can only be changed in php.ini.

expose_php = Off

Here is an example for PHP-FPM on Ubuntu on a VPS (change 8.3 to the PHP version you use).

# check the current setting
grep -n '^expose_php' /etc/php/8.3/fpm/php.ini

# restart PHP-FPM after editing
sudo systemctl restart php8.3-fpm

In the php.ini shipped with Ubuntu packages, expose_php = Off is the default. If the header still appears, check whether a different php.ini is being used. On shared hosting, some providers let you change this in the control panel's PHP settings and some do not.

If you cannot change php.ini, you can remove the header from WordPress code instead. Note that send_headers is a hook that runs when WordPress serves front-end pages, so it does not affect the admin screens or the login screen.

add_action( 'send_headers', function () {
	header_remove( 'X-Powered-By' );
} );

Step 5: Deny access to readme.html and license.txt

readme.html and license.txt are WordPress core files. The current readme.html does not contain the version number, but it does show that the site runs WordPress.

Even if you delete them, they come back when you update WordPress core. Denying access in the web server takes less work than deleting them.

Apache (.htaccess)

Add this to the .htaccess file in your WordPress folder. WordPress rewrites everything between # BEGIN WordPress and # END WordPress, so put it outside that block. This is Apache 2.4 syntax (Order and Deny from all are the old Apache 2.2 syntax).

<FilesMatch "^(readme\.html|license\.txt)$">
    Require all denied
</FilesMatch>

Nginx

Add this to your site's server block, test the configuration, then reload.

location ~* ^/(readme\.html|license\.txt)$ {
    deny all;
}
sudo nginx -t && sudo systemctl reload nginx

After adding it, run curl -s -o /dev/null -w '%{http_code}\n' https://example.com/readme.html and confirm it returns 403.

What hiding does not prevent

  • The contents of WordPress CSS and JS files differ slightly between versions, so the version can be guessed by fetching and comparing those files
  • Plugin versions can sometimes be read from a plugin's or theme's readme.txt or ver=. Plugin vulnerabilities are targeted more often than core ones
  • Anyone who can log in to the admin area still sees the version as before

So along with hiding the version, update core, plugins and themes, remove plugins you do not use, and disable file editing in the admin area.

Summary

  • The version mainly appears in the HTML and feed generator and in ?ver= on CSS and JS. The REST API at /wp-json/ does not include the WordPress version
  • add_filter( 'the_generator', '__return_empty_string' ); removes the generator from both the HTML and the feeds
  • Instead of removing ?ver=, replace only the values that equal the WordPress version so cache refreshing keeps working
  • Stop X-Powered-By with expose_php = Off in php.ini
  • readme.html comes back after updates even if you delete it, so deny access to it in the web server
  • Hiding the version is a supplementary measure; keeping everything updated matters most

Related articles:

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲