PenTesting Company
Nighttime satellite view of the United States, showing city lights from coast to coast
← Blog

Hide the Apache Server Header

8 min read

By default, Apache sends a Server response header that reports the software and version, such as Server: Apache/2.4.69 (Debian). To hide the version, set ServerTokens Prod in the main server configuration and reload, which leaves Server: Apache. ServerSignature Off (the httpd default) suppresses the footer on server-generated pages, such as the built-in 404 page. Removing the Apache name entirely is not something ServerTokens can do. The sections below walk through each step with captured output.

The official ServerTokens documentation says that disabling the Server header does nothing at all to make your server more secure, and that the idea of "security through obscurity" is a myth and leads to a false sense of safety. Hiding the banner reduces easy fingerprinting. It is not security assurance.

# Default (and ServerTokens OS) on this packaged build
Server: Apache/2.4.69 (Debian)

# After ServerTokens Prod
Server: Apache

# After a reverse proxy in front that dropped Server
(no Server header)

These lines come from the captured curl output further down, on Debian 13 with Apache/2.4.69 (Debian).

Set ServerTokens Prod and ServerSignature Off

ServerTokens controls the Server response header. The documented default is Full. ServerTokens Prod (or ProductOnly) sends only the product name: Server: Apache. The same directive also controls how much detail appears in a ServerSignature footer.

The examples below were captured on Debian 13 with the packaged Apache/2.4.69 (Debian). With no ServerTokens line, a plain request showed:

$ curl -sSI http://127.0.0.1:18201/
HTTP/1.1 200 OK
Server: Apache/2.4.69 (Debian)
Date: Wed, 11 Nov 2026 16:55:05 GMT
Content-Type: text/html
Content-Length: 29

On this package, ServerTokens Full and ServerTokens OS produced that same value. No extra module names appeared in the Full form in this capture. ServerTokens Minimal produced Server: Apache/2.4.69, and ServerTokens Prod produced:

$ curl -sSI http://127.0.0.1:18202/
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 11 Nov 2026 16:55:05 GMT
Content-Type: text/html
Content-Length: 29

ServerSignature controls the footer on server-generated documents, such as built-in error pages. The official documentation says the default is Off, which suppresses the footer, and that the details of the version number in that footer are controlled by ServerTokens. In our test, a 404 with the default signature setting had no footer. With ServerSignature On and the default tokens, the body ended like this:

$ curl -sSi http://127.0.0.1:18203/missing
HTTP/1.1 404 Not Found
Server: Apache/2.4.69 (Debian)
...
<hr>
<address>Apache/2.4.69 (Debian) Server at 127.0.0.1 Port 18203</address>

With ServerTokens Prod and ServerSignature On, the header was Server: Apache, and the footer was still present as Apache Server at 127.0.0.1 Port 18204. The version was gone, but the Apache name remained in both places. Set both directives if you want the version hidden in the header and the footer gone:

ServerTokens Prod
ServerSignature Off

Where the directives live

ServerTokens is valid only in the main server configuration. The httpd documentation says this setting applies to the entire server and cannot be enabled or disabled on a virtualhost-by-virtualhost basis. In our test, apache2 -t rejected a ServerTokens line inside a VirtualHost block with: ServerTokens cannot occur within <VirtualHost> section. Put it in the global configuration, not in a vhost or .htaccess file.

ServerSignature is different. Its documented context is server config, virtual host, directory, and .htaccess, so it can be set per site or per directory.

On Debian and Ubuntu, these directives usually live in /etc/apache2/conf-available/security.conf, which is enabled by a symlink under /etc/apache2/conf-enabled/ (the a2enconf helper creates that link). The main /etc/apache2/apache2.conf file includes those snippets with IncludeOptional conf-enabled/*.conf, then loads virtual hosts from sites-enabled/*.conf. The packaged Debian 13 security.conf in this build set ServerTokens OS and ServerSignature On. Those packaged values can differ by release, so read the file on your system rather than assuming Prod and Off are already set.

On RHEL-family systems, the same directives typically go in the main server configuration at /etc/httpd/conf/httpd.conf, or in a file included from it under /etc/httpd/conf.d/. There is no per-vhost ServerTokens setting on those systems either.

If more than one included file sets ServerTokens, the last value wins. In our test, a configuration that set ServerTokens Full and then ServerTokens Prod produced Server: Apache. On Debian, conf-enabled/*.conf is included in alphabetical order, so a later file can override security.conf. After a change, run a config test and reload, then check the public response rather than assuming which file was last.

Remove X-Powered-By and other headers with mod_headers

Application frameworks often add X-Powered-By. The lasting fix is to turn that off in the application where you can. See Remove the X-Powered-By Header for source-level settings in PHP, Express, and Next.js, and for a paste-in checker. In Apache, mod_headers can also remove a header that is already present, with Header unset or Header always unset.

The difference matters. The official Header documentation says the optional condition is onsuccess (the default) or always, and that always is not a superset of onsuccess with respect to existing headers. It also notes that headers generated by a CGI script live in the always table. We tested both forms against a CGI script and against a proxied HTTP upstream that each sent X-Powered-By: ExampleFramework/4.2.

For the CGI responses, Header unset X-Powered-By left the header in place on both the 200 and the 500 response. Header always unset X-Powered-By removed it on both:

# CGI, no Header directive
HTTP/1.1 200 OK
Server: Apache
X-Powered-By: ExampleFramework/4.2
...

# CGI, Header unset X-Powered-By
HTTP/1.1 200 OK
Server: Apache
X-Powered-By: ExampleFramework/4.2
...

# CGI, Header always unset X-Powered-By
HTTP/1.1 200 OK
Server: Apache
...

For the proxied HTTP 200 response, the result was the other way around. Header unset X-Powered-By removed the header, and Header always unset X-Powered-By left it in place. These are the results from this Apache/2.4.69 (Debian) capture, not a claim about every module or version.

Using both directives together removed X-Powered-By in every case we tried: CGI 200, CGI 500, and the proxied HTTP 200 response.

Header unset X-Powered-By
Header always unset X-Powered-By

We also tested whether mod_headers would remove Apache's own Server header. On this Apache/2.4.69 (Debian) build, Header unset Server, Header always unset Server, and both directives together each left Server: Apache in place. That is what these fixtures showed. It is not a way to hide the Apache name on this build.

Hiding 'Apache' completely

ServerTokens Prod still leaves Server: Apache. In our tests, stock mod_headers did not remove that header. Two approaches did change or drop the name.

The first is SecServerSignature from ModSecurity (the libapache2-mod-security2 package on Debian). In our test, with ServerTokens Full and SecServerSignature "webserver", the header became Server: webserver. With the same SecServerSignature line and ServerTokens Prod, the header stayed Server: Apache. If you use this directive, check the public header after reload rather than assuming the custom string appears.

ServerTokens Full
SecServerSignature "webserver"

The second is a reverse proxy or CDN in front of Apache that overwrites or drops Server after Apache has set it. In our test, a loopback reverse proxy forwarded the Apache response and omitted the Server header. The direct origin response still showed Server: Apache/2.4.69 (Debian). The response through the proxy had no Server field. If that front layer is Nginx, see Remove any response header in nginx for proxy_hide_header and related directives. Check whatever sits at the outermost hop, because that is what a visitor actually receives.

Hiding the name reduces easy fingerprinting. Other techniques can still identify the server. It is not security assurance.

  1. 1

    Application or CGI

    May set X-Powered-By

    • The application does not set Apache's own Server header.
    • Frameworks often add X-Powered-By. Turn that off in the application where you can.
    • In our tests, removing that header with mod_headers depended on Header unset versus Header always unset, and on whether the header came from CGI or a proxied HTTP upstream.
  2. 2

    Apache

    Adds the Server header

    • ServerTokens Prod hides the version and leaves Server: Apache. It cannot remove the name.
    • ServerSignature Off suppresses the footer on server-generated pages such as the built-in 404 page.
    • In our tests on Apache/2.4.69 (Debian), Header unset Server and Header always unset Server left Server: Apache in place.
    • SecServerSignature, from ModSecurity, replaced the header with a custom string when ServerTokens was Full. With ServerTokens Prod, the header stayed Server: Apache.
  3. 3

    Reverse proxy or CDN

    Optional hop in front of Apache

    • This layer can add, overwrite, or drop Server after Apache has set it.
    • In our test, a reverse proxy in front of Apache forwarded the response and omitted the Server header.
    • If the proxy is Nginx, see the Nginx guide section on removing any response header.
  4. 4

    Client

    Sees the public response

    • Verify with curl against the public hostname, including any proxy or CDN in front of Apache, and request a missing page to check the signature footer.
Illustrative only. On a request to example.com, Apache sets the Server header. A reverse proxy or CDN in front of Apache can change or drop that header before the client sees it. Check the public response at the outermost hop, including a missing page for the signature footer.

Check your Apache response headers

Paste raw curl -I, HEAD or GET response output, including the HTTP status line. For curl -IL, each response is shown separately, in pasted order. These are response blocks, not proof of distinct network hops.

Pasted text stays in your browser. Nothing is sent or stored.

Only pasted text is checked, not your live site. A truncated capture may omit fields. Check successful, redirect and error responses separately, including GET bodies. Hiding the version banner reduces information disclosure but is not security assurance.

Verify with curl

After you change the configuration, test it before reloading. On Debian and Ubuntu:

$ sudo apache2ctl configtest
Syntax OK

$ sudo systemctl reload apache2

On RHEL-family systems, the equivalent commands are often sudo apachectl configtest or sudo httpd -t, then sudo systemctl reload httpd. If the config test reports an error, fix it before reloading.

Then request a normal page and read the Server line:

$ curl -sI https://your-site/
HTTP/1.1 200 OK
Server: Apache
...

Also request a page that does not exist and include the body, so you can see whether a ServerSignature footer is still present:

$ curl -sSi https://your-site/does-not-exist

Repeat the check through any reverse proxy or CDN in front of Apache, because that layer can add or replace the header after Apache has set it. You can paste the same curl output into the response header checker on this page.

Nighttime satellite view of the United States, showing city lights from coast to coast

Have a system you want tested?

Tell us what you are working with and we will follow up with next steps.