Apache Server ETag Header Information Disclosure means Apache builds the ETag header for static files from each file’s inode number as well as its size and modification time, so responses leak a filesystem detail. Fix it by setting FileETag MTime Size (or FileETag None) in the server or virtual host configuration, reloading Apache and rescanning.
What the scanner is actually detecting
This is a remote, unauthenticated check. The scanner requests content from the web server and reads the ETag response header, which browsers and caches use to ask “has this file changed?” without downloading it again.
| Scanner | Finding title | ID |
|---|---|---|
| Nessus | Apache Server ETag Header Information Disclosure | Plugin 88098 (Medium, CVSS v3 5.3, CVE-2003-1418) |
According to Tenable, the plugin (family: Web Servers) only runs once Nessus has identified the service as Apache httpd, and it reports the ETag header when it reveals information such as the inode number of the requested file.
Apache’s own source shows how to read the header. For a static file, the ETag is up to three hexadecimal values joined by dashes, in the order inode, size, modification time, sometimes with a W/ (weak) prefix:
ETag: "4c1a2f-2d5-5f7b3c1e8a9c0" inode-size-mtime (flagged)
ETag: "2d5-5f7b3c1e8a9c0" size-mtime (Apache 2.4 default)
The FileETag directive decides which parts are used. Its default in Apache 2.4 is MTime Size; the Apache documentation notes that the default was INode MTime Size in 2.3.14 and earlier. So a flagged host is usually one of these:
- An Apache 2.2 server, still on the old default. Apache 2.2 is end of life; its final release was 2.2.34 in July 2017.
- An Apache 2.4 server where someone set
FileETag All,FileETag INode MTime Sizeor+INodein the main config, a virtual host, a<Directory>block or an.htaccessfile. - An Apache reverse proxy passing through an ETag generated by an older backend.
How serious is it?
Low, despite the Medium label. CVE-2003-1418 was filed against Apache 1.3.22 through 1.3.27 on OpenBSD, where the ETag revealed inode numbers and multipart MIME boundaries revealed child process IDs. Tenable lists no known exploit for this plugin.
An inode number does not give anyone file contents, credentials or code execution. Its value is reconnaissance. For example, because the same file almost always has a different inode number on each server, differing ETags can show how many nodes sit behind a load balancer. Treat this as quick hardening, not an emergency. The bigger concern is often what the finding reveals: if the host still runs Apache 2.2, the unsupported server is the real risk.
How to confirm it on the host
Request a static file (CSS, JavaScript or an image) rather than a PHP or application page, because Apache builds FileETag-based ETags for static files only:
curl -sI http://host.example.com/static/site.css | grep -i '^etag'
curl -skI https://host.example.com/static/site.css | grep -i '^etag'
curl does not ask for compression unless you add --compressed, so you see the uncompressed ETag. Three hex values mean the inode is probably included. To prove it, compare the first value with the file on disk:
stat -c 'inode=%i size=%s' /var/www/html/static/site.css
printf 'inode=%d size=%dn' 0x4c1a2f 0x2d5
If the decoded first value equals the inode from stat, the finding is valid. Then find out which configuration produces it:
httpd -v # RHEL, Rocky, AlmaLinux
apache2 -v # Debian, Ubuntu
sudo grep -RniE '^s*FileETag' /etc/httpd/ /etc/apache2/ 2>/dev/null
sudo find /var/www -name .htaccess -exec grep -Hni 'FileETag' {} +
How to fix it
Apache 2.4: remove the override
Because 2.4 already excludes the inode by default, the fix is usually to delete or correct whichever FileETag line the grep found. Remember the inheritance rule from the Apache documentation: a keyword without a prefix completely cancels the inherited setting, while + and - adjust it. A FileETag All inside one <Directory> or .htaccess therefore overrides a correct global setting for that path. Change such lines to:
FileETag MTime Size
or, to disable inode in the ETag while keeping everything else a block inherits:
FileETag -INode
To pin the setting server wide as well, create a small drop-in file. On the RHEL family, /etc/httpd/conf.d/etag.conf:
# Build ETags without inode numbers
FileETag MTime Size
On Debian and Ubuntu, put the same content in /etc/apache2/conf-available/etag.conf and enable it:
sudo a2enconf etag
Validate and reload:
sudo apachectl configtest && sudo systemctl reload httpd # RHEL family
sudo apache2ctl configtest && sudo systemctl reload apache2 # Debian family
Note that .htaccess files can only set FileETag where AllowOverride includes FileInfo, so check those files too.
Apache 2.2
The directive and syntax are the same. Add FileETag MTime Size to the main configuration (for example /etc/httpd/conf/httpd.conf on older RHEL and CentOS), then run apachectl configtest and apachectl graceful. Read the WebDAV warning under “What can break” first, and plan the upgrade to 2.4, because this setting does nothing about the other risks of an unsupported server.
Removing ETags entirely
FileETag None stops Apache from sending an ETag for file-based responses. Browsers then revalidate with Last-Modified and If-Modified-Since instead. Choose this only if nothing in your stack relies on ETag validation; for most sites FileETag MTime Size is the better choice.
Apache on Windows
Add the directive to confhttpd.conf under the server root, then test and restart the service from an elevated prompt (replace Apache2.4 with your service name):
httpd.exe -n "Apache2.4" -t
httpd.exe -k restart -n "Apache2.4"
Apache as a reverse proxy
If the ETag comes from a backend server, fix the backend. If you cannot, the proxy can strip the header with mod_headers, scoped to the proxied path:
<Location "/legacy/">
Header unset ETag
</Location>
This removes every ETag under that path, including ones an application uses for conditional requests, so keep the scope narrow.
How to verify the fix and rescan
- Rerun the curl commands against every host, port and name-based virtual host listed in the finding. The ETag should now have two values (size and mtime), or be absent if you chose
FileETag None. - If you test with
--compressed, a-gzipor-brsuffix is expected. mod_deflate and mod_brotli add it by default so compressed and uncompressed versions get different ETags. - Run the
statcomparison again and confirm no value matches the inode. - Test the same address the scanner used. If Nessus scanned a load balancer VIP, check the VIP and each node behind it.
- Rescan the affected hosts with the same Nessus policy, so the Apache detection that plugin 88098 relies on runs again.
What can break and how to roll back
- One-time cache revalidation. Every static file gets a new ETag, so browsers and CDNs holding the old value receive a full response on their next check instead of a 304. Expect a short bandwidth bump, nothing more. Dropping the inode also tends to make ETags consistent across load-balanced nodes, provided the files have the same size and modification time on each.
- WebDAV on older versions. The Apache 2.2 documentation warns not to change the default for locations served by mod_dav_fs, because it used a fixed INode MTime Size format for conditional requests. mod_dav_fs only began honoring
FileETagin 2.4.47, so test WebDAV clients on older 2.4 builds too. - Over-broad header removal.
Header unset ETagacross a whole virtual host can break APIs that useIf-Matchfor safe updates.
Back up each file before editing. To roll back, remove the drop-in (sudo rm /etc/httpd/conf.d/etag.conf or sudo a2disconf etag), restore the edited files, then run configtest and reload.
Common false positive reasons
- Compression suffix.
"2d5-5f7b3c1e8a9c0-gzip"has three dash-separated parts but no inode. Anyone counting dashes, human or tool, can misread it. - Not Apache’s ETag. The header came from an application, a backend behind a reverse proxy, or a CDN or load balancer in front of the server.
- Application-generated ETags. Frameworks often use hashes or UUID-like values that contain dashes. If the values do not match
statoutput, they are not inodes. - Wrong node or stale data. One unfixed node in a pool, or a report older than the change.
If the evidence shows no inode, keep the curl and stat output with the finding so the next reviewer does not repeat the work.
FAQ
Is the Apache ETag vulnerability worth fixing?
It is low risk, but the fix is a one-line configuration change with little side effect. Fixing it is usually faster than documenting why you did not.
Should I use FileETag MTime Size or FileETag None?
FileETag MTime Size removes the inode and keeps efficient caching. FileETag None also clears the finding but gives up ETag validation for static files.
Why is an Apache 2.4 server flagged if 2.4 excludes inodes by default?
Something re-enabled it: FileETag All or INode in a virtual host, directory block or .htaccess, or an older backend behind a proxy. The grep and find commands above locate it.
Can I accept the risk instead?
Yes, for example on a legacy WebDAV share you cannot change yet. Record the reason, owner and review date through a documented risk acceptance process rather than silently excluding the plugin.
Tracking this finding across many hosts
This check fires per web service, so a large estate can produce many rows. SITEY, a self-hosted vulnerability management platform, imports findings from 16 scanners (Nessus results arrive as uploaded .nessus exports), merges duplicates within each scanner, and can re-test Nessus findings such as plugin 88098 individually after the change. AI-drafted, host-specific remediation scripts are deployed by its agents on Windows and Linux only after human approval.