Table of Contents

Using Apache Memory Cache

Apache has its own memory cache module mod_mem_cache which can be used to cache content similar to memcached. The following directives configure Apache to use 1GB of memory and will cache up to 1000 objects, each between 1 byte and 1 megabyte.

<IfModule mod_mem_cache.c>
  Cache Enable mem /
  MCacheSize 1074741824
  MCacheMaxObjectCount 1000
  MCacheMaxObjectSize 1048576
</IfModule>

Usual Required Modules

authz_host_module
log_config_module
headers_module
setenvif_module
# Proxy needed by default on OSX
proxy_module
mime_module
# DAV needed by default on OSX
dav_module
dav_fs_module
# Status needed by default on OSX
status_module
negotiation_module
dir_module
userdir_module
dir_module
userdir_module
apple_userdir_module
alias_module
rewrite_module
php5_module

Remove Directory Listing

Find:

Options Indexes FollowSymLinks MultiViews

and remove Indexes from the options list.

Allow and Deny Network

Every directory can be protected with Allow and Deny. The following example denies access to the root path / to anybody except the local network 192.168.1.0/24:

<Directory />
       Order deny,allow
       Allow from 192.168.1.0/24
       Deny from all
</Directory>

The Order is important - it says to first deny, which resolves to Deny from all and after that to allow which resolves to Allow from 192.168.1.0/24.

Reverse Proxy with Virtual Host

Apache can be configured as a reverse proxy in order to pass traffic to a backend. For example, the following directives:

<VirtualHost *:80>
       ProxyRequests Off
       ProxyPreserveHost On
       ProxyPass / http://192.168.0.1:80/ connectiontimeout=300 timeout=300
       ProxyPassReverse / http://192.168.0.1:80/
       ServerName website.com
       ServerAlias *.website.com
       ErrorLog ${APACHE_LOG_DIR}/error.log
       LogLevel warn
       CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

will redirect all traffic for website.com and any sub-domain, such as site.website.com to the host 192.168.0.1. Additionally, it specifies that connections will timeout in 300s. As long as:

<VirtualHost *:80>

is specified, then you can have any number of websites to be proxied to their respective backends.

Block Bad Bots

On Debian-like systems, a global rule can be set to block bad bots. In order to do so create a file: /etc/apache2/conf.d/blockbots.conf containing the bad_bots configuration.

You must also enable the setenvif module with:

a2enmod setenvif

Create WebDAV Share

The following directive will share /var/www/webserver/share through WebDAV:

        <Directory /var/www/webserver/share>
                DAV On
                AuthType Digest
                AuthName "share"
                AuthDigestDomain /
                AuthDigestProvider file
                AuthUserFile /etc/apache2/.htpasswd
                <LimitExcept PROPFIND>
                    Require valid-user
                </LimitExcept>
        </Directory>

It seems that Directory is prefered instead of Location; the latter leading sometimes to System Error 67 meaning that the network path could not be found.

The authentication mechanism to use for maximum compatibility is Digest and username and password pairs can be generated using the htdigest command line utility. For this example:

htdigest -c /etc/apache2/.htpasswd share username

which will prompt for a password.

If you have been trying for a while to access the DAV share from Windows and are prompted for the username and password over and over again even if you entered the correct information, you may want to run in Windows on a command prompt:

net stop WebClient
net start WebClient

in order to restart the web-client thereby resetting WebDAV caches and fixing the username and password prompt loop.

Set Expiration for Resources

After enabling mod_expires with:

a2enmod expires

after which an optimized configuration can be added as a file under /etc/apache/conf.d/ in order to set the expiration time for resources.

Apache Logs Statistics Through using The Shell

We use zcat in combination with the -f flag to read compressed and uncompressed files. The commands assume that your current working directory is in the apache log directory. The commands also assume that the files are named access.log* which may be different on other systems (ie: access_log).

Get Number of Unique Visitors

zcat -f access.log* | awk '{print $1}' | sort -u | wc -l

Get Number of Unique Returning Visitors

zcat -f access.log* | awk '{print $1}' | sort | uniq -c -d | sort -n -k 1

Apache Image Hot-Link Protection

The idea of preventing people from hot-linking images on your website is contradictory in itself: if you are serving content, then why prevent people from using the content you provide? You can instead shut your website down and keep the content to yourself.

Nevertheless, some people chose to do this, and one way to prevent hot-linking is to branch on the referrer (typo intentional in the example below) and deny any access to images when the referrer is not your website. In the example, we assume that www.example.com and example.com are the accepted domains used as referrer to your images and any other referrer, except a blank referrer (last rule) is denied the connection:

SetEnvIfNoCase Referer "^http://www.example.com/" locally_linked=1
SetEnvIfNoCase Referer "^http://www.example.com$" locally_linked=1
SetEnvIfNoCase Referer "^http://example.com/" locally_linked=1
SetEnvIfNoCase Referer "^http://example.com$" locally_linked=1
SetEnvIfNoCase Referer "^$" locally_linked=1
<FilesMatch "\.(gif|png|jpe?g)$">
Order Allow,Deny
Allow from env=locally_linked
</FilesMatch>

Note that branching on referrers is a very bad idea, given upstream caches that may manipulate headers as well as the possibility to circumvent this protection easily by sending a different referrer (your domain) along with the request. Bad, bad idea.

Permanently Redirecting pages

The best way to permanently redirect surfers to a different website is to RedirectMatch with the permanent option. This will ensure that the 301 (permanently moved) will be sent before redirecting.

Suppose you have a virtual host configuration for the domain servername.com:

<VirtualHost *:80>
  ServerName    servername.com
  DocumentRoot  /var/www/servername.com
  <Directory /var/www/servername.com
    Order allow,deny
    Allow from all
  </Directory>
</VirtualHost>

and that you want to redirect www.servername.com to servername.com. You would then add another virtual host that will perform the redirection:

<VirtualHost *:80>
  ServerName  www.servername.com
  RedirectMatch permanent ^/(.*) http://servername.com/$1
</VirtualHost>

Migrating Website Domain

In this example, we want to move somesite.net to newsite.org. We already have a virtual host in-place to handle newsite.org and we need to redirect every page from somesite.net as well as any sub-domains to newsite.org. In order to do that, we modify the old virtual-host for somesite.net:

<VirtualHost *:80>
        ServerName    somesite.net
        ServerAlias   *.somesite.net
        RewriteEngine on
        RewriteCond   %{HTTPS}s    ^..(s?)
        RewriteRule   ^            -                                 [E=PROTO:http%1]
        RewriteCond   %{HTTP_HOST} !^\w+\.newsite\.org$              [NC]
        RewriteCond   %{HTTP_HOST} ^(\w+)\.\w+\..+                   [NC]
        RewriteRule   ^/?(.*)      %{ENV:PROTO}://%1. newsite.org/$1 [R=301,L]
        RewriteCond   %{HTTP_HOST} somesite\.net
        RewriteRule   ^/?(.*)      %{ENV:PROTO}://newsite.org/$1     [R=301,L]
</VirtualHost>

this will match both protocols, HTTP and HTTPs (matching the last s), it will also match any sub-domains and redirect everything to newsite.org.

Disabling Timeouts for Long-Running Services

When using a web-server for live streams or software repositories (git, svn, etc…) it is sometimes useful to disable timeouts. This can be done in apache using the following directives:

<IfModule reqtimeout_module>
    RequestReadTimeout header=0 body=0
</IfModule>
LimitRequestBody 0

Make Apache Case-Insensitive

When migrating documents from case-insensitive filesystems to a server with Apache and a case-sensitive system, some links may appear broken and inaccessible. In order to resolve this, enable the speling (note the lack of double-l) module with:

a2enmod speling

and then either in an .htaccess, Directory directive or globally add the setting:

CheckSpelling on

Configure Zend OpCache

To increase the amount of memory alloted to the Zend OpCache found in newer PHP variants, edit the file /etc/php5/apache2/conf.d/05-opcache.ini. It should contain something like the following:

; configuration for php ZendOpcache module
; priority=05
zend_extension=opcache.so

and then after that line add the following options:

opcache.memory_consumption=256
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=8000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1

Redirect to HTTPs and then Authenticate

Suppose you have a /support alias where clients should be able to access some protected data by authenticating first using basic authentication. In this case, the password may travel over the wire unencrypted such that we need to first redirect through HTTPs and then provide authentication.

In the example below a /support alias is created that is protected by basic authentication through PAM. By using the 403 error document we can redirect to the HTTPs version of the website site.tld.

Alias /support /usr/share/support
 
<Directory /opt/support>
  Options FollowSymLinks MultiViews
  AllowOverride All
 
  SSLOptions +StrictRequire
  SSLRequireSSL
  ErrorDocument 403 https://site.tld/support
 
  AuthType Basic
  AuthName "Restricted Area"
  AuthBasicProvider external
  AuthExternal pwauth
  GroupExternal unixgroup
  Require valid-user
 
</Directory>

The reason for this hack is that authentication takes precedence such that using mod_rewrite to create a redirect would not work. As such, the setup now generates a 403 error when the HTTP version of the website is requested, which in turn redirects to the HTTPs version where the authentication takes place over HTTPs.

Enable HSTS

If a website accepts a connection through HTTP and then redirects to HTTPS, there is a small window of opportunity where the client talks to the server without encryption that can be abused with a MITM attack.

First enable the headers module:

a2enmod headers

To enable HSTS, edit the SSL virtual host version of your website and add the following stanza:

Header always set Strict-Transport-Security "max-age=31556926; includeSubdomains;"

this will tell browsers to use the HTTPS version of the website without loading the HTTP version first.

The age in the example is set to one year and the includeSubdomains option will enable HSTS for any subdomains as well.

Enforce Strong Cryptography

When using Apache SSL, the following directives should be added in order to use strong cryptography:

SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder on
SSLCompression off
SSLSessionTickets off

the settings will disable SSLv2 and SSLv3 as well as enable most of the elliptic-curve cryptography.

Optionally, given a non self-signed certificate, SSL stapling can be used for apache above 2.3.3:

SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache shmcb:/var/run/ocsp(128000) 

Return 404 for Default Virtual Host

Create a virtual host file or create a virtual host using the following configuration:

<VirtualHost *:80>
	Redirect 404 /
</VirtualHost>

The effect is that if a visitor attempts to access the server IP directly, the default virtual host will return a 404 error.

Calculate the Optimal Maximum Number of Concurrent Connections

To calculate the MaxClients value for Apache, you first need to get the Resident Set Size (RSS) of an Apache process. In order to do this, under Linux, you can issue:

ps -ylC apache2 --sort:rss

otherwise, the top utility may work as well by watching the SIZE or RSS value.

The formula to calculate the value that should be passed to MaxClients in the Apache configuration is:

\begin{eqnarray*}
\text{MaxClients} &=& \frac{(\text{Total Memory}\mbox{ }GB) * 1024 - \text{Memory for Other Processes}\mbox{ }MB}{\text{Resident Set Size (RSS)}\mbox{ }MB} \\
\end{eqnarray*}

For instance, assuming a server with a total of 8GB of RAM, leaving 1GB over to other processes, and with an Apache RSS of 100MB the calculation would become:

\begin{eqnarray*}
\text{MaxClients} &=& \frac{8GB * 1024 - 1024}{100MB} \\
&\approx& 71 
\end{eqnarray*}

Implementing a Global and Consistent Directory Index Style

The following setup implements a global and consistent directory index style that can be used for multiple virtual hosts just by adding an include to any Directory stanza within a virtual host configuration.

where:

Alias "/fancyindex.css" "/var/www/.fancyindex.css
IndexOptions +Charset=UTF-8
IndexOptions +TrackModified
IndexOptions +Charset=UTF-8
IndexOptions +FoldersFirst
IndexOptions +NameWidth=*
IndexOptions +FancyIndexing
IndexOptions +HTMLTable
IndexOptions +SuppressDescription
IndexIgnore favicon.ico
IndexIgnore auth*
IndexIgnore include*
IndexIgnore css*
IndexIgnore share*
IndexIgnore upload*
IndexIgnore incoming*
IndexStyleSheet "/fancyindex.css"
Options +Indexes
    <Directory /path>
        include "includes/fancyindex.conf"
    </Directory>
* {
    font-family: monospace;
}

and is responsible for setting the CSS for the rendered index page.

IndexStyleSheet takes an URL relative to the virtual host document root such that the aliasing performed within /etc/apache2/conf-available/fancyindex.conf redirects the requests to the default virtual host where the /var/www/.fancyindex.css file is placed. It ain't pretty, but it works; at least short of changing IndexStyleSheet itself!

Perhaps a good reason for preferring this setup to using AllowOverride and .htaccess files is that the style of the directory index is generated by Apache itself rather than the website such that one can avoid mixing data with code. Furthermore, given multiple virtual hosts with the same owner, a consistent style may be preferred and with a centralized way of batch-changing all directory indexes.

Globally Enable brotli Compression

On Debian, to enable brotli compression, the Apache module must first be installed by issuing the command:

apt-get install brotli

and then the Apache module must be enabled by issuing the command:

a2enmod brotli

Lastly, create the file /etc/apache2/conf-available/brotli.conf with the following contents:

<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css text/javascript application/javascript
</IfModule>

The purpose of this file is to instruct the brotli Apache module to compress certain resources, namely textual content as well as javascript with the brotli compressor.

Finally, enable the configuration file by issuing the command:

a2enconf brotli

and reload the Apache configuration:

systemctl apache2 reload

The brotli compressor should now be enabled and will globally compress content as it is transferred from the Apache server.

Note that an alternative configuration is to add the contents of the file /etc/apache2/conf-available/brotli.conf to a virtual host definition in order to only compress some websites.

Rate Limiting an IP Network with mod_QoS

The process of rate-limiting an IP network with Apache mod_qos involves matching an IP address block using the Apache2 SetEnvIfExpr expression:

SetEnvIfExpr "-R '47.128.0.0/16'" Amazon=yes

where:

followed by using mod_qos to rate-limit:

QS_EventLimitCount Amazon 10 60

where: