> ## Content Index
> Fetch the complete content index at: https://www.ctrl.blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Fun with Apache HTTPD and subrequest variable inconsistencies
- URL: https://www.ctrl.blog/entry/httpd-subrequest-variable-inconsistencies/
- Published: 2020-06-11T01:18:00.000Z
- Updated: 2026-08-23T22:11:46.000Z
- Description: Why does the REQUEST_URI variable return /index.html and not / when requesting a directory? Blame it on the subrequest for the DirectoryIndex file.
- Author: Daniel Aleksandersen
- Tags: Apache HTTPD

The Apache HTTP Server (HTTPD) is extremely configurable and powerful. However, its modules are a collection of disparate and inconsistent tools. In this article, I’d walk you through a recent problem I encountered when I wanted to combine two modules.

This article assumes some basic familiarity with the HyperText Transfer Protocol (HTTP) requests and the HTTPD server configuration syntax.

The problem I wanted to solve: The `REQUEST_URI` variable is supposed to contain the request path as it was supplied in the HTTP request (unless it has been rewritten by `mod_rewrite`). However, it was suffixed with “index.html” in `mod_headers` when requesting a directory (such as /).

Early on I realized that the value in the suffix came from the `DirectoryIndex` directive. When you request a directory, HTTPD tries to fulfill it using an internal subrequest that fetches the file specified in that directive. I understood the underlying problem but not how to retrieve the original `REQUEST_URI` variable.

I tried solving this using `mod_setenvif`. This was the wrong approach, but I want to walk you through it:

```apacheconf
DirectoryIndex /index.html
# check if the requested file exists
<If "-f '%{DOCUMENT_ROOT}%{REQUEST_URI}'">
  # copy the REQUEST_URI variable to PATH
  SetEnvIf REQUEST_URI .* PATH=$0
  Header set Test1 %{REQUEST_URI}e
  Header set Test2 %{PATH}e
</If>
```

When requesting `/`, the `Test1` response header was set to /index.html and `Test2` returned (null). Neither of these was the expected value (/).

A value of null means that the variable hasn’t being set (as opposed to being empty). The code worked as expected when requesting files but fails when requesting directories.

The inconsistencies in how `mod_setenvif` and `mod_headers` handle the `REQUEST_URI` are frustrating. It makes it difficult to use the former module to copy and correct the variable for use with the latter.

If you’re a seasoned HTTPD admin — or an attentive reader — you may have spotted the problem or at least noticed that something is wrong. I’m expecting a response header to appear when requesting directories but the header is gated by a conditional that only acts when the request is a file! But wait — why is the response header being sent at all?

There’s a lot of different moving parts here. I’ve actually explained what’s gone wrong already, but I’ll walk you through it step by step.

First of all, the conditional block isn’t met for the initial request. It’s for a directory so no headers are added. However, to fulfill the request, HTTPD issues a subrequest to request the `DirectoryIndex` file. This subrequest fulfills the requirement for running the conditional block and the headers are appended to the subrequest. The headers are attached to the response to the initial request.

So, why are the two test headers different, though? The second paragraph in the [mod\_setenvif documentation](https://httpd.apache.org/docs/2.4/mod/mod%5Fsetenvif.html?ref=ctrl.blog) has the answer:

> “When the server looks up a path via an internal subrequest such as looking for a DirectoryIndex or generating a directory listing with mod\_autoindex, per-request environment variables are not inherited in the subrequest. Additionally, SetEnvIf directives are not separately evaluated in the subrequest due to the API phases mod\_setenvif takes action in.”

Knowing all of this, we can construct a better workaround to achieve the desired goal. The three differences from the above code example are marked in blue.

```apacheconf
DirectoryIndex /index.html
# check if the requested file or directory exists
<If "-e '%{DOCUMENT_ROOT}%{REQUEST_URI}'">
  # copy the REQUEST_URI variable to PATH
  SetEnvIf REQUEST_URI .* PATH=$0
  # set header only if the variable PATH is set
  Header set Test1 %{REQUEST_URI}e ENV=PATH
  Header set Test2 %{PATH}e ENV=PATH
</If>
```

This time around when requesting `/` we get different responses. The `Test1` response header still responds with /index.html. The `Test2` response header is now / as desired!

So, what is different this time around? The conditional is run for both the initial request (for the directory) and the subrequest for the `DirectoryIndex` file. The `PATH` variable is set correctly for the initial request, and it isn’t overwritten by the subrequest because the `PATH` variable isn’t being set there. `SetEnvIf`’s conditional (`REQUEST_URI`) is null so the subrequest won’t overwrite the value set for the initial request.

I said I wanted the `REQUEST_URI` variable without the index.html suffix. However, the above code example still include it when explicitly requesting `/index.html`. You can remove it from the variable in this situation using regular expressions (RegEx). Replace the `SetEnvIf` line from the above code example with the following (or adjust to your `DirectoryIndex` directive as needed):

```apacheconf
SetEnvIf REQUEST_URI ^(.*?)(index\.html)?$ PATH=$1
```

There are many types of subrequests, though. You’ll be running into the same problem again if you’re setting custom error pages with the `ErrorDocument` directive. The `REQUEST_URI` variable will disclose the location of the error document. You can work around this error specifically by checking the request status code before sending the response header.

The following example combines the above method (checking to see if the variable is empty) and a check to see if the request was successful (that it generated a `HTTP 200 OK` successfully responses):

```apacheconf
Header set Test2 %{PATH}e "expr=%{REQUEST_STATUS} == 200 && -n %{ENV:PATH}"
```

These aren’t the only cases where you could run into subrequests overwriting variables. It should be the most common ones, however. You must read HTTPD module documentation top-to-bottom and read all the associated documentation. All the information you’ll need is \[most often\] there. It can be a pain to configure it sometimes because it doesn’t behave as you think it does. All the inconsistencies between modules make it hard to apply things you’ve learned about one module on another.

#### Sources

- Apache HTTPD version 2.4.43
- [Apache Core Features](https://httpd.apache.org/docs/2.4/mod/core.html?ref=ctrl.blog), version 2.4, 2020-04-01, HTTP Server Documentation, Apache Foundation
- [Apache Headers module](https://httpd.apache.org/docs/2.4/mod/mod%5Fheaders.html?ref=ctrl.blog), version 2.4, 2020-04-01, HTTP Server Documentation, Apache Foundation
- [Apache SetEnvIf module](https://httpd.apache.org/docs/2.4/mod/mod%5Fsetenvif.html?ref=ctrl.blog), version 2.4, 2020-04-01, HTTP Server Documentation, Apache Foundation