Fixed: NGINX Cache Purge Fails Running PHP-FPM

So this is another of those posts where I’ve had to pick all the information from around the internet because it’s not in one place. Or at least I couldn’t find it – and the chances of me needing this again in the future are pretty high so if nothing else it’s here for my […]

nginx purge cache module featured image
Reading Time: 6 minutes

So this is another of those posts where I’ve had to pick all the information from around the internet because it’s not in one place. Or at least I couldn’t find it – and the chances of me needing this again in the future are pretty high so if nothing else it’s here for my own future reference!

The situation is this :- You use NGINX and php-fpm and you’re running (probably WordPress) something that you want to utilise NGINX’s excellent FASTCGI_CACHE routines. You also want to maintain user separation, so each php-fpm runs as a different user (obviously using fastcgi). Finally, when something changes on your WordPress site you want to be able to invalidate the NGINX cache for all those pages that are relevant.

If you’re like me, you’ll have installed NGX-MOD-HTTP-CACHE-PURGE from your repository (in my case, Debian 12). And you’ll have set everything up correctly following all the instructions. You’ll have even setup the Nginx Helper Plugin from WordPress. And your cache stubbornly will not flush.

Checking the logs you get a 404 error any time the PURGE is attempted. That means that the module couldn’t find (or as it turns out didn’t have permission to remove) the cache file. So it returns 404 saying there was no file to remove.

The problem, it turns out, is that default module that comes with Debian doesn’t support running in an environment with multiple users operating PHP-FPM. Which will be anyone that’s using Virtualmin for their web control panel and probably others too.

So, here’s how to set this up, from scratch, so that you can cache your WordPress pages using NGINX’s fastcgi-cache with php-fpm with user separation. This is not my work, except for the detective part. I’ll create the people who helped below.

Install NGINX (If You Haven’t Already)

To be fair, I’m not going to tell you how to do that. Though if you’re on Debian 12 it’s straight forward enough.

apt install nginx

or

apt install nginx-full

That’ll get you set up with Nginx.

Check Which Nginx Version You Have

nginx -V
nginx version: nginx/1.22.1
built with OpenSSL 3.0.8 7 Feb 2023 (running with OpenSSL 3.0.11 19 Sep 2023)
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-AoTv4W/nginx-1.22.1=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=stderr --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_secure_link_module --with-http_sub_module --with-mail_ssl_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-http_geoip_module=dynamic --with-http_image_filter_module=dynamic --with-http_perl_module=dynamic --with-http_xslt_module=dynamic --with-mail=dynamic --with-stream=dynamic --with-stream_geoip_module=dynamic

From the output there we can see this is Nginx 1.22.1 – and it’s showing us how it was compiled. Copy everything after the ‘configure arguments:’ for later.

Download The VERY SAME Nginx Source Version

I highly recommend you do these next steps on a development server of the same architecture as your live server. I do NOT recommend doing this on the live platform – things can go wrong. I use Proxmox on my home server for this. You could just spin up a Hetzner VPS – if you haven’t got a Hetzner VPS my link will get you 20 Euro to spin up a VPS to do this build. It’s an affiliate link but the 20Eur is free for you and I won’t get paid unless you actually decide to keep using their service and become a paying user.

You can get every version of Nginx ever released at https://nginx.org/download – so in my case I ran the following…

wget https://nginx.org/download/nginx-1.22.1.tar.gz

Download the NGX Cache Purge Module That Supports Multi-User

You cannot use the normal ngx-cache-purge module that comes with Debian for this. And you can’t (apparently) use the source code for that module either, it’s just not set up for it for some reason. However, you can use a patched version, available on Github at https://github.com/torden/ngx_cache_purge

Copy that link, then issue the command

wget https://github.com/torden/ngx_cache_purge/archive/refs/tags/v2.3.1.tar.gz

Or check if there’s a later version. It hasn’t been updated in a while so it may well not be.

Extract the files, (use tar zxvf *.tar.gz) and you’ll end up with 2 source code directories.

Install Debian Dependencies

You’ll unfortunately have to figure this bit out on your own if you’re using a RedHat based system. But for Debian you’ll need;

apt install build-essential libpcre3-dev libssl-dev zlib1g-dev libxml2-dev libxslt1-dev libgd-dev libgeoip-dev

Configure The Module

Woohoo, we’re finally there. We’re gonna build the module. Now, fortunately, so long as this bit goes right, you do not have to fully rebuild Nginx on your live server – only the module. It will rebuild Nginx on your development system, but it doesn’t take long. We’re only interested in the module though – hence why I recommend you just do this on a local development environment.

Your development environment will need to be the same architecture as your live server though, so you can’t compile it for amd64 and then expect it to run on an arm based server.

Anyhoo – cd into the nginx source directory from earlier. In my case that would look like this;

cd nginx-1.22.1/

Read this all before you proceed cos it’s confusing and it caught me out. You will need to run ‘configure’ in this directory and tell it that you’re compiling the ngx_cache_purge-2.3 module, as a module. But you also need to include all the configuration parameters from the original Nginx which you hopefully copied above.

So, the first part you’ll need to type – but do not press enter yet.

./configure --add-dynamic-module=../ngx_cache_purge-2.3

Now paste the rest of the configuration information you copied from the original ‘nginx -V’ command mentioned earlier. The final command will look something like this;

./configure --add-dynamic-module=../ngx_cache_purge-2.3.1 --with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-AoTv4W/nginx-1.22.1=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=stderr --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_secure_link_module --with-http_sub_module --with-mail_ssl_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-http_geoip_module=dynamic --with-http_image_filter_module=dynamic --with-http_perl_module=dynamic --with-http_xslt_module=dynamic --with-mail=dynamic --with-stream=dynamic --with-stream_geoip_module=dynamic

If you mess that bit up, the module won’t be ‘binary compatible’ with your server and you’ll get an error when you try to restart nginx on the live machine. I know, cos it happened to me.

You may find you get errors during the configuration process, due to missing libraries or other things. You may have to install them yourself. For me I had to

apt install libperl-dev

Make the Module

Now you just need to run the command

make modules

By the way, if at any point you decide to ‘make clean’ (which I did because I messed the original configuration up) then it’ll delete the Makefile – which makes sense because the Makefile is created from the ./configure command you gave it. Just re-run the ./configure to re-create the make file.

Copy the Module To The Live Server and Enable It

I’ll leave it as an exercise to you how you get the module uploaded to your live server. It’s in the ‘objs’ directory so you’ll need to “cd objs” first. I just used scp to get it to my server.

Your server may have a different location for where the modules live too, mine were in

/usr/share/nginx/modules so that’s where I copied this one to.

If you’re running Debian you can enable the module by creating a file under /etc/nginx/modules-enabled called 50-ngx-cache-purge (you can call it what you like to be honest, Nginx won’t care). In that file you need the following command;

load_module modules/ngx_http_cache_purge_module.so;

Different systems do that differently and you may need to just simply load it from the nginx.conf in /etc/nginx instead. Adjust to match how your system expects to find it.

Test The Nginx.conf Syntax Before Reloading

nginx -t

If there’s something wrong with what you’ve done, you’ll get an error here but your web server will continue running, so long as you don’t restart it. Correct the mistake and try again. If it says everything is OK then you can issue the big command;

systemctl restart nginx

Bear in mind though the nginx -t doesn’t check whether you got the configuration right when you were building the module. So be prepared that it might all fall apart at this point. If it does, put a # in front of the load_module that you created earlier and then restart again. You’ll need to debug why it failed. The only reason I can foresee is a binary incompatibility which means you either compiled on a different architecture or you didn’t copy the original configuration correctly when you initially started the build process.

Adding Caching to your Nginx

If you’re reading this you’ve probably already set up the caching side of things. If you haven’t I recommend reading the original Github issue that sent me down this rabbit-hole, or look at an article such as this – https://www.digitalocean.com/community/tutorials/how-to-setup-fastcgi-caching-with-nginx-on-your-vps but ignore the bit about purging because that won’t work on a multi-user php-fpm system either. https://www.linuxbabe.com/nginx/setup-nginx-fastcgi-cache provides a really comprehensive tutorial for setting it up to run with WordPress too.

If you go to linuxbabe’s tutorial, follow it all the way through but don’t install the module – you’ve already done that, and installing the one from your distribution will likely stop it working again. The Nginx Cache Sniper plugin mentioned in that post won’t work if you have user separation setup on your php-fpm. But the Nginx Helper plugin should work, so long as you use the nginx Fastcgi cache option.

Conclusion

It’s WELL worth having Nginx cache your WordPress pages for you – but it’s a pain in the bum if they don’t get cleared automatically when you update. If you’re using php-fpm with multiple users and user separation (i.e. each php-fpm process runs as the user who owns the site) – and if you’re running hosting for multiple people you really should be using separation – then the standard tutorials for purging the cache won’t work.

The worst is, you won’t find any information about this unless you’re pulling your hair out and wondering why you’re bothering.

If you’ve found this post helpful please feel free to share it using the buttons below. If you have any questions, comments or feedback we’d love to hear from you by leaving a comment using the form below.

Thanks for reading!

Credits

Featured Image by Settergren from Pixabay
Znuff on this Github Issues Thread for showing me I wasn’t going mad.
JeffCleverly on the same thread for showing it can be fixed, using Torden’s module (below).
RomanAcademy for creating this medium post about recompiling FastCGI Cache Purge Module
Torden for forking the original module and making it work with multi-user php-fpm.
LinuxBabe for providing a comprehensive way to test purging with curl.

Hosted on Hetzner, using the technology described in this post! (Use my link and get 20 Euro to play with a Hetzner server – you could even use it to build the new module). (Affiliate link).