Configuring Redis Object Cache for WordPress on a LEMP Stack

/ 8 min

Table of Contents

The Architecture of High-Performance WordPress

Let me start with the problem, because it shapes every decision that follows. A default WordPress install regenerates each page on every request: PHP boots, queries MariaDB, assembles the template, and hands HTML back to the browser. That round trip is fine when three people visit your blog. It collapses the moment a few thousand arrive at once.

Under heavy traffic, the bottleneck is rarely bandwidth. It's the PHP-FPM worker pool and the database holding everyone in a queue. The fix is conceptually simple. Stop running PHP for visitors who don't need it, and serve a flat HTML file straight off disk.

Architecture Diagram

For this build I deliberately set aside a repository-based LEMP install. Instead I compiled the CentminMod stack from source. The reason comes down to what you get baked in: the jemalloc memory allocator and TCP BBR congestion control, both of which matter when you're squeezing latency out of a busy box. The initial load testing for this setup ran over several months, which gave me enough data to trust the worker tuning.

Speaking of tuning, the PHP-FPM pool here sits at pm.max_children = 64 with pm.max_requests = 500. Those two values recycle workers before memory creep sets in, while keeping enough headroom for the dynamic requests that genuinely need PHP.

IDE or code editor with syntax highlighting, screen glare across a dark theme, fingerprints
Main Point: The caching strategy isn't about making PHP faster. It's about making sure most visitors never touch PHP at all.

Analyzing Nginx Directives for Expert Caching

If you're coming from Apache, the first instinct is to drop a few rewrite rules into an .htaccess file and walk away. In a LEMP environment that file is dead weight. Nginx never reads it. There is no per-directory configuration scan, which is part of why Nginx is fast, so all your routing logic has to live in the server block itself.

The heart of expert caching is the Nginx try_files directive documentation. The directive walks an ordered list of paths and serves the first one that exists. We want it checking for a pre-built static cache file before it even considers handing the request to the PHP-FPM socket.

Mapping the request lifecycle

I mapped out the request sequence carefully before writing a single line, because the order of arguments in try_files is the whole game. Here is the directive that ended up in production:

try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php?$args;

Read it left to right. Nginx first looks for the cached index.html that WP Super Cache wrote to disk. If that file exists, the visitor gets pure static HTML and the PHP layer stays asleep. Only when no cache file matches does the request fall through to /index.php.

One ordering mistake to avoid

There's a trap worth naming directly. If you forget to append a proper fallback and the final argument resolves to a directory that loops back on itself, you can end up with infinite redirect loops. The fix is making sure your terminal fallback hands off cleanly to the PHP handler rather than re-entering the location block. Test this with a cold cache, not a warm one.

For the static assets themselves, expiration headers are set to roughly a two to three week window. Images, CSS, and fonts rarely change between deploys, so letting browsers hold them that long trims repeat requests without risking stale layouts.

Implementing WP Super Cache via SSH

Now to the hands-on part. We'll do this over SSH rather than clicking through the dashboard, because the file permissions matter and I'd rather you see exactly what's happening on disk.

  1. Open a secure connection to the server. Use key-based authentication, and if you've moved SSH off port 22, point your client at the non-standard port you configured.
  2. Install the WP Super Cache plugin and activate it. You can do this through WP-CLI or the admin panel — whichever you trust more on this box.
  3. Open the plugin's caching settings and switch the mode to Expert. This is the critical toggle. Expert mode forces WP Super Cache to write fully static HTML files to disk, which is precisely what our try_files directive is hunting for.

Once Expert caching is active, WordPress begins generating the cache directory structure. In my run, plugin initialization and the first round of cache directory generation finished inside a few minutes. Don't panic if the folder looks empty for the first minute or two.

Locking down permissions

Permissions are where a lot of self-built stacks quietly break. Over SSH, set directories to chmod 755 and files to 644. That gives the web server read access to the cache files while keeping write access scoped to the WordPress process. Anything looser is a security liability; anything tighter and Nginx can't read what it needs to serve.

Expert Tip: After enabling Expert mode, load your homepage in a private browser window and then check the cache directory over SSH. Seeing the index.html appear confirms the plugin and the filesystem are cooperating before you touch Nginx at all.

Configuring the Nginx Server Block

With static files generating correctly, we connect Nginx to them. My preference is to keep the primary configuration file clean, so rather than dumping cache rules into the main server block, I isolated them into a dedicated file and pulled it in with an include statement.

  1. Navigate to the CentminMod configuration root. The custom rules live under /usr/local/nginx/conf/wpincludes/, a path I created specifically to separate WordPress logic from core Nginx settings.
  2. Create and edit wordpress-super-cache.conf. This holds the try_files directive from earlier along with any location-specific handling.
  3. Reference it from the main server block with an include statement so the rules load alongside the rest of your virtual host.

Validate before you reload

Never reload a live config blind. Run nginx -t first. It parses the entire configuration and reports the exact line of any syntax error before the running server is touched. Only once that returns a clean result do you issue systemctl reload nginx, which swaps the configuration without dropping active connections.

The trade-off you're accepting

Here's the honest catch, and it's a real one. Serving files directly off disk bypasses the application backend entirely. That means anything dynamic — a shopping cart, a logged-in user's personalized header, will break unless you explicitly exclude it. The mechanism for those exclusions is an Nginx map directive that watches for specific cookies.

In practice, cache bypass rules hinge on the presence of cookies like wordpress_logged_in_. When that cookie is set, the map flips a variable that tells Nginx to skip the cache and route the request to PHP. Get this right and editors see live content while anonymous visitors get the fast static path. Skip it, and your logged-in admins start seeing other people's cached pages.

Integrating Google Analytics Without Performance Loss

You've worked hard to keep PHP out of the request path. It would be a shame to undo that by dropping a render-blocking script into the page head. So analytics gets the same scrutiny as everything else.

Start by locating your unique Google Analytics Tracking ID inside your property's admin settings. While you're in there, review the Data Sharing configuration. Those toggles govern what Google may do with your collected data, and tightening them matters for both compliance and visitor privacy — decide deliberately rather than accepting defaults.

Placing the tag without blocking the render

Analysis of metrics against Core Web Vitals pushed me toward deferring the tracking payload rather than executing it inline. The goal is simple: don't let the analytics script stall the initial DOM render.

I implemented a script execution delay in the range of a few hundred milliseconds, which lets the visible page paint first and the measurement fire a beat later. The tracking payload itself stays under about 15 kilobytes, small enough that the deferred load is barely perceptible. Place the snippet so it loads after your primary content, and you keep the static-cache speed you built the whole stack to protect.

Pre-flight checklist

Before you call any of this done, run through the deployment checks. These are the items I verify on every build, and they've saved me from more than one late-night rollback. This guide reflects a CentminMod-based stack; on a stock distribution package, your paths and socket names will differ, so adapt rather than copy blindly.

  • Verify SSH access using key-based authentication on a non-standard port.
  • Confirm the PHP-FPM service is actively running and bound to the correct Unix socket.
  • Back up the existing nginx.conf and virtual host files before editing.
  • Run nginx -t and confirm a clean result before reloading.
  • Load a page anonymously, then as a logged-in user, to confirm the cache bypass behaves.
Rate this article
3

Your Thoughts

Nothing here yet. Add your opinion.

Leave a Comment

Rate this article
3

Stay Updated

No spam. Unsubscribe at any time.

Customise cookies