Server: - Add a --cache-path allowlist so a server can be limited to the caches it is meant to purge, defaulting to any path as before. - Set socket permissions explicitly (--socket-mode, default 0660) instead of inheriting the service manager's umask, which left the socket unreachable. - Refuse to remove a socket another instance is still serving. - Read keys both raw and decoded, so keys nginx stored with escapes and keys a caller escaped by hand both purge. - Add an exact= parameter for literal keys containing glob punctuation. - Report purge failures as 500 rather than 502, and send error bodies through http.Error so a failure is not reported as a successful purge. - Graceful shutdown with systemd readiness notification. Purge: - Group purge arguments into PurgeRequest and report the number of entries removed. - Compile exclude globs once, and fail the purge when one is invalid rather than purging the keys it was meant to keep. - Cap header scanning and tolerate entries nginx evicts mid-walk. - Switch to filepath.WalkDir to avoid an Lstat per cache file. New: - service command to install, start, stop, and remove the system service. - Makefile, VERSION, and build identifiers stamped via ldflags. - Tests for the server handler. Build: - Update to Go 1.25, kong v1, GoReleaser v2, and current GitHub Actions. - Add vet and test steps to CI. - Rename purgeCmd.go/serverCmd.go to Go's file naming convention. Bump version to 0.2.0.
11 KiB
nginx-cache-purge
A tool to help purge Nginx cache. It can either run locally with the purge command, or run as a local unix service to allow for purging by Nginx http requests. The tool supports using wildcard/glob syntax in the purge key to match multiple keys from the cache.
Install
You can install either by downloading the latest binary release, or by building.
Building
Building should be as simple as running:
make
The build stamps the binary with the contents of the VERSION file plus the
git commit and build date, shown by nginx-cache-purge --version. A plain
go build also works but reports the version as dev.
Usage
The following are some examples of ways to purge cache
Purge a specific key
$ nginx-cache-purge purge /var/nginx/proxy_temp/cache example.com/index.html
Purge all keys for a domain
$ nginx-cache-purge purge /var/nginx/proxy_temp/cache 'example.com/*'
Purge all keys for jpeg and png files
$ nginx-cache-purge purge /var/nginx/proxy_temp/cache 'example.com/*.{jpg,jpeg,png}'
Purge all keys
$ nginx-cache-purge purge /var/nginx/proxy_temp/cache '*'
Purge a key that contains wildcard characters
Whether a key is a wildcard is otherwise guessed from the punctuation in it, and
real cache keys carry that punctuation: a request URI with a query string puts
? in the key, and PHP-style array parameters put [ and ]. --exact says
the key is a literal, for both the key and any excludes.
$ nginx-cache-purge purge --exact /var/nginx/proxy_temp/cache 'example.com/list.php?f[]=x'
Running as a service
If you want to run as a service to allow purge requests via http requests, the service can install itself:
nginx-cache-purge service install
nginx-cache-purge service start
service also accepts stop, restart, status, and uninstall. The
installed unit runs nginx-cache-purge server as a notify service, creates the
runtime directory the socket lives in, and restarts on failure.
Connecting to a UNIX socket needs write permission on it, so the socket is
created mode 0660: the purge server and Nginx have to run as the same user, or
share a group. The installed unit runs as root, so either add a
User=/Group= drop-in for it, or widen the socket with
--socket-mode.
Restricting which caches may be purged
The directory to purge comes from the request, and the purge deletes what it
finds under it, so a server left unrestricted will purge any path its user can
reach. Pass --cache-path to name the caches it may serve, repeating it for
more than one:
ExecStart=/usr/local/bin/nginx-cache-purge server --cache-path /var/nginx/proxy_temp/cache
A request naming any other directory is refused with 403. Directories inside a
named cache are allowed, and symlinks are resolved, so a link to a named cache is
recognised as that cache. Naming no path leaves every path purgeable, which is
what a server without the flag has always done.
The installed unit runs nginx-cache-purge server with no arguments, so add the
flag with a drop-in or write the unit yourself.
If you'd rather write the unit yourself, place the following in
/etc/systemd/system/nginx-cache-purge.service.
[Unit]
Description=Nginx Cache Purge
After=network.target
[Service]
User=nginx
Group=nginx
RuntimeDirectory=nginx-cache-purge
ExecStart=/usr/local/bin/nginx-cache-purge server --cache-path /var/nginx/proxy_temp/cache
Restart=always
RestartSec=3s
[Install]
WantedBy=multi-user.target
You can then run the following to start the service:
systemctl daemon-reload
systemctl start nginx-cache-purge.service
Nginx config
If you want to purge via Nginx http requests, you'll need to add configuration to your Nginx config file.
The server reads four query parameters:
| Parameter | Description |
|---|---|
path |
Path to the cache directory, the same one given to proxy_cache_path. |
key |
Cache key or wildcard match, the same one built by proxy_cache_key. |
exclude |
Key to keep, can be a wildcard. Repeat it to exclude more than one. |
exact |
Read the key and excludes as literals rather than wildcards. |
Nginx substitutes $request_uri into the rewrite without escaping it, so a key
containing % or + arrives as the literal bytes Nginx stored it under, and
that is what is purged. A key escaped by a caller writing the request itself is
purged too, so either convention works.
Literal keys and wildcards
A key built from $request_uri is a literal, and a request URI routinely holds
the punctuation that would otherwise mark the key as a wildcard: ? from a query
string, [ and ] from PHP-style array parameters. Read as wildcards those keys
purge the wrong entries, fail outright, or match nothing while still answering
PURGED and leaving the stale entry served. The examples below therefore pass
exact=1, which is what you want when a PURGE request names one URL.
Leave exact off where the purge is meant to take a wildcard, such as the
/purge(/.*) location further down, where a request for /purge/images/*
clears everything under /images/.
Because $request_uri carries the client's own query string into the purge
parameters, put exact=1 before key= in the rewrite. Parameters are taken
first-wins, so an exact=0 a client appends to its request URI arrives second
and is ignored. The same ordering already protects path. Note that exclude is
collected rather than taken first-wins, so a client can append an exclude that
holds back its own purge.
Map PURGE requests
http {
map $request_method $is_purge {
default 0;
PURGE 1;
}
proxy_cache_path /var/nginx/proxy_temp/cache levels=1:2 keys_zone=my_cache:10m;
proxy_cache_key $server_name$request_uri;
server {
location / {
proxy_cache_bypass $is_purge;
if ($is_purge) {
proxy_pass http://unix:/run/nginx-cache-purge/http.sock;
rewrite ^ /?path=/var/nginx/proxy_temp/cache&exact=1&key=$server_name$request_uri break;
}
proxy_cache my_cache;
proxy_pass http://upstream;
}
}
}
Auth via cookie
http {
map $cookie_purge_token $is_purge {
default 0;
nnCgKUx1p2bIABXR 1;
}
proxy_cache_path /var/nginx/proxy_temp/cache levels=1:2 keys_zone=my_cache:10m;
proxy_cache_key $server_name$request_uri;
server {
location / {
proxy_cache_bypass $is_purge;
if ($is_purge) {
proxy_pass http://unix:/run/nginx-cache-purge/http.sock;
rewrite ^ /?path=/var/nginx/proxy_temp/cache&exact=1&key=$server_name$request_uri break;
}
proxy_cache my_cache;
proxy_pass http://upstream;
}
}
}
Auth via header
http {
map $http_purge_token $is_purge {
default 0;
nnCgKUx1p2bIABXR 1;
}
proxy_cache_path /var/nginx/proxy_temp/cache levels=1:2 keys_zone=my_cache:10m;
proxy_cache_key $server_name$request_uri;
server {
location / {
proxy_cache_bypass $is_purge;
if ($is_purge) {
proxy_pass http://unix:/run/nginx-cache-purge/http.sock;
rewrite ^ /?path=/var/nginx/proxy_temp/cache&exact=1&key=$server_name$request_uri break;
}
proxy_cache my_cache;
proxy_pass http://upstream;
}
}
}
Auth via header and IP white list.
http {
map $http_purge_token $is_purge {
default 0;
nnCgKUx1p2bIABXR 1;
}
geo $purge_allowed {
default 0;
127.0.0.1 1;
192.168.0.0/24 1;
}
proxy_cache_path /var/nginx/proxy_temp/cache levels=1:2 keys_zone=my_cache:10m;
proxy_cache_key $server_name$request_uri;
server {
location / {
set $should_purge $purge_allowed;
if ($is_purge != 1) {
set $should_purge 0;
}
proxy_cache_bypass $should_purge;
if ($should_purge) {
proxy_pass http://unix:/run/nginx-cache-purge/http.sock;
rewrite ^ /?path=/var/nginx/proxy_temp/cache&exact=1&key=$server_name$request_uri break;
}
proxy_cache my_cache;
proxy_pass http://upstream;
}
}
}
Using IP whitelists
This location takes a wildcard, so it leaves exact off: a request for
/purge/images/* clears everything under /images/.
http {
proxy_cache_path /var/nginx/proxy_temp/cache levels=1:2 keys_zone=my_cache:10m;
proxy_cache_key $server_name$request_uri;
server {
location / {
proxy_cache my_cache;
proxy_pass http://upstream;
}
location ~ /purge(/.*) {
allow 127.0.0.1;
deny all;
proxy_pass http://unix:/run/nginx-cache-purge/http.sock;
rewrite ^ /?path=/var/nginx/proxy_temp/cache&key=$server_name$1 break;
}
}
}
Help
$ nginx-cache-purge --help
Usage: nginx-cache-purge <command> [flags]
Tool to help purge Nginx cache
Flags:
-h, --help Show context-sensitive help.
--version Print version information and quit
Commands:
server (s) Run the server
purge (p) Purge cache now
service Manage the purge server system service.
Run "nginx-cache-purge <command> --help" for more information on a command.
$ nginx-cache-purge p --help
Usage: nginx-cache-purge purge (p) <cache-path> <key> [flags]
Purge cache now
Arguments:
<cache-path> Path to cache directory.
<key> Cache key or wildcard match.
Flags:
-h, --help Show context-sensitive help.
--version Print version information and quit
--exclude-key=EXCLUDE-KEY,...
Key to exclude, can be wild card and can add multiple
excludes.
--exact Treat the key and excludes as literal keys rather than
wildcard matches.
$ nginx-cache-purge s --help
Usage: nginx-cache-purge server (s) [flags]
Run the server
Flags:
-h, --help Show context-sensitive help.
--version Print version information and quit
--socket=STRING Socket path for HTTP communication (default
/run/nginx-cache-purge/http.sock).
--socket-mode="0660" Octal permissions to give the socket.
--cache-path=CACHE-PATH,...
Cache directory that may be purged, can be
repeated. Any path is purgeable when none is
given.
$ nginx-cache-purge service --help
Usage: nginx-cache-purge service <action> [flags]
Manage the purge server system service.
Arguments:
<action> start,stop,status,restart,install,uninstall
Flags:
-h, --help Show context-sensitive help.
--version Print version information and quit