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_generatorfilter 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.txtorver=. 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-Bywithexpose_php = Offin php.ini readme.htmlcomes 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:
- How to set security headers for WordPress: .htaccess, Nginx and functions.php
- How to restrict access to the WordPress REST API
- Disable the WordPress theme and plugin file editors with DISALLOW_FILE_EDIT
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)