Apache Tips and Tricks

Apache Configuration

See also: Apache and Active Directory

Using Google's Cache to Serve Deep Linked Images

I run a few web sites. I don't care if people copy any images off them to use on their own site. I don't mind if they use one of my own images to link back to my site. I do care if they just use an image on their site because they are to lazy to copy the image or to save their own bandwidth.

A friend suggested an Apache re-write rule to prevent it happening. After a tweak, I've found it most useful, I just need to wait long enough to take effect.

RewriteEngine on
RewriteCond     %{REQUEST_FILENAME} \.(gif|jpe?g|png)$       [NC]
RewriteCond     %{HTTP_REFERER}     !^$
RewriteCond     %{HTTP_REFERER}     !(www\.)?spack\.org      [NC]
RewriteCond     %{HTTP_REFERER}     !google\.                [NC]
RewriteCond     %{HTTP_REFERER}     !search\?q=cache         [NC]
RewriteRule     (.*)                -                    [F,NC,L]

Source: http://www.debian-administration.org/users/ajt/weblog/56

Blocking Deep Linked Images

These mod_rewrite rules allow you to redirect people deep linking directly to your images to a web page or image (be careful of loops if redirecting to an image):

RewriteEngine on
RewriteCond %{HTTP_REFERER} !^http://(www\.)?spack.org(/)?.*$     [NC]
RewriteCond %{HTTP_REFERER} !^http://(www\.)?adam.shand.net(/)?.*$     [NC]
RewriteRule .*\.(gif|jpg|jpeg|bmp|png)$     http://www.spack.org/index.cgi/DeepLinking [R,NC]

Source: http://www.htmlbasix.com/disablehotlinking.shtml

Shadow Authentication (mod_auth_shadow)

I recently had a system where I wanted to authenticate web users against the system accounts (eg. /etc/shadow). First I tried using mod_auth_pam but couldn't get it to work, and besides I didn't really like it as a solution because it required giving Apache permissions to read /etc/shadow (from a security point of view, any cgi or php script could do anything it wanted with my encrypted passwords, YUCK!). Out of curiosity I then tried using /etc/shadow file and using it as the htpasswd file (eg. "AuthUserFile /etc/shadow"), this worked but had the same disadvantages as auth_pam. Eventually I found auth_shadow which seems to do what I want without having to give Apache read access to the shadow file. It was fairly straight forward to get working, the only bit that threw me was AuthBasicAuthoritative and how it interacts with AuthUserFile.

When using AuthBasic with another authentication mechanism like AuthShadow you have to tell Apache to try the other authentication mechanism (in this case auth_shadow) after it tries basic authentication (ie. Basic isn't authoritative, try the next option as well). With the below configuration when a user tries to log into your site the first thing that happens is Apache tries to authenticate them against /etc/apache2/htpasswd, if the user exists in the htpasswd file then their authentication request will succeed or fail based only on the htpasswd file. If the user doesn't exist in the htpasswd file then it will try and authenticate them against the shadow file.

Here's the relevant configuration to make it work:

<Location />
    Options Indexes FollowSymLinks ExecCGI

    AuthName "www.spack.org"
    AuthShadow on
    AuthBasicAuthoritative off
    AuthType Basic
    AuthUserFile /etc/apache2/htpasswd

    require valid-user
</Location>

Caveats:

  • The AuthBasicAuthoritative directive is for Apache 2.2 and newer, for Apache 2.0 you need to use AuthAuthoritative.
  • It's important to remember that if the user exists at all in the htpasswd file
  • It will work without the AuthUserFile directive, however you will get an error in your Apache logs. The error annoyed me so I just created a blank htpasswd file and that seemed to fix the problem, the errors I was getting were:

    [Sat Sep 29 01:50:04 2007] [error] Internal error: pcfg_openfile() called with NULL filename
    [Sat Sep 29 01:50:04 2007] [error] [client 127.0.0.1] (9)Bad file descriptor: Could not open password file: (null)
    

Digest Authentication (mod_auth_digest)

The problem with the widely deployed "basic authentication" for Apache is that it requires that your passwords go across the network in clear text. This sucks as the only way to solve the problem is to use OpenSsl to deploy SSL/TLS encryption which has configuration, performance and cost penalties. Recently I discovered mod_auth_digest which allows you to password protect portions of you web site without having passwords travelling around the network in clear text, or requiring end to end encryption (SSL/TLS).

Make sure that the Apache module is enabled in your httpd.conf:

LoadModule digest_auth_module /usr/lib/apache/1.3/mod_auth_digest.so

Now choose a portion of your web site you want to password protect and configure Apache. I don't fully understand the AuthDigestDomain part of this yet, but it appears to allow you to have usernames and passwords shared between different sections of your web site and even different websites without the client having to re-enter their username/password.

<Location /edit>
    AuthType Digest
    AuthName "www.spack.org"
    AuthDigestFile /etc/apache/htdigestpasswd
    AuthDigestDomain /
    AllowOverride None
    Options None
    require user adam
</Location>

And finally configure a file, realm and password for the user that you've granted access to (in the above example the user is "adam"). Note that the realm must match what you list as the "AuthName" in your Apache configuration:

htdigest -c /etc/apache/htdigestpasswd www.spack.org adam
Adding password for adam in realm www.spack.org.
New password:
Re-type new password:

Apparently using digest authentication requires client side support, and mod_auth_digest is marked as experimental however all the clients I have access to seem to work flawlessly (Redhat Linux 9: Mozilla 1.5, Konqueror 3.1-12, Opera 7.21, cadaver; Mac OSX: Finder, Safari 1.0 (v85), IE 5.2).

Disabling PHP by Default

Typically when you enable the PHP within Apache you enable it everywhere. In some situations, especially if you are paranoid, giving everyone with write access to your DocumentRoot can be a deeply bad idea. Surprisingly it's quite hard to figure out how to disable PHP by default and only enable it where you want.

There are two ways that I know of to do this:

The proper way is to pass a flag to the PHP module telling it to disable PHP at the top of your document root and to then enable it where you need it:

<Directory /var/www>
    php_flag engine off
</Directory>

<Directory /var/www/php_okay>
    php_flag engine on
</Directory>

Source: http://adam.rosi-kessel.org/weblog/free_software/php_perils.html

The hacky way is to remove the mime type mapping which tells Apache that a file ending with a .php extension is a PHP file and should be parsed by the PHP module at the top of your DocumentRoot, and then remap it specifically where you need it:

<Directory /var/www>
    RemoveType .php .php3 .php4 .phtml
</Directory>

<Directory /var/www/php_okay>
    AddType application/x-httpd-php .php .php4 .php3 .phtml
</Directory>

Proxy Requests to Another Apache Instance

Sometimes you want to have a front end Apache server transparently redirect requests to another web server. I've done this for many reasons, the most common being either to redirect mod_perl traffic to a mod_perl enabled server and the second most common reason is to proxy web traffic from the internet through your corporate firewall to an internal server.

The best way to do this seems to be using the Apache ProxyPass directives. Lets say you want to punch a hole from a web server in your DMZ (gotham) through to your internal webmail server (nest). So in gotham's Apache configuration you would add these lines:

ProxyPass            /webmail/       http://nest.spack.org/
ProxyPassReverse     /webmail/       http://nest.spack.org/

This causes requests for http://gotham.spack.org/webmail/ to be redirected to the web server on nest. I used to use mod_rewrite for this but I found that sometimes I ended up with URL's not being rewritten correctly for the client and form's would try and have the client directly contact the internal server which obviously doesn't work. However for posterity here's the rewrite rules I used:

RewriteEngine on
RewriteRule  ^/(.*)  http://nest.spack.org/$1    [P,L]

Recompiling Suexec for Different Defaults

Suexec is a very powerful and useful way of getting CGI programs to run as a different user then the default Apache web user. However it's default security checks are compile time options and very strict, because of this if you have an unusual or customised setup you may well need to recompile it in order to make it behave in the way you need.

  1. Download the full Apache sources from http://httpd.apache.org/ (make sure you match the version you download the the version you are actually running).

  2. Untar the sources and change into that directory.

  3. Check how your current suexec is compiled, you want to keep as many things as possible the same as your current setup or you are just making work for youself fixing up the differences when you recompile (the location of your suexec binary will vary depending on which distro you are using):

    # /usr/lib/apache2/suexec2 -V
     -D AP_DOC_ROOT="/var/www"
     -D AP_GID_MIN=100
     -D AP_HTTPD_USER="www-data"
     -D AP_LOG_EXEC="/var/log/apache2/suexec.log"
     -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
     -D AP_UID_MIN=100
     -D AP_USERDIR_SUFFIX="public_html"
    
  4. Configure Apache for your new desired settings:

    # ./configure --with-suexec-bin="/usr/lib/apache2"  --with-suexec-caller="apache" \
    

    --with-suexec-userdir="public_html" --with-suexec-docroot="/srv/www" --with-suexec-uidmin="1000" \ --with-suexec-gidmin="100" --with-suexec-logfile="/var/log/apache2/suexec.log"

  5. Change into the support directory inside the Apache source tree and build suexec:

    # cd support
    # make suexec
    
  6. Make sure that the new suexec binary has the values you expect:

    # ./suexec -V
     -D AP_DOC_ROOT="/srv/www"
     -D AP_GID_MIN=100
     -D AP_HTTPD_USER="apache"
     -D AP_LOG_EXEC="/var/log/apache2/suexec.log"
     -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
     -D AP_UID_MIN=1000
     -D AP_USERDIR_SUFFIX="public_html"
    
  7. Back up your old suexec binary and copy in the new one:

    # cp -p /usr/lib/apache2/suexec /usr/lib/apache2/suexec.bak
    # cp suexec /usr/lib/apache2/suexec
    

Relaxing Security in a Subdirectory

There are times when you might want to apply a tight security blanket over portions of your site, such as with something like:

<Directory /var/www>
    Satisfy All
    AuthUserFile /etc/apache/htpasswd
    Require valid-user
</Directory>

Due to Apache's scoping rules, this blanket applies to all documents in that directory and in any subordinate subdirectories underneath it. But suppose you want to make a subdirectory, such as /var/www/public, available without restriction?

The Satisfy directive is the answer. Add the following to either the .htaccess file in the subdirectory or in an appropriate container:

Satisfy Any
Order Deny,Allow
Allow from all

Source: O'Reilly Apache Cookbook, Recipe 6.9 (ISBN:0596001916)

Server Side Includes (mod_include)

I always forget one of these steps when I try to enable Server Side Includes under the ApacheWebServer. This is a public note to myself to remember all the steps so next time I don't have to scream abuse at my computer until I remember. These steps are specific to DebianLinux, but should be roughly correct for any UNIX installation of Apache.

With a modern version of Apache all of these changes will happen in /etc/apache/gd.conf, with older versions you might have to edit the now obsolete /etc/apache/srm.conf as well.

Make sure that Apache is configured to use the Includes module ?FootNote(If you Apache setup doesn't use DSO modules you don't need to do this step so long as the include module is compiled in. If it '''isn't''' compiled in then you have to recompile Apache.):

LoadModule includes_module /usr/lib/apache/1.3/mod_include.so

Make sure that Apache is setup to recognise when a file needs to be server parsed ?FootNote(Using .html as the server-parsed file extension does imply a performance penalty because now every .html file needs to be parsed regardless of whether it needs to be or not. However unless you have a very heavily loaded server you shouldn't notice the difference. The default Apache configuration is to use .shtml as the server parsed HTML extension. Another option is the X-bit hack, deprecated but still useful, this means that if the HTML file has the excute bit set, the server will parse it.):

AddType text/html .html
AddHandler server-parsed .html

Enable Includes in the relevant directory ?FootNote(You can replace the "Includes" option with "IncludesNoExec" in order to limit SSI functionality to the inclusion of files, and exlude the ability to run arbitrary files.):

<Directory /var/www/>
    Options Indexes SymLinksIfOwnerMatch Includes
</Directory>

If you are running virtual domains you can use the SuExec feature of Apache to make sure that SSI's (and CGI's) run as a specific user and don't leave a virtual chroot area.

WebDAV Configuration (mod_dav)

WebDAV provides a useful way to be able to treat web folders as local folders. This is can be great for providing people ways to upload, download and edit their web content directly and without having an actual system account on the server.

There are two big disadvantages to DAV, first that there are no popular browsers or OS's which natively support DAV over SSL, so make sure that any usernames and passwords you configure aren't privledged for anything else. Second, all actions performed through the DAV service are performed as the use which your web server runs as and can't be over-ridden by suexec. This is a security consideration in a virtualhosting enviroment because users can potentially read and/or overwrite each others files, and it's also an major inconvenience if you want to provide users with shell access as well as DAV access to their files (because files uploaded via DAV aren't owned by the shell user). I've talked to the Apache people about this and apparently it's a inherent limitation in the way Apache and the DAV module interact which they have no intention of addressing. Suck.

Here's how you configure Apache to enable DAV:

<IfModule mod_dav.c>
    DAVLockDB /var/lock/DAV/Lock
    DAVMinTimeout 0
    DAVDepthInfinity off
    LimitXMLRequestBody 1000000
</IfModule>

NOTE: The DAVLockDB directive refers to a file root '''not''' a directory. With the above configuration it will try and create the /var/lock/DAV/Lock.dir and /var/lock/DAV/Lock.pag files. If you assume like I did that it refers to a directory and leave off the trailing slash of the DAVLockDB directive Apache will try and create files called /var/lock/DAV.dir and /var/lock/DAV.pag which it will almost certainly not have permission to do. Strace is your only friend.''

And then enable a directory or location for DAV:

Alias /webdav         /var/www/www.spack.org
<IfModule mod_dav.c>
    <Location /webdav>
        DAV On

        AllowOverride None
        Options None
        ForceType text/plain

        AuthType Basic
        AuthName "www.spack.org"
        AuthUserFile /etc/apache/htpasswd

        <LimitExcept GET HEAD OPTIONS>
            require user adam
        </LimitExcept>

    </Location>
</IfModule>

Useful things to note. The IfModule directive mean that if the DAV module isn't available for some reason the contained directives just won't be used, rather then causing Apache to fail to start. The reason for the /webdav Alias is to create a seperate DAV only name space which can never conflict with your norman URL's. This allows you to use the 'ForceType text/plain" directive which is important to stop Apache parsing server side stuff like CGI's and PHP before allowing you to edit them.