Skip to main content

Hiding Server Information in .htaccess

Every piece of information your server reveals about itself gives attackers a head start. When a response header announces that you are running Apache 2.4.51 with PHP 7.4.3, an attacker does not need to guess what software you use. They can look up known vulnerabilities for those exact versions and craft targeted exploits. While hiding server information is not a security solution on its own, it is a simple and effective layer of defense that removes low-hanging fruit from automated scanners and opportunistic attackers.

This guide covers why information disclosure matters, how to disable the TRACE HTTP method, how to remove revealing response headers like X-Powered-By, how to suppress the server signature from error pages, and how to strip potentially sensitive data from ETag headers.

Why Information Disclosure Matters

By default, Apache and the technologies running on it are surprisingly talkative about their identity. A typical response from an unconfigured server might include:

HTTP/1.1 200 OK
Server: Apache/2.4.57 (Ubuntu)
X-Powered-By: PHP/8.2.7
ETag: "1a2b3c-4d5e-5f6a7b8c9d0e"

And an error page footer might display:

Apache/2.4.57 (Ubuntu) Server at example.com Port 443

From these few lines, an attacker learns:

Information RevealedHow It Helps an Attacker
Apache version (2.4.57)Search CVE databases for known vulnerabilities in this version
Operating system (Ubuntu)Narrow down OS-specific exploits and default configurations
PHP version (8.2.7)Look up PHP-specific vulnerabilities for this exact version
ETag with inode dataPotentially fingerprint the server or detect load balancer changes
Server portConfirm which ports are open and serving content

Automated scanning tools like Shodan, Censys, and Nikto harvest this information at scale. They continuously crawl the internet, catalog server versions, and match them against known vulnerability databases. If your server advertises an outdated Apache or PHP version, it will appear in search results for anyone looking for vulnerable targets.

The principle here is security through obscurity as a layer, not a strategy. Hiding version information does not fix vulnerabilities. You must still keep your software updated. But removing these details forces attackers to spend more time probing, which makes automated attacks less effective and reduces your exposure to opportunistic scanning.

Disabling the TRACE Method

The HTTP TRACE method is a diagnostic tool that echoes back the exact request the server received. While this is useful for debugging proxies and intermediate servers, it creates a security risk called Cross-Site Tracing (XST).

What TRACE Does

When a client sends a TRACE request:

TRACE / HTTP/1.1
Host: example.com
Cookie: session=abc123
Authorization: Bearer token-xyz

The server responds with the entire request reflected back in the response body:

HTTP/1.1 200 OK
Content-Type: message/http

TRACE / HTTP/1.1
Host: example.com
Cookie: session=abc123
Authorization: Bearer token-xyz

An attacker who can trick a user's browser into sending a TRACE request (via Java applets, Flash, or other mechanisms) can read the reflected response and steal authentication cookies and tokens, even if those cookies are marked as HttpOnly (which normally prevents JavaScript from reading them).

Modern browsers block TRACE requests initiated by JavaScript (XMLHttpRequest and fetch), but other attack vectors exist through browser plugins and less common HTTP clients.

Blocking TRACE with a RewriteRule

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^TRACE [NC]
RewriteRule .* - [R=405,L]
</IfModule>

This configuration:

  • RewriteCond %{REQUEST_METHOD} ^TRACE [NC] matches any request using the TRACE method (case-insensitive).
  • RewriteRule .* - [R=405,L] returns a 405 Method Not Allowed response for any URL. The - means no URL substitution. The [L] flag stops further processing.

After adding this rule, a TRACE request receives:

HTTP/1.1 405 Method Not Allowed

Verifying the Block

You can test this with curl:

curl -X TRACE https://example.com/

Without protection, you see the request echoed back. With the rule in place, you receive a 405 error.

tip

If you have access to the main Apache server configuration (httpd.conf or a virtual host file), the cleaner approach is to use the TraceEnable directive:

# In httpd.conf or virtual host config (not available in .htaccess)
TraceEnable Off

This disables TRACE at the server level, which is more efficient than processing it through mod_rewrite. The .htaccess rewrite approach is the alternative when you do not have access to the main configuration.

Also Consider Blocking TRACK

The TRACK method is a non-standard equivalent of TRACE that some servers support. Block both for completeness:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK) [NC]
RewriteRule .* - [R=405,L]
</IfModule>

Removing the X-Powered-By Header

The X-Powered-By header is set by backend frameworks and languages to advertise themselves. PHP, ASP.NET, Express.js, and many other technologies add this header automatically.

A typical header looks like:

X-Powered-By: PHP/8.2.7

Or:

X-Powered-By: ASP.NET

This header serves no functional purpose for the browser or the user. It exists purely for informational (or promotional) reasons. For an attacker, it is a gift: it identifies the exact technology and version powering your site.

Removing with Header unset

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

Two directives are used because they operate at different stages of response processing:

  • Header unset X-Powered-By removes the header from normal (2xx, 3xx) responses.
  • Header always unset X-Powered-By removes the header from all responses, including error responses (4xx, 5xx). Without always, the header might still appear on error pages.

Better: Disable at the Source

Removing the header in .htaccess works, but it is more efficient to prevent the header from being generated in the first place.

For PHP, edit php.ini:

php.ini
expose_php = off

This prevents PHP from adding the X-Powered-By header and also removes the PHP version from the Server header and the X-Powered-By header on error pages.

For Express.js (Node.js):

app.disable('x-powered-by');

For ASP.NET, add to web.config:

<httpRuntime enableVersionHeader="false" />
note

Even if you disable the header at the application level, keeping the Header unset directive in .htaccess is a good defense in depth practice. If a future configuration change or application update re-enables the header, the .htaccess rule catches it as a safety net.

Verifying the Removal

curl -I https://example.com/

Check that no X-Powered-By line appears in the response headers.

Removing the Server Signature

Apache can reveal its identity in two places: the Server response header and the footer text on server-generated pages (error pages, directory listings, etc.).

ServerSignature Off

The server signature is the line of text Apache appends to the bottom of server-generated pages like 404 errors, 403 forbidden pages, and directory listings:

Apache/2.4.57 (Ubuntu) Server at example.com Port 443

This footer reveals the Apache version, the operating system, the hostname, and the port. Disable it with:

ServerSignature Off

With ServerSignature Off, error pages no longer include this footer. Your custom error pages (if you have them) are unaffected because they are your own HTML, not server-generated content.

There are three possible values:

ValueBehavior
OffNo footer is added to server-generated pages.
OnAdds server version and virtual host name to the footer.
EMailSame as On, plus adds a mailto: link to the ServerAdmin address.

Always use Off in production. There is no benefit to displaying server version information to your visitors.

ServerTokens (Main Config Only)

While ServerSignature controls the footer text, the ServerTokens directive controls the Server response header that is sent with every HTTP response:

Server: Apache/2.4.57 (Ubuntu)

The ServerTokens directive determines how much detail this header includes:

ValueServer Header Output
FullApache/2.4.57 (Ubuntu) PHP/8.2.7 OpenSSL/3.0.8
OSApache/2.4.57 (Ubuntu)
MinorApache/2.4.57
MinimalApache/2.4
MajorApache/2
ProdApache

The most restrictive setting:

# In httpd.conf or virtual host config ONLY
ServerTokens Prod

This reduces the Server header to just:

Server: Apache
warning

ServerTokens cannot be set in .htaccess. It is a server-level directive that can only be used in the main configuration file (httpd.conf, apache2.conf, or virtual host files). If you are on shared hosting and do not have access to these files, you cannot change the ServerTokens value. Contact your hosting provider to request this change.

Can You Remove the Server Header Entirely?

Apache does not provide a built-in way to completely remove the Server header. However, mod_headers can overwrite it:

<IfModule mod_headers.c>
Header always set Server "webserver"
</IfModule>

This replaces the Server header content with a generic string. Note that some Apache builds and modules may re-add the original header. The Header unset Server approach does not reliably work in all configurations because Apache adds the Server header at a very late stage in response processing.

The most reliable approach is ServerTokens Prod in the main config combined with ServerSignature Off in .htaccess.

Removing ETag Inode Information

ETags (Entity Tags) are identifiers that Apache assigns to files for caching purposes. When a browser has a cached copy of a file, it sends the ETag back to the server with a conditional request. If the file has not changed, the server responds with 304 Not Modified, saving bandwidth.

By default, Apache generates ETags using three pieces of information:

  • INode: The file's filesystem inode number.
  • MTime: The file's last modification time.
  • Size: The file's size in bytes.

A default ETag looks like:

ETag: "1a2b3c-4d5e-5f6a7b8c9d0e"

Why Default ETags Are Problematic

The inode component creates two issues:

1. Information disclosure:

The inode number is an internal filesystem detail that reveals information about your server's file system structure. While not directly exploitable on its own, it adds to an attacker's overall fingerprint of your server.

2. Load balancer inconsistency:

If your site runs on multiple servers behind a load balancer, the same file will have different inode numbers on different servers. This means the same file produces different ETags depending on which server handles the request. When a user's subsequent request hits a different server, the ETag does not match, and the server sends the full file instead of a 304 response. Caching effectiveness drops significantly.

Fixing ETag Configuration

Remove the inode component and keep only the modification time and file size:

FileETag MTime Size

This generates ETags based on the file's last modification time and size, which are consistent across servers with the same file content.

Alternatively, you can rely entirely on Last-Modified and Cache-Control headers for caching and disable ETags completely:

<IfModule mod_headers.c>
Header unset ETag
</IfModule>
FileETag None
  • FileETag None tells Apache not to generate ETags.
  • Header unset ETag removes any ETag that might be added by other modules or backend applications.

Which Approach to Choose

ApproachBest For
FileETag MTime SizeSites behind load balancers that still want ETag caching
FileETag NoneSites that rely on Cache-Control and Last-Modified only

For most modern websites, FileETag MTime Size is the right choice. It preserves caching functionality while removing the inode information leak.

Complete Information Hiding Configuration

Here is a comprehensive configuration that implements all the information hiding techniques covered in this guide:

Complete server information hiding
# === Disable Server Signature ===
ServerSignature Off

# === Remove ETag Inode Information ===
FileETag MTime Size

# === Disable TRACE and TRACK Methods ===
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK) [NC]
RewriteRule .* - [R=405,L]
</IfModule>

# === Remove Revealing Headers ===
<IfModule mod_headers.c>
# Remove X-Powered-By (PHP, ASP.NET, etc.)
Header unset X-Powered-By
Header always unset X-Powered-By

# Remove server technology hints
Header unset X-AspNet-Version
Header always unset X-AspNet-Version
Header unset X-AspNetMvc-Version
Header always unset X-AspNetMvc-Version
Header unset X-Runtime
Header always unset X-Runtime
Header unset X-Version
Header always unset X-Version

# Minimize Server header (may not work on all configurations)
Header always set Server "webserver"
</IfModule>

This configuration, combined with ServerTokens Prod in the main Apache config and expose_php = off in php.ini, reduces your server's information footprint to the absolute minimum.

caution

Information hiding is a complement to keeping your software updated, not a replacement. An attacker who is specifically targeting your site can still determine your technology stack through other fingerprinting techniques (response timing, header ordering, default file paths, error message formats). The real protection comes from running patched, up-to-date software. Information hiding simply removes the easy indicators that automated scanners use to identify low-hanging targets.

Removing server information is one of the simplest and lowest-risk changes you can make to your .htaccess configuration. There are no compatibility concerns, no functionality trade-offs, and no user-visible effects. Add these directives to every production site as a baseline security measure, and combine them with regular software updates and the other security headers covered throughout this guide series.