How to Configure Nginx FastCGI Caching on RHEL 7

FastCGI caching in Nginx allows you to store responses from your PHP backend — typically PHP-FPM — directly on disk or in memory, serving subsequent identical requests without re-executing PHP. On a busy RHEL 7 server this can reduce backend load by an order of magnitude, cutting response times from hundreds of milliseconds to single-digit milliseconds for cached pages. This guide walks you through every layer of Nginx FastCGI caching: declaring the cache zone, keying and validating entries, wiring it into your server block, bypassing the cache for authenticated users, adding diagnostic headers, and purging stale content.

Prerequisites

  • RHEL 7 with a valid subscription or access to EPEL/Remi repositories
  • Nginx 1.10 or later installed and running (systemctl status nginx)
  • PHP-FPM configured and listening on a Unix socket or TCP port
  • Root or sudo access
  • SELinux awareness — file-context labelling for the cache directory may be required

Step 1: Understand How Nginx FastCGI Caching Works

Nginx FastCGI caching intercepts the response that PHP-FPM (or any FastCGI process) returns and writes it to a cache zone. The zone is a combination of shared memory (for the key lookup index) and a directory on disk (for the cached response bodies). When the same request arrives again, Nginx checks the index, finds a valid entry, and serves the file directly — PHP is never called.

Three directives form the foundation:

  • fastcgi_cache_path — defines the disk location, memory zone size, and expiry rules for inactive entries
  • fastcgi_cache_key — a string Nginx hashes to produce a unique cache key per request
  • fastcgi_cache_valid — how long a cached response is considered fresh for a given HTTP status code

Step 2: Create the Cache Directory

Choose a path with enough disk space. A dedicated tmpfs mount gives the best performance for high-traffic sites.

# Create the directory
sudo mkdir -p /var/cache/nginx/fastcgi

# Give Nginx ownership
sudo chown nginx:nginx /var/cache/nginx/fastcgi

# Apply the correct SELinux file context so Nginx can write to it
sudo semanage fcontext -a -t httpd_cache_t "/var/cache/nginx/fastcgi(/.*)?"
sudo restorecon -Rv /var/cache/nginx/fastcgi

If semanage is not installed, add it with sudo yum install -y policycoreutils-python.

Step 3: Declare the Cache Zone with fastcgi_cache_path

The fastcgi_cache_path directive belongs in the http block of your Nginx configuration — typically /etc/nginx/nginx.conf. Open that file and add the following inside the http { } block, outside any server { } block:

fastcgi_cache_path /var/cache/nginx/fastcgi
                   levels=1:2
                   keys_zone=PHP_CACHE:100m
                   max_size=2g
                   inactive=60m
                   use_temp_path=off;

Parameter breakdown:

  • levels=1:2 — creates a two-level directory hierarchy to avoid thousands of files in one folder
  • keys_zone=PHP_CACHE:100m — names the zone PHP_CACHE and allocates 100 MB of shared memory for the key index (roughly 800,000 keys per 1 MB)
  • max_size=2g — caps total disk usage at 2 GB; Nginx evicts least-recently-used entries when this is reached
  • inactive=60m — entries not accessed within 60 minutes are deleted regardless of validity
  • use_temp_path=off — writes directly to the cache directory, avoiding an extra file copy

Step 4: Set the Cache Key

The cache key determines what makes two requests “the same.” A minimal but practical key includes the scheme, host, and full URI including the query string:

fastcgi_cache_key "$scheme$request_method$host$request_uri";

Place this also in the http block so it applies globally, or inside a specific server block to scope it. If your application varies output by cookie (e.g., a locale cookie), append $cookie_lang to the key. Never include session cookies in the key — that defeats the purpose of caching and creates one cache entry per user.

Step 5: Enable Caching in the Location Block

Within your server block, find (or create) the location ~ .php$ block that proxies requests to PHP-FPM and add the cache directives:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/html;
    index index.php index.html;

    # Map to control cache bypass
    set $skip_cache 0;

    # Do not cache POST requests
    if ($request_method = POST) {
        set $skip_cache 1;
    }

    # Do not cache URIs containing query strings
    if ($query_string != "") {
        set $skip_cache 1;
    }

    # Do not cache certain paths
    if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap") {
        set $skip_cache 1;
    }

    # Do not cache for logged-in users or recent commenters
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
        set $skip_cache 1;
    }

    location ~ .php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # Cache settings
        fastcgi_cache        PHP_CACHE;
        fastcgi_cache_valid  200 301 302 60m;
        fastcgi_cache_valid  404              1m;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache     $skip_cache;

        # Add a header so you can see cache hits/misses
        add_header X-Cache-Status $upstream_cache_status;
    }
}

Step 6: Configure fastcgi_cache_valid for Multiple Status Codes

You can specify different TTLs for different HTTP status codes, which is important for not caching errors for too long:

fastcgi_cache_valid 200 301 302  60m;   # Cache successful and redirect responses for 1 hour
fastcgi_cache_valid 404           1m;   # Cache not-found responses for only 1 minute
fastcgi_cache_valid any           10m;  # Cache everything else for 10 minutes

Step 7: Add the X-Cache-Status Header for Diagnostics

The $upstream_cache_status variable contains one of the following values: HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING, REVALIDATED. Adding it as a response header lets you verify caching behaviour with curl:

add_header X-Cache-Status $upstream_cache_status always;

Test it after reloading Nginx:

curl -sI http://example.com/ | grep X-Cache-Status
# First request → X-Cache-Status: MISS
# Second request → X-Cache-Status: HIT

Step 8: Bypass the Cache for Logged-In Users

The $skip_cache variable already handles WordPress session cookies in Step 5. The key directives are:

  • fastcgi_cache_bypass $skip_cache — when $skip_cache is 1, Nginx fetches from PHP-FPM even if a cached copy exists
  • fastcgi_no_cache $skip_cache — when $skip_cache is 1, the fresh response from PHP-FPM is not stored in the cache

Using both directives together ensures that logged-in users always receive live PHP output and that their personalised responses do not overwrite cached public pages.

Step 9: Cache Purging Approaches

Nginx open-source does not include a built-in purge module. Two practical approaches exist:

Option A: Manual cache wipe

# Remove all cached files (safe to do while Nginx is running)
sudo find /var/cache/nginx/fastcgi -type f -delete

Option B: ngx_cache_purge module (Nginx Plus or self-compiled)

If you compile Nginx with the ngx_cache_purge third-party module, you can expose a purge endpoint:

location ~ /purge(/.*) {
    allow 127.0.0.1;
    deny all;
    fastcgi_cache_purge PHP_CACHE "$scheme$request_method$host$1";
}

Then purge a specific URL with:

curl -X PURGE http://127.0.0.1/purge/path/to/page

Option C: Stale-while-revalidate pattern

Add these directives so Nginx serves stale content while it refreshes in the background, eliminating cache-miss latency spikes:

fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 5s;

Step 10: Reload Nginx and Verify

# Test configuration syntax
sudo nginx -t

# Reload without dropping connections
sudo systemctl reload nginx

# Watch the cache directory populate
watch -n 1 'du -sh /var/cache/nginx/fastcgi'

# Check for cache hits
curl -sI http://example.com/ | grep -i cache

If nginx -t reports errors, check for misplaced directives — fastcgi_cache_path and fastcgi_cache_key must be in the http block, while fastcgi_cache, fastcgi_cache_valid, fastcgi_cache_bypass, and fastcgi_no_cache belong in a location block.

Conclusion

Nginx FastCGI caching is one of the most impactful performance improvements you can make to a PHP-backed website on RHEL 7 without changing your application code. By carefully tuning the cache key to include only the dimensions that actually differentiate responses, setting appropriate TTLs per status code, and using the bypass/no-cache flags for authenticated sessions, you gain a full-page cache that is both fast and correct. Monitor X-Cache-Status headers and the cache directory size regularly as traffic patterns evolve, and revisit your inactive and max_size parameters accordingly.