Configuring the Nginx Server Block with FastCGI Cache

The complete Nginx server block for WordPress with FastCGI page caching — every directive explained. Plus the difference between server-level cache and plugin cache, and a WP Rocket storage warning worth knowing.

Terminal showing nginx -t test is successful after adding the WordPress server block

This is the most important config file in the entire stack. Everything before this — Nginx installation, MariaDB, PHP-FPM — was preparation. This server block is what tells Nginx how to actually serve your WordPress site.

The config below is what I run on my own server. Every directive is explained so you know what you’re putting on your machine, not just that it works.


Server-Level Cache vs Plugin Cache — Before You Start

If you’re coming from shared hosting, you probably know WordPress caching through plugins: W3 Total Cache, WP Super Cache, WP Rocket. These work by generating static HTML files inside WordPress’s directory and serving them instead of re-running PHP each time.

FastCGI cache is fundamentally different — and understanding the difference matters before you configure it.

Without cache:
Browser → Nginx → PHP-FPM → WordPress → MariaDB → HTML → Browser
                  (PHP runs every time)

With plugin cache (WP Rocket etc.):
Browser → Nginx → PHP-FPM → WordPress checks cache → serves HTML file
                  (PHP still runs, but less work)

With FastCGI cache (HIT):
Browser → Nginx → serves cached HTML directly
                  (PHP never runs)

FastCGI cache operates at the Nginx layer — before PHP is even involved. A cache hit means zero PHP execution, zero database queries. The performance difference on real traffic is significant.

The WP Rocket disk space problem on VPS:

WP Rocket’s page cache creates a static HTML copy of every page on your site — stored in /wp-content/cache/. On a site with hundreds of posts, this can grow to several gigabytes. On shared hosting where disk space is managed for you, this goes unnoticed. On a VPS where you’re watching df -h yourself, it shows up fast.

If you’re running WP Rocket on a VPS and wondering why disk usage keeps climbing, check:

du -sh /var/www/yoursite/wp-content/cache/

With FastCGI cache enabled at the Nginx level, WP Rocket’s page cache is redundant. This config disables it. WP Rocket’s other features (minification, lazy loading, database cleanup) still run — just not page caching.


Step 1 — Create the Cache Directory

FastCGI cache needs a directory to store cached pages:

sudo mkdir -p /var/cache/nginx/fastcgi
sudo chown nginx:nginx /var/cache/nginx/fastcgi

Step 2 — Add FastCGI Cache Config to nginx.conf

Open the main Nginx config:

sudo nano /etc/nginx/nginx.conf

Inside the http {} block, add these lines before the include /etc/nginx/conf.d/*.conf; line:

# FastCGI cache zone definition
fastcgi_cache_path /var/cache/nginx/fastcgi
    levels=1:2
    keys_zone=WORDPRESS:100m
    inactive=60m
    max_size=1g;

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

What this does:

  • fastcgi_cache_path — where cached files are stored on disk
  • levels=1:2 — directory structure for cache files (prevents thousands of files in one folder)
  • keys_zone=WORDPRESS:100m — name the cache zone and allocate 100MB of RAM for the index
  • inactive=60m — remove cached items not accessed for 60 minutes
  • max_size=1g — maximum disk space for the cache
  • fastcgi_cache_key — how Nginx identifies unique pages (scheme + method + domain + URL)

Save and exit.


Step 3 — Create the WordPress Server Block

Create a new config file for your domain:

sudo nano /etc/nginx/conf.d/yourdomain.conf

Paste the complete config below, replacing yourdomain.com with your actual domain and /var/www/yourdomain.com with your actual web root path:

# Cache bypass conditions
# These variables determine when NOT to serve cached content
map $http_cookie $no_cache_cookies {
    default         0;
    ~SESS            1;  # PHP sessions
    ~wordpress_logged_in 1;  # Logged in users
    ~comment_author  1;  # Comment authors
    ~woocommerce_    1;  # WooCommerce (cart, checkout)
}

server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com;

    # Redirect HTTP to HTTPS (after SSL is installed in Part 5)
    # Uncomment after running Certbot:
    # return 301 https://$host$request_uri;

    root /var/www/yourdomain.com;
    index index.php index.html;

    # Access and error logs
    access_log /var/log/nginx/yourdomain.com.access.log;
    error_log  /var/log/nginx/yourdomain.com.error.log;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Hide Nginx version
    server_tokens off;

    # Gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 1000;

    # FastCGI cache settings for this server
    set $skip_cache 0;

    # Don't cache POST requests
    if ($request_method = POST) {
        set $skip_cache 1;
    }

    # Don't cache URLs with query strings
    if ($query_string != "") {
        set $skip_cache 1;
    }

    # Don't cache WordPress admin, login, or special pages
    if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-login.php|wp-.*.php|index.php|/feed/|sitemap(_index)?.xml)") {
        set $skip_cache 1;
    }

    # Don't cache if user has special cookies (logged in, WooCommerce active)
    if ($no_cache_cookies = 1) {
        set $skip_cache 1;
    }

    # Main location block — WordPress permalink handling
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # PHP-FPM processing
    location ~ \.php$ {
        # Security: block access to PHP files in uploads
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;

        # Connect to PHP-FPM via Unix socket
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;

        # Standard FastCGI params
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;

        # FastCGI cache directives
        fastcgi_cache WORDPRESS;
        fastcgi_cache_valid 200 301 302 1h;  # Cache successful responses for 1 hour
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;

        # Add cache status header (for debugging — shows HIT/MISS/BYPASS)
        add_header X-Cache-Status $upstream_cache_status always;

        # Timeouts
        fastcgi_read_timeout 300;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }

    # Static files — served directly, no PHP needed
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires max;
        log_not_found off;
        access_log off;
    }

    # Deny access to sensitive files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # Block access to WordPress config and sensitive files
    location ~* wp-config.php {
        deny all;
    }
}

Understanding the Key Directives

try_files $uri $uri/ /index.php?$args This handles WordPress pretty permalinks. When someone visits /about/, Nginx checks if a file called about exists, then if a directory called about/ exists, and if neither, passes the request to WordPress’s index.php. Without this, only the homepage works.

fastcgi_pass unix:/run/php-fpm/www.sock Sends PHP requests to the Unix socket we configured in the previous article. Must match the listen value in /etc/php-fpm.d/www.conf.

fastcgi_cache WORDPRESS Enables caching using the zone defined in nginx.conf. WORDPRESS is the zone name — it must match keys_zone=WORDPRESS:100m exactly.

fastcgi_cache_bypass $skip_cache and fastcgi_no_cache $skip_cache Two separate directives that both need to be set. bypass tells Nginx not to serve a cached response. no_cache tells Nginx not to store the response in cache. Both are needed to fully disable caching for the conditions we defined.

add_header X-Cache-Status $upstream_cache_status always Adds a header to responses showing whether the page was served from cache (HIT), generated fresh (MISS), or deliberately bypassed (BYPASS). Used for debugging — check it with curl -I https://yourdomain.com.

The $skip_cache bypass rules Four conditions that bypass the cache:

  1. POST requests — form submissions must reach PHP
  2. URLs with query strings — search results, paginated content
  3. WordPress admin paths — dynamic, user-specific
  4. Cookie-based bypass — logged-in users, WooCommerce sessions
Diagram showing cache HIT path (Browser to Nginx only) vs MISS path (Browser through Nginx, PHP-FPM, and MariaDB) vs BYPASS path for admin and logged-in users
Three paths through the cache: HIT serves from Nginx memory, MISS runs the full PHP stack, BYPASS always runs PHP regardless of cache state.

Step 4 — Test and Apply

# Test for syntax errors
sudo nginx -t

# If test passes, reload
sudo systemctl reload nginx

If nginx -t shows an error, it will point to the file and line number. Common issues:

  • Missing semicolon at end of a directive
  • Typo in the domain name
  • Wrong PHP-FPM socket path

Basic Server Block Variants

Redirect all HTTP to HTTPS (use after SSL setup in Part 5):

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$host$request_uri;
}

Redirect www to non-www:

server {
    listen 443 ssl;
    server_name www.yourdomain.com;
    return 301 https://yourdomain.com$request_uri;
}

Block direct IP access (returns 444 — drops connection):

server {
    listen 80 default_server;
    server_name _;
    return 444;
}

Save these in your personal config notes — they come up repeatedly.

Frequently Asked Questions

What is the difference between FastCGI cache and a WordPress cache plugin?
A WordPress plugin cache (like WP Rocket) runs inside PHP — WordPress loads, the plugin checks for a cached version, and serves it. FastCGI cache operates at the Nginx level, before PHP is even involved. A cached page hit never reaches PHP at all. FastCGI cache is faster, but plugin cache is easier to configure and works on shared hosting.
Can I use WP Rocket and FastCGI cache at the same time?
Yes, but with specific configuration. Disable WP Rocket's page caching feature (since Nginx handles this), but keep WP Rocket's other features: CSS/JS minification, lazy loading, database optimization, and browser caching. This gives you the best of both layers without conflicts or disk waste.
Why does WP Rocket use so much disk space on a VPS?
WP Rocket's page cache creates static HTML copies of every page and post. On a large site, this can accumulate into gigabytes of cached files. On a VPS where you're managing disk space yourself, monitor /var/www/yoursite/wp-content/cache/ regularly. With FastCGI cache enabled, WP Rocket's page cache is redundant and can be disabled.
Why does the cache need to bypass WordPress admin?
Admin pages are dynamic and personalized — they show notifications, pending updates, user-specific data. Serving a cached admin page means an admin action might use stale data. The bypass rules ensure anyone logged in, submitting forms, or accessing wp-admin always gets a fresh PHP-generated response.

Disclosure: Some links on this page are affiliate links. If you make a purchase through them, I may earn a small commission at no extra cost to you. I only recommend products I've genuinely evaluated. Full disclosure →