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.
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]
Just to keep you on your toes.
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_spelingperforms 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
/statusto/stats.htmlwhen you meant theserver-statushandler.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.