2. URL Mapping

Some people are heroes. And some people jot down notes.
Sometimes they’re the same person.

—Terry Pratchett, The Truth

In this chapter, we’ll discuss the various ways that the Apache HTTP Server (httpd) handles URL Mapping.

When the Apache HTTP Server receives a request, it is processed in a variety of ways to see what resource it represents. This process is called URL Mapping.

mod_rewrite is part of this process, but will be handled separately, since it is a large portion of the contents of this book.

The exact order in which these steps are applied may vary from one configuration to another, so it is important to understand not only the steps, but the way in which you have configured your particular server.

2.1. mod_rewrite

mod_rewrite is perhaps the most powerful part of this process. That is, of course, why it features prominently in the name of this book.

For now, we’ll just say that mod_rewrite fills a variety of different roles in the URL mapping process. It can, among other things, modify a URL once it is received, in many different ways.

While this usually happens before the other parts of URL mapping, in certain circumstances, it can also perform that rewriting later on in the process.

This, and much more, will be revealed in the coming chapters.

2.2. DocumentRoot

The DocumentRoot directive specifies the filesystem directory from which static content will be served. It’s helpful to think of this as the default behavior of the Apache HTTP Server when no other content source is found.

Consider a configuration of the following:

DocumentRoot /var/www/html

With that setting in place, a request for <http://example.com/one/two/three.html> will result in the file /var/www/html/one/two/three.html being served to the client with a MIME type derived from the file name - in this case, text/html.

2.3. DirectoryIndex

The DirectoryIndex directive specifies what file, or files, will be served in the event that a directory is requested. For example, if you have the configuration:

DocumentRoot /var/www/html
DirectoryIndex index.html index.php

Then when the URL <http://example.com/one/two/> is requested, Apache httpd will attempt to serve the file /var/www/html/index.html and, if it’s not able to find that, will attempt to serve the file /var/www/html/index.php.

If neither of those files is available, the next thing it will try to do is serve a directory index.

2.4. FallbackResource

The FallbackResource directive, provided by mod_dir, defines a default resource to serve when a request doesn’t map to any existing file in the filesystem. This is the mechanism behind the “front controller” pattern used by virtually every modern web framework — Laravel, Symfony, WordPress, Drupal, and many others.

Before FallbackResource existed (it was introduced in httpd 2.2.16), the standard way to implement a front controller was a mod_rewrite rule like this:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ /index.php [L]

That four-line incantation — “if it’s not an existing file or directory, send it to index.php” — appears in countless .htaccess files across the web. FallbackResource replaces it with a single line:

FallbackResource /index.php

Existing files — images, CSS, JavaScript, static HTML — are served normally. Only requests that would otherwise produce a 404 are routed to the specified resource. The original request URL is available to the handler via the REQUEST_URI server variable.

If the front controller lives in a subdirectory, specify the full path:

<Directory "/var/www/html/app">
    FallbackResource /app/index.php
</Directory>

You can disable FallbackResource in a child directory to prevent inheritance — useful for directories that should return a genuine 404 when a file isn’t found:

<Directory "/var/www/html/app/static">
    FallbackResource disabled
</Directory>

If you find yourself writing a RewriteCond !-f / RewriteCond !-d pair, stop and consider FallbackResource first. It’s simpler, faster (no regex engine involved), and communicates the intent much more clearly. Save mod_rewrite for cases where you need to transform the URL, not merely route it.

2.5. Automatic directory listings

The module mod_autoindex serves a file listing for any directory that doesn’t contain a DirectoryIndex file. (See section DirectoryIndex above.)

To permit directory listings, you must enable the Indexes setting of the Options directive:

Options +Indexes

See the documentation of the Options <https://httpd.apache.org/docs/current/mod/core.html#options> for further discussion of that directive.

If the Indexes option is on, then a directory listing will be displayed, with whatever features are enabled by the IndexOptions directive.

Typically, a directory will look like the example shown below.

AutoIndex

For further discussion of the autoindex functionality, consult the mod_autoindex documentation at <https://httpd.apache.org/docs/current/mod/mod_autoindex.html>.

Future versions of this book will include more detailed information about directory listings.

2.6. Alias

The Alias directive is used to map a URL to a directory path outside of your DocumentRoot directory.

Alias /icons /var/www/icons

An Alias is usually accompanied by a <Directory> stanza granting httpd permission to look in that directory. In the case of the above Alias, for example, add the following:

<Directory /var/www/icons>
  Require all granted
</Directory>

Or, if you’re using httpd 2.2 or earlier:

<Directory /var/www/icons>
  Order allow,deny
  Allow from all
</Directory>

There’s a special form of the Alias directive - ScriptAlias - which has the additional property that any file found in the referenced directory will be assumed to be a CGI program, and httpd will attempt to execute it and sent the output to the client.

CGI programming is outside of the scope of this book. You may read more about it at https://httpd.apache.org/docs/current/howto/cgi.html

2.7. AliasMatch and ScriptAliasMatch

AliasMatch and ScriptAliasMatch are regex-capable versions of Alias and ScriptAlias. They allow you to use regular expressions to match the URL and use backreferences in the target path.

For example, to map user CGI directories without mod_userdir:

ScriptAliasMatch "^/~([a-zA-Z0-9]+)/cgi-bin/(.+)" "/home/$1/cgi-bin/$2"

A request for http://example.com/~alice/cgi-bin/stats.pl would execute /home/alice/cgi-bin/stats.pl.

Similarly, AliasMatch can map patterns of URLs to filesystem locations:

AliasMatch "^/docs/([a-z]{2})/" "/srv/docs/$1/"

This maps /docs/en/ to /srv/docs/en/, /docs/fr/ to /srv/docs/fr/, and so on.

A common gotcha: unlike Alias, which treats the URL prefix as a literal string, AliasMatch consumes the entire URL-path during the match. If you’re not careful with your regex, you may capture more or less than you intended. Use anchors (^ and $) and be deliberate about what your groups capture.

2.8. Redirect

The purpose of the Redirect directive is to cause a requested URL to result in a redirection to a different resource, either on the same website or on a different server entirely.

The Redirect directive results in a Location header, and a 30x status code, being sent to the client, which will then make a new request for the specified resource.

The exact value of the 30x status code will influence what the client does with this information, as indicated in the table below:

Code

Meaning

300

Multiple Choice - Several options are available

301

Moved Permanently

302

Temporary Redirect

304

Not Modified - use whatever version you have cached

Other 30x statuses are available, but these are the only ones we’ll concern ourselves with at the moment.

The syntax of the Redirect directive is as follows:

Redirect [status] RequestedURL TargetUrl

2.9. RedirectMatch

RedirectMatch is the regex-capable counterpart to Redirect. It allows you to match the requested URL against a regular expression and use backreferences in the target URL.

For example, to redirect an entire directory tree while preserving the path structure:

RedirectMatch 301 "^/oldsite/(.*)" "https://newsite.example.com/$1"

This redirects /oldsite/page.html to https://newsite.example.com/page.html, and so on for any path under /oldsite/.

Another common use is stripping or adding file extensions:

RedirectMatch 301 "^/(.+)\.htm$" "/$1.html"

RedirectMatch is often a simpler and more appropriate choice than a RewriteRule with the [R] flag when all you need is a pattern-based redirect. It doesn’t require RewriteEngine On and it expresses the intent — “redirect” — directly.

2.10. Location

The <Location> directive limits the scope of the enclosed directives by URL. It is similar to the <Directory> directive, and starts a subsection which is terminated with a </Location> directive. <Location> sections are processed in the order they appear in the configuration file, after the <Directory> sections and .htaccess files are read, and after the <Files> sections.

<Location> sections operate completely outside the filesystem. This has several consequences. Most importantly, <Location> directives should not be used to control access to filesystem locations. Since several different URLs may map to the same filesystem location, such access controls may be circumvented.

The enclosed directives will be applied to the request if the path component of the URL meets any of the following criteria:

The specified location matches exactly the path component of the URL. The specified location, which ends in a forward slash, is a prefix of the path component of the URL (treated as a context root). The specified location, with the addition of a trailing slash, is a prefix of the path component of the URL (also treated as a context root). In the example below, where no trailing slash is used, requests to /private1, /private1/ and /private1/file.txt will have the enclosed directives applied, but /private1other would not.

<Location /private1>
    #  ...
</Location>

In the example below, where a trailing slash is used, requests to /private2/ and /private2/file.txt will have the enclosed directives applied, but /private2 and /private2other would not.

<Location /private2/>
    # ...
</Location>

When to use <Location> Use <Location> to apply directives to content that lives outside the filesystem. For content that lives in the filesystem, use <Directory> and <Files>. An exception is <Location />, which is an easy way to apply a configuration to the entire server. For all origin (non-proxy) requests, the URL to be matched is a URL-path of the form /path/. No scheme, hostname, port, or query string may be included. For proxy requests, the URL to be matched is of the form scheme://servername/path, and you must include the prefix.

The URL may use wildcards. In a wild-card string, ? matches any single character, and * matches any sequences of characters. Neither wildcard character matches a / in the URL-path.

Regular expressions can also be used, with the addition of the ~ character. For example:

<Location ~ "/(extra|special)/data">
    #...
</Location>

would match URLs that contained the substring /extra/data or /special/data. The directive <LocationMatch> behaves identically to the regex version of <Location>, and is preferred, for the simple reason that ~ is hard to distinguish from - in many fonts, leading to configuration errors when you’re following examples.

<LocationMatch "/(extra|special)/data">
  #...
</LocationMatch>

The <Location> functionality is especially useful when combined with the SetHandler directive. For example, to enable status requests, but allow them only from browsers at example.com, you might use:

<Location /status>
  SetHandler server-status
  Require host example.com
</Location>

2.10.1. Virtual Hosts

Rather than running a separate physical server, or separate instance of httpd, for each website, it is common practice to run sites via virtual hosts. Virtual hosting refers to running more than one web site on the same web server.

Virtual hosts can be name-based - that is, multiple hostnames resolving to the same IP address - or IP based - that is, a dedicated IP address for each site - depending on various factors including availability of IP addresses and preference. Name-based virtual hosting is more common, but there are scenarios in which IP-based hosting may be preferred.

Virtual hosting is discussed in more detail in Virtual hosts and mod_rewrite.

2.11. mod_vhost_alias

When you have a handful of virtual hosts, writing an explicit <VirtualHost> block for each one is straightforward. When you have hundreds or thousands — as a hosting provider might — individual blocks become unmanageable. mod_vhost_alias solves this by dynamically deriving the document root from the hostname of the incoming request.

The simplest configuration uses VirtualDocumentRoot with the %0 interpolation token, which expands to the full server name:

UseCanonicalName Off
VirtualDocumentRoot "/var/www/vhosts/%0"

A request for http://www.example.com/page.html is served from /var/www/vhosts/www.example.com/page.html. No per-host configuration is needed — just create the directory and drop in the files.

More sophisticated interpolation tokens let you split the hostname into components. %1 is the first dot-separated part, %2 the second, %-1 the last, and so on. You can even extract individual characters for hash-based directory layouts:

VirtualDocumentRoot "/var/www/vhosts/%3+/%2.1/%2.2/%2.3/%2"

This maps www.domain.example.com to /var/www/vhosts/example.com/d/o/m/domain/.

There’s also VirtualScriptAlias and VirtualScriptAliasIP for CGI directories, and VirtualDocumentRootIP for IP-based mass hosting.

Before reaching for mod_rewrite to implement mass virtual hosting, check whether mod_vhost_alias does what you need — it’s faster and far simpler to maintain.

2.11.1. Proxying

mod_proxy and its family of protocol-specific sub-modules (mod_proxy_http, mod_proxy_fcgi, mod_proxy_ajp, mod_proxy_wstunnel, and others) allow httpd to forward requests to another server and return the response to the client. This is a form of URL mapping — the URL is mapped not to a local file but to a remote resource.

The most common directive is ProxyPass, which maps a local URL prefix to a backend:

ProxyPass        "/app"  "http://appserver.local:8080/app"
ProxyPassReverse "/app"  "http://appserver.local:8080/app"

ProxyPassReverse rewrites Location headers in the backend’s response so that redirects point to the proxy’s URL rather than the backend’s. Without it, clients may be redirected to URLs they can’t reach.

Proxying interacts with mod_rewrite via the [P] flag, which is discussed in Proxies and mod_rewrite. The short version: [P] causes a RewriteRule substitution to be treated as a proxy request. This is powerful but has subtleties — and in many cases a simple ProxyPass is both clearer and more efficient.

2.12. mod_proxy_express

mod_proxy_express does for reverse proxying what mod_vhost_alias does for document roots: it dynamically maps incoming hostnames to backend URLs using a DBM file, without requiring per-host configuration.

A simple text file maps hostnames to backends:

www1.example.com  http://192.168.211.2:8080
www2.example.com  http://192.168.211.12:8088
www3.example.com  http://192.168.212.10

Convert it to DBM with httxt2dbm, then enable the module:

ProxyExpressEnable on
ProxyExpressDBMFile /etc/httpd/proxy-map

This is a lightweight alternative to using RewriteMap with the [P] flag for dynamic reverse proxying.

2.12.1. mod_actions

mod_actions lets you trigger a CGI script based on the MIME type of the requested resource or the HTTP request method.

The Action directive maps a handler or MIME type to a CGI script:

Action image/gif /cgi-bin/image-handler.cgi

Any request for a .gif file will be handled by /cgi-bin/image-handler.cgi, which receives the original URL in the PATH_INFO and PATH_TRANSLATED environment variables.

You can also fire a script for a specific HTTP method using the Script directive:

Script PUT /cgi-bin/upload-handler.cgi

This is a niche feature, but when you need it, it’s simpler than trying to match request methods with mod_rewrite.

2.12.2. mod_imagemap

mod_imagemap provides server-side image map processing — an early web technology where different regions of an image could link to different URLs. Clicking on a specific area of the image sends the coordinates to the server, which looks them up in a map file and returns the appropriate URL.

While server-side image maps have been almost entirely replaced by client-side image maps (the HTML <map> and <area> elements) and modern JavaScript-driven interfaces, mod_imagemap remains part of the httpd distribution for backwards compatibility.

2.12.3. mod_negotiation

mod_negotiation implements content negotiation — the ability for the server to choose the best representation of a resource based on the client’s stated preferences (language, media type, encoding, character set).

The most visible feature is MultiViews, enabled via the Options directive:

Options +MultiViews

With MultiViews enabled, a request for /docs/manual causes httpd to search for files matching the pattern /docs/manual.* and choose the best match based on the Accept-* headers in the request. So if both manual.en.html and manual.fr.html exist, a French browser will receive the French version.

A more explicit approach uses type maps — files (typically with a .var extension) that list the available variants and their properties:

URI: manual

URI: manual.en.html
Content-Type: text/html
Content-Language: en

URI: manual.fr.html
Content-Type: text/html
Content-Language: fr

Content negotiation is worth understanding because it can interact with mod_rewrite in surprising ways. If MultiViews is on and you have a rewrite rule that expects a literal file path, the negotiation phase may match a file before your rule fires — or your rule may fire and then negotiation remaps the result. When debugging unexpected behavior, check whether MultiViews is enabled.

2.12.4. mod_userdir

mod_userdir enables the classic Unix convention of per-user web directories accessed via http://example.com/~username/. The UserDir directive specifies which directory within a user’s home directory serves as their web root:

UserDir public_html

A request for http://example.com/~alice/index.html is then served from /home/alice/public_html/index.html.

You can also point UserDir at an entirely different directory tree:

UserDir /var/www/users

This maps ~alice to /var/www/users/alice/, regardless of where Alice’s home directory actually is.

For security, you should typically disable UserDir for sensitive accounts — especially root:

UserDir disabled root

You can also take the whitelist approach — disable everyone and explicitly enable specific users:

UserDir disabled
UserDir enabled alice bob carol

The ~ in URLs can be awkward, and some administrators prefer cleaner paths like /users/alice/. This is achievable with an AliasMatch (see AliasMatch and ScriptAliasMatch above) and doesn’t require mod_userdir at all.

2.12.5. mod_speling

mod_speling [1] attempts to fix mistyped URLs by performing a case-insensitive match and allowing up to one character error — an insertion, omission, transposition, or wrong character.

Enable it with:

CheckSpelling On

Note that the directive is CheckSpelling — with two l’s. [2]

If a request for /Index.HTML doesn’t find a file but /index.html exists, mod_speling will issue a 301 redirect to the correct URL. If multiple close matches exist, the client receives a 300 (Multiple Choices) response listing the candidates.

Use CheckCaseOnly On to limit correction to capitalization differences without attempting to fix other misspellings.

Caveats:

  • mod_speling performs a directory scan for each miss, which can be expensive on busy servers or large directories.

  • It may match files you didn’t intend — for example, correcting a request for /status to /stats.html when you meant the server-status handler.

  • It should not be enabled in DAV-enabled directories, where it can redirect write operations to unintended resources.

mod_speling can eliminate a surprising number of 404 errors caused by case differences — particularly useful when migrating from a case-insensitive filesystem (Windows/IIS) to a case-sensitive one (Linux). It’s a lighter touch than writing RewriteRule patterns to handle every possible capitalization variant.

2.12.6. File not found

In the event that a requested resource is not available, after all of the above mentioned methods are attempted to find it, httpd returns a 404 (Not Found) response. The ErrorDocument directive lets you customize what the client sees:

ErrorDocument 404 /errors/not-found.html

The argument can be a local URL-path (as above), an external URL, or a simple text string (prefixed with a double-quote character):

ErrorDocument 404 "Sorry, we couldn't find that page.

ErrorDocument works for any HTTP status code, not just 404 — you can customize 403 (Forbidden), 500 (Internal Server Error), and others.

Note that FallbackResource (see FallbackResource above) fires before the 404 would be generated. If FallbackResource is set, ErrorDocument 404 will only trigger for requests that your fallback handler itself decides to reject.