Docs

Hide software versions

Hide software versions

Many servers announce exactly what they run: Server: nginx/1.18.0 (Ubuntu), X-Powered-By: PHP/7.4.33, or a <meta name="generator" content="WordPress 6.1.1"> tag in the page. The version number itself does nothing for your visitors. For an attacker it is a shortcut: it tells them which known vulnerabilities to try first, and automated scanners look for exactly these strings.

Hiding the version does not fix an old version, so keep updating. But there is no reason to advertise it, and the site checklist marks it as something to check.

Where versions show up

Where Example Who adds it
Server header nginx/1.18.0 (Ubuntu), Apache/2.4.41 (Ubuntu) the web server
X-Powered-By header PHP/8.3.35, Express, ASP.NET PHP, Node.js or IIS
<meta name="generator"> in the HTML WordPress 6.4.2, WPML ver:4.6.9, Elementor 3.18.0, WooCommerce 8.5 the CMS and its plugins

The checklist shows which of these it found. A header that says only nginx or Apache, without a number, is fine.

Check it yourself

Replace example.com with your site:

curl -sI https://example.com | grep -iE '^(server|x-powered-by):'
curl -s https://example.com | grep -io '<meta name="generator"[^>]*>'

The first command prints the headers, the second the generator tags. When both print nothing, or only a name without a version, you are done.

How to fix

Pick what you have access to. Most sites need two fixes: one for the headers (server or hosting panel) and one for the generator tags (the CMS). If you only have WordPress admin access, the WordPress tab covers both as far as WordPress can.

WordPress

WordPress prints its own version, and many plugins add theirs (WPML, Elementor, WooCommerce, Slider Revolution, Yoast in comments). Instead of removing them one by one, remove every generator tag at once.

Add this with the free Code Snippets plugin (Snippets → Add New, run everywhere) or at the end of your child theme's functions.php. Do not put it in a parent theme: an update would overwrite it.

// No WordPress version in the page or in RSS feeds.
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');

// Every other <meta name="generator"> tag (WPML, Elementor, WooCommerce …).
add_action('template_redirect', function () {
    ob_start(fn ($html) => preg_replace('/<meta\s+name=["\']generator["\'][^>]*>\s*/i', '', $html));
});

// PHP version header, where PHP itself sends it.
add_action('send_headers', fn () => header_remove('X-Powered-By'));

Then clear the page cache (WP Rocket, LiteSpeed Cache, W3 Total Cache, your host's cache): a cached page still carries the old tags.

The Server header is set by the web server, not WordPress. Use the tab for your hosting panel or server for that one.

cPanel

PHP version (X-Powered-By):

  1. Open Software → MultiPHP INI Editor.
  2. Switch to Editor Mode and pick the domain.
  3. Add or change the line expose_php = Off and click Save.

If the header is still there, add this to the .htaccess file in the site's root folder (File Manager, with Show Hidden Files on):

<IfModule mod_headers.c>
    Header always unset X-Powered-By
    Header unset X-Powered-By
</IfModule>

Web server version (Server): on shared cPanel hosting the server configuration belongs to the host. Most hosts already send just Apache or LiteSpeed. If yours shows a version, ask support to set ServerTokens Prod; it is a one-line change for them.

Generator tags come from the CMS: see the WordPress tab.

Plesk

PHP version (X-Powered-By): Websites & Domains → example.com → PHP Settings, in Additional configuration directives add:

expose_php = Off

nginx version and leftover headers: Websites & Domains → example.com → Apache & nginx Settings, in Additional nginx directives add:

server_tokens off;
fastcgi_hide_header X-Powered-By;
proxy_hide_header X-Powered-By;

Click OK; Plesk checks the syntax and reloads nginx.

Generator tags come from the CMS: see the WordPress tab.

nginx

Server version: in /etc/nginx/nginx.conf, inside the http { … } block:

http {
    server_tokens off;

    # PHP-FPM and proxied apps (Node, Python) can each send X-Powered-By.
    fastcgi_hide_header X-Powered-By;
    proxy_hide_header X-Powered-By;
}

PHP version: find the php.ini that PHP-FPM uses. The command line uses a different file, so php --ini points to the wrong one. On Debian and Ubuntu it is /etc/php/8.3/fpm/php.ini (your version number may differ). Set:

expose_php = Off

Test and apply both:

sudo nginx -t && sudo systemctl reload nginx
sudo systemctl restart php8.3-fpm

Generator tags come from the CMS: see the WordPress tab.

Apache and LiteSpeed

With server access (Apache). On Debian and Ubuntu, edit /etc/apache2/conf-available/security.conf; on Rocky, Alma and CentOS, /etc/httpd/conf/httpd.conf:

ServerTokens Prod
ServerSignature Off

Then remove the PHP header (it needs mod_headers, on Debian and Ubuntu: sudo a2enmod headers):

Header always unset X-Powered-By

Apply it:

sudo apachectl configtest && sudo systemctl reload apache2

(httpd instead of apache2 on Rocky, Alma and CentOS.)

Without server access (shared hosting, LiteSpeed). LiteSpeed reads .htaccess like Apache and normally shows no version in Server. Add this to the .htaccess in the site's root to drop the PHP header:

<IfModule mod_headers.c>
    Header always unset X-Powered-By
    Header unset X-Powered-By
</IfModule>

Also set expose_php = Off in the host's PHP settings, if the panel has them.

Generator tags come from the CMS: see the WordPress tab.

Cloudflare

If the site runs behind Cloudflare (orange cloud), Cloudflare already replaces the Server header with cloudflare. X-Powered-By from the origin still passes through. Remove it on the free plan too:

  1. Open the domain in Cloudflare and go to Rules → Transform Rules → Modify Response Header.
  2. Create rule, name it Remove X-Powered-By, apply it to All incoming requests.
  3. Under Modify response header choose Remove, header name X-Powered-By, and Deploy.

Generator tags are part of the HTML and Cloudflare leaves them alone: see the WordPress tab.

Node.js and Next.js

Express sends X-Powered-By: Express unless you turn it off:

const app = express();
app.disable('x-powered-by');

Next.js sends X-Powered-By: Next.js. In next.config.js:

module.exports = {
    poweredByHeader: false,
};

If nginx sits in front of the app, proxy_hide_header X-Powered-By; there catches anything the app still sends (see the nginx tab).

IIS and ASP.NET

In the site's web.config:

<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>
    <security>
      <!-- IIS 10 version 1709 and newer: no Server header at all. -->
      <requestFiltering removeServerHeader="true" />
    </security>
  </system.webServer>
  <system.web>
    <httpRuntime enableVersionHeader="false" />
  </system.web>
</configuration>

enableVersionHeader removes X-AspNet-Version, which also gives away the framework version.

Hosted platforms

On Shopify, Wix, Squarespace and Webflow the platform runs the servers, and the headers are theirs to set. They do not announce versions that matter, and if the checklist finds one there, there is nothing to change on your side.

Confirm the fix

Run the two curl commands from Check it yourself again, then open the target in PulseKeeper and click Check now on the Checklist tab. The item turns OK within a few minutes instead of at the next daily run.