How to cache static content in apache http server for performance optimization

How to cache static content in apache http server for performance optimization

ahs_mod_cache_setup

Here in this article we will try to understand about HTTP caching and implement a very basic setup to cache static content for a website and validate the caching status.

Test Environment

  • Fedora server 41
  • Apache httpd v2.4.63

HTTP Caching

HTTP Caching is used to store response associated with a request and reuse the stored response for subsequent requests.

Key Components of HTTP Caching

  • Cache: A storage location (browser, proxy server, or CDN) where the cached data is kept.
  • Cache-Control Headers: Instructions sent by the server to define how and for how long the resources should be cached.
  • Expiration and Validation: Mechanisms to ensure that the cached data is fresh and valid.

Advantages of HTTP Caching

  • Improved Performance: Reduces the time needed to load a webpage by serving cached data instead of fetching it from the server.
  • Reduced Bandwidth Usage: Limits the amount of data transmitted between the client and server.
  • Reduced Server Load: Offloads the need to process requests by serving cached responses.
  • Better User Experience: Faster page load times improve the overall user experience.

Types of Caches

  • Private Caches
  • Shared Caches
    • Proxy Caches
    • Managed Caches

Private cache: They are used to store data that is personalized or relevant to a single user. They are typically used to improve performance and responsiveness by storing frequently accessed or user-specific data locally such as browser cache.

Proxy caches and managed caches are both caching strategies used to improve website performance and reduce traffic load on origin servers.

Proxy caches: They are usually implemented by intermediary servers (proxies) that store copies of frequently accessed web content.

Managed caches: They are explicitly deployed by service developers to offload the origin server and deliver content efficiently, often using specific caching strategies.

Cache-Control Directives

HTTP caching behavior is controlled using headers, such as:

  • Cache-Control: Specifies caching rules (e.g., no-cache, no-store, max-age).
  • Expires: Defines an expiration date for the cached resource.
  • ETag: Helps validate whether the cached version is still up-to-date.
  • Last-Modified: Indicates the last time the resource was modified.

For more details please refer HTTP Caching.

High Level Architecture

Procedure

Step1: Ensure Apache HTTP server installed and running

As a first step we need to ensure that httpd service is installed and running on the server as shown below.

admin@linuxser:~$ rpm -qa | grep httpd-2.4.*
httpd-2.4.63-1.fc41.x86_64
admin@linuxser:~$ sudo systemctl status httpd.service 
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/system/service.d
             └─10-timeout-abort.conf, 50-keep-warm.conf
     Active: active (running) since Thu 2025-04-24 10:05:35 IST; 1h 51min left

Step2: Ensure mod_cache module is loaded

The default httpd package installation on Fedora 41 OS loads the “mod_cache” module from the “00-base.conf” module configuration file as shown below.

admin@linuxser:~$ cat /etc/httpd/conf.modules.d/00-base.conf | grep mod_cache
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
LoadModule cache_socache_module modules/mod_cache_socache.so

We can also validate it by listing all the dynamically loaded modules by running below command. As we can see the module is loaded.

admin@linuxser:~$ httpd -M | grep "\<cache"
 cache_module (shared)
 cache_disk_module (shared)
 cache_socache_module (shared)

Step3: Create and Update configuration file

Let us create a new file named “cachedemo.conf” in the “/etc/httpd/conf.d/” which is by default including in the default “httpd.conf” configuration file.

This configuration basically suggests httpd service to cache any static content under the root context “/” and also any static images and javascript files under “/images” and “/js” context as shown below. The cached data is stored at the location specified by “CacheRoot” directive.

  • CacheRoot: The directory root under which cache files are stored
  • CacheEnable: Enable caching of specified URLs using a specified storage manager
  • CacheDirLevels: The number of levels of subdirectories in the cache
  • CacheDirLength: The number of characters in subdirectory names

For more details refer mod_cache.

admin@linuxser:~$ cat /etc/httpd/conf.d/cachedemo.conf 
CacheRoot   "/var/cache/apache/"
CacheEnable disk /
CacheDirLevels 2
CacheDirLength 1

<Location "/">
   CacheEnable disk
</Location>

<Location "/images/">
   CacheEnable disk
</Location>

<Location "/js/">
   CacheEnable disk
</Location>

Ensure that the “CacheRoot” directive directory exists as shown below.

admin@linuxser:~$ sudo mkdir -p /var/cache/apache
admin@linuxser:~$ sudo chown apache:apache /var/cache/apache

Also let us update the logformat that is used for access_log to log the cache-status as shown below.

root@linuxser:/etc/httpd/conf# cat httpd.conf | grep -i logformat
    LogFormat "%{cache-status}e %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

Step4: Upload a static content

Here in this step we just need to get some static content for our website. Let me just clone Mozilla Firefox beginner html site code for this demo.

root@linuxser:/var/www/html# git clone https://github.com/mdn/beginner-html-site-styled.git
root@linuxser:/var/www/html# mv beginner-html-site-styled/* .
root@linuxser:/var/www/html# rm -rf beginner-html-site-styled

Step5: Restart httpd service

Once the configuration is updated we need to restart the httpd service as shown below.

admin@linuxser:~$ sudo systemctl restart httpd.service

Step6: Validate Caching Result

Here we will try to access our “index.html” by setting the “Cache-Control” header with directive “max-age=30”. This basically means the page is going to be valid only for 30 sec and it needs to be revalidated by the cache service by sending a conditional request to the origin server asking whether the resource has been updated since last cached results.

admin@fedser:~$ for i in {1..50}; do curl -H "Cache-Control: max-age=30" http://linuxser.stack.com/index.html; sleep 1; done

As you can see from the results the cache results needs be revalidated after 30 sec by sending a conditional request and if no changes are made to resource the cache results will be refreshed again.

root@linuxser:/var/log/httpd# tail -f access_log
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:26:58 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:26:59 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:00 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:01 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:02 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:03 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:04 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:05 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:06 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:07 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:08 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:09 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:10 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:11 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:12 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:13 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:14 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:15 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:16 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:17 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:18 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:19 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:20 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:21 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:22 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:23 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:24 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:25 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:26 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
cache hit 192.168.122.1 - - [11/May/2025:01:27:27 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"
conditional cache hit: entity refreshed 192.168.122.1 - - [11/May/2025:01:27:28 +0530] "GET /index.html HTTP/1.1" 200 1092 "-" "curl/8.2.1"

If you do not want a response to be reused, but instead want to always fetch the latest content from the server, you can use the no-cache directive to force validation as shown below.

admin@fedser:~$ for i in {1..50}; do curl -I -H "Cache-Control: no-cache" http://linuxser.stack.com/index.html; sleep 1; done

Now if you check the logs, the cache results will never will used and it will always try to fetch the content from origin server showing the cache result as “miss” as shown below.

root@linuxser:/var/log/httpd# tail -f access_log
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:41 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:42 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:43 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:44 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:45 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:46 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:47 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:48 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:49 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:50 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:51 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:52 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:54 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:55 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:56 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:57 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:58 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:35:59 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:00 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:01 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:02 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:03 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:04 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:05 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:06 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:07 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:08 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:09 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:10 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:11 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:12 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:13 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:14 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:15 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:16 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:17 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:18 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:19 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"
cache miss: attempting entity save 192.168.122.1 - - [11/May/2025:01:36:20 +0530] "HEAD /index.html HTTP/1.1" 200 - "-" "curl/8.2.1"

Here we just looked at the very basic implementation of caching in apache http server, but the specifics of managing cache freshness, different caching strategies, and various HTTP headers can become intricate.

Hope you enjoyed reading this article. Thank you..