PHP Snuffleupagus Tutorial — Harden PHP-FPM on Debian and Ubuntu (2026)

Right. First day. Someone gave you the laptop, someone showed you the coffee machine, and now they’ve handed you “the website” and walked away. Congratulations — you are now responsible for keeping a PHP application alive on the open internet. No pressure.

Here’s the secret nobody tells you on day one: your WAF is not enough. Your fancy NGINX rules are not enough. Keeping WordPress updated is not enough. Bots are scanning your IP right now, looking for the one outdated plugin you forgot. And when they find it, they upload a tiny PHP file to wp-content/uploads/ and your evening goes sideways.

This is the post I wish someone had handed me when I was twenty and starting out. We’re going to install a thing called PHP-Snuffleupagus — yes, named after Big Bird’s furry friend — and turn your PHP runtime into a place where attackers’ tools simply don’t work, even when they get in.

I’ll explain everything. No assumed knowledge. If you’ve never touched /etc/php/ before, you’re in the right place.

PHP Snuffleupagus tutorial cover — hardening PHP-FPM on Debian and Ubuntu
A friendly bouncer for your PHP interpreter. That is genuinely what this is.

The story of every PHP compromise, ever

Picture this. It’s 2 a.m. Your phone buzzes. The client emails: “Google is showing a warning that my site contains malware.” You log in. The site is serving fake pharmaceuticals in five languages. You start digging.

What happened? Almost always the same thing:

  1. A plugin has a bug. The bug lets an attacker upload a file.
  2. The attacker uploads a tiny PHP file pretending to be an image. Maybe shell.php.jpg. Maybe just image.php with a misconfigured upload check.
  3. The attacker visits that file in a browser. PHP runs it. The file calls eval($_POST['x']) and now the attacker can run any PHP code, as your web server user, on your server.
  4. They drop more files. They edit your WordPress database. They install backdoors in your themes. You spend the weekend nuking and restoring from backup.

Your WAF saw the upload and thought “that’s a JPEG, fine.” Your NGINX config doesn’t care about eval(). WordPress itself has no idea any of this happened.

Here’s the thing: nothing along the way actually checked what the PHP code was doing. The HTTP request was fine. The file extension was fine. PHP just… ran whatever was in the file. That’s the gap. That’s where Snuffleupagus lives.

So what is Snuffleupagus?

Snuffleupagus is a small piece of code that loads inside the PHP interpreter every time PHP starts. It’s not a separate server. It’s not a firewall. It’s a Zend extension — in plain English, a plugin for PHP itself.

Once it’s loaded, every function call your PHP code makes goes through Snuffleupagus first. Want to call eval()? Snuffleupagus checks its rulebook: “is eval allowed in this script?” If not, eval returns false and your PHP code can’t do the bad thing. The attacker’s exploit chain breaks at the interpreter level — before the dangerous function actually executes.

An analogy I always come back to:

  • NGINX / your WAF — the bouncer at the front door of the restaurant. Checks IDs, kicks out the obviously drunk.
  • Snuffleupagus — the bouncer who lives inside the kitchen. Even if someone sneaks in through a window, they still can’t touch the knives.

You want both. They watch different doors.

Real WordPress RCEs Snuffleupagus would have stopped dead

“Theoretical” rarely sells anything. Here are real, in-the-wild WordPress plugin vulnerabilities from the last few years, and exactly which Snuffleupagus rule kills the exploit chain. Every one of these was a “tens of thousands of sites compromised in a weekend” story.

File Manager 6.9 — CVE-2020-25213

An unauthenticated attacker could hit a vulnerable elFinderConnector.php endpoint and upload anything they wanted, including a one-line PHP webshell. The shell then called system($_GET['c']) to give the attacker arbitrary command execution as the web server user. Snuffleupagus’s wordpress-strict.rules blocks system() across every script in wp-content/ — the upload still happens, but the shell can’t run a single command. The attacker uploads a paperweight.

Backup Migration — CVE-2023-6553

A require call inside the plugin trusted a $_GET parameter without sanitisation, letting an unauthenticated attacker include any file path — including remote URLs when allow_url_include was on. Snuffleupagus’s INI lock (sp.ini.key("allow_url_include").set("0").ro();) keeps the dangerous PHP setting off no matter what plugin code tries to override it. Even if the attacker controls the parameter, the include can’t reach a remote payload.

The eternal “eval-of-base64” backdoor

Open any compromised WordPress install at 2 a.m. and you’ll find files like wp-content/uploads/2023/04/.htaccess.php containing <?php @eval(base64_decode($_POST['x']));. It’s the same backdoor people were dropping in 2009. Every Snuffleupagus rulebook in the myguard pack drops eval() outright in any file under wp-content/uploads/ — the file sits there, perfectly harmless, until you find it and delete it.

The pattern is always the same: a single dangerous function call is the difference between “the attacker poked at your site” and “the attacker owns your site.” Snuffleupagus removes that function call from the attacker’s toolbox at the interpreter level. No rule update, no signature, no vendor — just a flat “no” from PHP itself.

A picture of where Snuffleupagus sits

Here’s the path a single request takes through your server. Snuffleupagus is the last line of defence, the one closest to the actual damage:


  Browser ─────► NGINX / Angie ─────► PHP-FPM pool ─────► PHP interpreter
                  │                    │                    │
                  │                    │                    └─► Snuffleupagus ──► your code
                  │                    │                                              │
                  ↓                    ↓                                              ↓
              checks URL,         picks worker,                                  every function
              method, headers     sets ini values                                call is filtered
              (ModSecurity)       (memory_limit etc.)                            against the rulebook

The interesting bit: by the time a request reaches the PHP interpreter, the WAF has already approved it and the FPM pool has already spawned a worker. If anything malicious got through, Snuffleupagus is your last chance to stop it before system("rm -rf /") actually runs.

Step one: install the thing (one minute, promise)

This is the easy part. The myguard APT repository ships php-snuffleupagus as a regular Debian/Ubuntu package, pre-compiled for PHP 7.0 through 8.5. No gcc, no phpize, no submodules. Just apt.

If you’ve never added the myguard repository before, do this once:

wget https://raw.githubusercontent.com/eilandert/deb.myguard.nl/main/myguard.deb
sudo dpkg -i myguard.deb
sudo apt update

Now install the extension for whichever PHP version you use. Don’t know which one? Run php -v and read the first line. Most modern servers are on 8.3 or 8.4:

sudo apt install php8.4-snuffleupagus

That’s it. The package drops the .so file in the right place, drops an extension=snuffleupagus line into mods-available, symlinks it into both fpm/conf.d/ and cli/conf.d/, and installs five ready-to-use rulebooks under /etc/php/8.4/php-snuffleupagus/.

Reload PHP-FPM so the extension actually loads:

sudo systemctl reload php8.4-fpm

Verify it’s there:

php -m | grep -i snuf

You should see snuffleupagus in the output. If you don’t, check journalctl -u php8.4-fpm for the reason. Usually it’s a typo in a rule file, and the error message tells you which line.

Step two: pick your rulebook

This is where most tutorials go off the rails. They hand you 400 lines of rules copied from upstream, half of which break WordPress and the other half of which lock down things you don’t actually have. Skip that.

The myguard package ships five small, focused rulebooks. Pick one. You can change later. Here’s the decision tree:


Is this server running WordPress?
│
├── No: is it Roundcube webmail?
│     │
│     ├── Yes ──────► roundcube.rules
│     │
│     └── No: is it your generic PHP app / Drupal / a Symfony thing?
│           │
│           └─────► php-relax.rules
│
└── Yes: does it have plugins that shell out?
        (backup plugins, image converters, anything that
         calls mysqldump or cwebp under the hood)
        │
        ├── Yes ────► wordpress-lax.rules
        │
        └── No ─────► wordpress-strict.rules

Plus: if this server also runs wp-cli or an MCP agent on a
separate FPM pool, that pool needs mcp-agent.rules (no
function blocking, just RCE guard).

Each rulebook does one job:

  • php-relax.rules — the bare minimum that catches the worst stuff (remote includes, eval-of-base64, mail header injection, environment hijacking) without blocking anything a normal app does. Safe for any PHP application.
  • wordpress-strict.rules — everything in relax, plus an outright ban on exec, system, shell_exec, proc_open, phpinfo, and friends. Use this if your WordPress doesn’t have plugins that need subprocesses.
  • wordpress-lax.rules — strict’s friendlier cousin. Lets subprocess calls through if they don’t contain shell metacharacters. Use this if you have UpdraftPlus, BackWPup, ShortPixel local mode, or other plugins that call out to mysqldump / cwebp.
  • mcp-agent.rules — for a privileged internal pool. No call-blocking, no ini_protection — just the RCE primitives (remote include, eval+decode chains, environment hijacking). Perfect for wp-cli or an automation agent.
  • roundcube.rules — Roundcube has its own session handler, never shells out, and lives behind authentication. This rulebook reflects that: strict on subprocess and recon, lenient where Roundcube needs flexibility.

Step three: wire the rulebook into your FPM pool

This is the part that trips everyone up the first time, so we’ll go slow.

PHP-FPM is the bit that actually runs your PHP. It’s organised into pools: each pool is a group of worker processes with the same configuration. Most servers have just one pool, called www, in /etc/php/8.4/fpm/pool.d/www.conf. Bigger servers split into multiple pools so different sites or different parts of the same site can have different settings.

Open your pool file:

sudo nano /etc/php/8.4/fpm/pool.d/www.conf

Scroll to the bottom and add this one line:

php_admin_value[sp.configuration_file] = /etc/php/8.4/php-snuffleupagus/wordpress-strict.rules

That’s it. That one line tells the FPM pool which rulebook to load. Save (Ctrl-O, Enter, Ctrl-X in nano) and reload:

sudo systemctl reload php8.4-fpm

If PHP-FPM refuses to start, run:

sudo journalctl -u php8.4-fpm -n 50

and read the last few lines. Snuffleupagus error messages are blunt but accurate — usually a missing file path or a typo in a rule.

Important thing nobody warns you about: do not put sp.configuration_file in /etc/php/8.4/mods-available/snuffleupagus84.ini. The package deliberately doesn’t set it there. If you set it globally, it merges with per-pool overrides in weird ways and you get errors that point at the wrong line in the wrong file. Always set the rulebook at the pool level.

Two pools, two rulebooks: a worked example

Snuffleupagus PHP-FPM pool architecture — public www pool with strict rules, internal agent pool with relaxed rules
Different doors get different bouncers. The internet gets the strict one.

Here’s a real-world setup. You’re running a WordPress site, and you also run wp-cli commands via cron and a small management agent. The wp-cli stuff needs to call exec() and proc_open() all day long. The public site never should.

Make two pools. Call them www (public traffic) and agent (the trusted internal one). The public pool gets strict, the internal pool gets the relaxed agent ruleset:

# /etc/php/8.4/fpm/pool.d/www.conf
[www]
listen = /run/php/php8.4-fpm.sock
user   = www-data
group  = www-data
pm     = dynamic
pm.max_children = 25

php_admin_value[memory_limit]           = 512M
php_admin_value[sp.configuration_file]  = /etc/php/8.4/php-snuffleupagus/wordpress-strict.rules
# Leave disable_functions EMPTY when using Snuffleupagus — the rulebook
# already does that job, and mixing both produces weird boot errors.
php_admin_value[disable_functions]      =
# /etc/php/8.4/fpm/pool.d/agent.conf
[agent]
listen = /run/php/php8.4-agent.sock
user   = agent
group  = www-data
pm     = dynamic
pm.max_children = 4

php_admin_value[memory_limit]           = 1024M
php_admin_value[sp.configuration_file]  = /etc/php/8.4/php-snuffleupagus/mcp-agent.rules
php_admin_value[disable_functions]      =

In your NGINX or Angie config, route the public site at fpm.sock and your internal endpoint (say, /wp-json/agent/) at agent.sock. The same Snuffleupagus extension is loaded in both pools, but each enforces a completely different rulebook. The attacker hits the public pool and runs into a wall. Your cron hits the agent pool and gets the work done.

Things that will trip you up (so you can skip the pain)

I’ve made every one of these mistakes. Pay attention, save yourself an evening.

1. The misleading error trace

Snuffleupagus sometimes reports a violation with a stack trace that points at code that literally doesn’t contain the function it claims. You’ll see things like strcoll() expects 2 arguments, 1 given on a line that obviously calls strtolower(). Or password_hash() expects at least 2 arguments on a line that just calls define().

This is not a PHP bug. This is Snuffleupagus telling you a rule fired, but the location and function name in the trace are misleading. When you see a fatal that “can’t possibly be true” given the source, suspect Snuffleupagus first.

To confirm: temporarily comment out the sp.configuration_file line in your pool, reload FPM, and try again. If the fatal disappears, it’s a rule firing.

2. The memory_limit trap

You can write sp.ini.key("memory_limit").max("2G").rw(); in your rulebook. It will silently fail to parse the G suffix and the cap falls back to a tiny default. The whole site 500s. Use max("1024M") instead. Always megabytes.

3. Don’t try to chain rulebooks

Upstream’s strict.rules uses .include "other-file.rules" to pull in default.rules, suhosin.rules, and friends. On recent Snuffleupagus, this silently breaks: each included file re-sets the secret_key, calls sp.ini_protection.enable() a second time, and defines conflicting memory_limit ranges. Symptoms: random 500s with the body memory_limit.

The myguard rulebooks are each self-contained on purpose. Pick one. Don’t .include anything.

4. Old syntax for cookie encryption

If a tutorial tells you to write:

sp.cookie_encryption.name("PHPSESSID");
sp.cookie_encryption.encrypt();

that’s the old, two-statement form. It’s rejected at parse time now. The current syntax is one chained statement:

sp.cookie_encryption.name("PHPSESSID").encrypt();

The myguard rulebooks already use the new form.

5. The disable_functions double-up

Old habit: put a list of dangerous functions in php_admin_value[disable_functions] in your pool file. With Snuffleupagus, don’t. The pool-level disable_functions directive plus a Snuffleupagus rulebook that locks disable_functions read-only causes a fatal during PHP boot that looks like a totally unrelated arity error (see point 1). Leave the pool’s disable_functions empty and let the rulebook do the work.

When a rule fires: how to debug

Once Snuffleupagus is on, you’ll occasionally see a feature on your site stop working. A plugin breaks. A cron job fails. That’s expected: a real attacker breaks the same way. Your job is to tell legitimate breakage from attempted exploitation.

Snuffleupagus logs violations to PHP-FPM’s error log:

sudo tail -f /var/log/php8.4-fpm.log

Look for lines starting with [snuffleupagus]. They tell you the rule that fired, the script that triggered it, and the function call that was blocked.

A real example:

[snuffleupagus][/wp-content/plugins/some-plugin/loader.php][system][drop]
  - Aborted execution on call of the function 'system' in
    /var/www/html/wp-content/plugins/some-plugin/loader.php on line 47

Now you have a choice. Did your plugin legitimately need system()? Probably not, and it’s safer to remove that plugin. Does the plugin need it for a specific narrow case? Add a per-script allow rule above the global drop:

sp.disable_function.function("system")
    .filename("/var/www/html/wp-content/plugins/some-plugin/loader.php")
    .allow();

Always tighten, never loosen the global rules. Add carve-outs for the one script that needs an exception, keep the rest of your site locked down.

Performance: is this going to slow my site down?

Honestly? No. Snuffleupagus runs inside the PHP process, so there’s no extra HTTP hop. Every function call goes through a hash-map lookup against the loaded rules — we’re talking microseconds per request, well under a percent on any real workload.

The myguard build is compiled with -O3 -flto -fvisibility=hidden -fno-plt and the standard Debian hardening flags (RELRO, BIND_NOW, stack-protector, no-execstack). It’s smaller, faster, and harder to attack than the upstream-stock build. If you’re benchmarking against a no-Snuffleupagus baseline, you might see a 0.5–1% throughput dip on a busy site. You will not see it on a normal site.

For context: a single wp_query() with a couple of joins takes longer than Snuffleupagus’s per-request overhead for an entire WordPress page load. This is not where your performance budget goes.

Snuffleupagus vs ModSecurity vs Suhosin vs disable_functions

People often ask “isn’t this just ModSecurity?” or “didn’t disable_functions already handle this?” Short answer: no, they all watch different things. Here’s the honest breakdown.

Layer Where it runs What it sees What it can’t see
ModSecurity / WAF NGINX / Apache request handler HTTP request bytes, headers, body What PHP does once the request is dispatched
disable_functions PHP php.ini / pool config Function name (e.g. system) Caller, arguments, file path; can’t whitelist per-script
Suhosin PHP extension (legacy) Session/cookie/include hardening; PHP 7.x mostly PHP 8.x abandoned upstream; very limited rule expressiveness
Snuffleupagus PHP extension (Zend) Function name and arguments and caller file/line; per-script and per-pool HTTP-layer attacks that never reach PHP (use ModSec for those)

Verdict: they pair, they don’t compete. ModSecurity stops drive-by junk before it costs you a PHP request. Snuffleupagus stops the exploit chain that successfully bypassed the WAF, the file extension check, and your plugin’s “sanitisation.” disable_functions is the blunt hammer that breaks legitimate code along with the attacks. Suhosin is a museum piece on modern PHP.

For the matching WAF-layer guide on this stack, read How to install ModSecurity and OWASP CRS on NGINX next. And no, before you ask: ModSecurity v3 / libmodsecurity3 is not end-of-life. Trustwave ended commercial support in July 2024 and handed the project to OWASP in January 2024. It’s actively maintained at owasp-modsecurity/ModSecurity on GitHub today.

Snuffleupagus inside a Docker container

Containerising PHP-FPM doesn’t change anything fundamental — Snuffleupagus still loads as a Zend extension, still reads a rulebook file, still enforces it on every function call. You just need to make sure the rulebook is actually inside the container at runtime.

Two ways to do it. The easy way: bake the rulebook into your image at build time.

# Dockerfile
FROM debian:trixie-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates wget && \
    wget -qO /tmp/myguard.deb \
        https://raw.githubusercontent.com/eilandert/deb.myguard.nl/main/myguard.deb && \
    dpkg -i /tmp/myguard.deb && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
        php8.4-fpm php8.4-snuffleupagus && \
    rm -rf /var/lib/apt/lists/* /tmp/myguard.deb

COPY ./conf/wordpress-strict.rules /etc/php/8.4/php-snuffleupagus/active.rules
COPY ./conf/www.conf               /etc/php/8.4/fpm/pool.d/www.conf

EXPOSE 9000
CMD ["php-fpm8.4", "--nodaemonize"]

The flexible way: mount the rulebook at runtime, so you can hot-swap rule packs without rebuilding:

# docker-compose.yml
services:
  php:
    image: deb.myguard.nl/php-fpm:8.4-snuf
    read_only: true
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    user: "33:33"
    tmpfs:
      - /tmp
      - /var/run
    volumes:
      - ./conf/wordpress-strict.rules:/etc/php/8.4/php-snuffleupagus/active.rules:ro
      - ./conf/www.conf:/etc/php/8.4/fpm/pool.d/www.conf:ro
      - wp-content:/var/www/html/wp-content

The read_only: true + cap_drop: ALL + no-new-privileges combo is the Docker hardening pattern we cover end-to-end in Docker Hardening for Self-Hosters. Pair it with Snuffleupagus and you’ve got two completely independent layers of “no” between the attacker and your filesystem.

Quick reference: the sp.* directives you’ll actually use

The rulebook syntax looks alien at first. It’s not — it’s just chained method calls. Here are the seven directives that cover ~95% of real-world configs.

Directive What it does Example
sp.disable_functionBlock / allow / log a function call, optionally filtered by file, args, hash.function("system").drop();
sp.evalGlobally drop eval() (eval is always dangerous; no filename needed)sp.eval.drop();
sp.cookie_encryptionEncrypt a named cookie at runtime (PHPSESSID, custom session names).name("PHPSESSID").encrypt();
sp.ini.keyConstrain an INI value to a range and lock it read-only.key("memory_limit").max("1024M").rw();
sp.readonly_execRefuse to execute any PHP file that is also writable by the running user — kills “upload-then-execute” attackssp.readonly_exec.enable();
sp.global_strictForce strict comparisons in PHP — closes a whole class of == bypass trickssp.global_strict.enable();
sp.upload_validationRun an external script against every uploaded file before the move is allowed.script("/usr/local/bin/clamscan").enable();

All seven are demonstrated, in real use, in the rulebooks the myguard package installs. cat /etc/php/8.4/php-snuffleupagus/wordpress-strict.rules is your best friend the first month.

PHP version support: 7.0 through 8.5

The myguard repository ships php-snuffleupagus as a per-PHP-version package. You install the one that matches your interpreter:

sudo apt install php7.0-snuffleupagus   # legacy long-tail apps
sudo apt install php7.4-snuffleupagus   # WordPress on Debian 11
sudo apt install php8.0-snuffleupagus
sudo apt install php8.1-snuffleupagus   # Debian 12 default
sudo apt install php8.2-snuffleupagus
sudo apt install php8.3-snuffleupagus
sudo apt install php8.4-snuffleupagus   # Debian 13 Trixie default
sudo apt install php8.5-snuffleupagus   # latest

Why per-version? Snuffleupagus is a Zend extension — it has to be compiled against the exact PHP API version it runs in. Upstream provides source; the myguard packages compile against every officially supported PHP build, so you don’t need a compiler or a CI pipeline just to deploy this WAF-inside-your-interpreter.

Each version is built with identical flags (-O3 -flto -fvisibility=hidden -fno-plt plus Debian hardening: RELRO, BIND_NOW, stack-protector, no-execstack), so the security guarantees are the same regardless of which PHP you’re on. The only practical difference between PHP 7.x and 8.x for Snuffleupagus is that 8.x exposes a few extra hookable internal functions — the rule syntax is unchanged.

One quirk to know: PHP 8.5 with tracing JIT enabled mis-runs WordPress under Snuffleupagus on some workloads. If you see weird strtolower/strcoll arity errors, set opcache.jit=off in that pool. We hit this on our own MCP agent pool and the fix is straightforward; it’s an interaction between PHP 8.5’s tracing JIT and WordPress’s internal code paths, not a Snuffleupagus bug.

Frequently asked questions

Do I still need a WAF if I have Snuffleupagus?
Yes. They protect different layers. A WAF stops bad HTTP requests before they reach PHP at all — that’s faster and cheaper than running PHP and finding out. Snuffleupagus is your last line if something gets through. Use both. The combination is much stronger than either alone.
Does Snuffleupagus replace ModSecurity?
No — they cover different layers. ModSecurity inspects the HTTP request before PHP runs at all. Snuffleupagus inspects what PHP actually does, including after a request has been “approved” by the WAF. Pair them; they’re complementary, not redundant.
Is ModSecurity / libmodsecurity3 end-of-life?
No. Trustwave ended commercial support in July 2024 and handed the project to OWASP in January 2024. The codebase is actively maintained on GitHub under owasp-modsecurity/ModSecurity, and our repo ships current builds. “ModSec is dead” headlines refer to the vendor exit, not the code.
Can I run Snuffleupagus inside a Docker container?
Yes — see the Docker section above. The extension loads identically inside or outside a container; you just need the rulebook present in the image (or bind-mounted at runtime). Pair it with Docker’s own hardening (read-only filesystem, dropped capabilities, no-new-privileges) for a really stubborn container to break.
Which PHP versions are supported?
PHP 7.0, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, and 8.5 — each shipped as a separate phpX.Y-snuffleupagus Debian/Ubuntu package. Install the one that matches the PHP your site actually runs (php -v tells you).
My PHP-FPM won’t start after I added the rulebook. What do I do?
Run sudo journalctl -u php8.4-fpm -n 100 and read the last messages. Snuffleupagus reports the file path and line number of the broken rule. The most common cause is a typo in sp.configuration_file — double-check the path exists with ls -la. If the error trace looks impossible (claims a function call that isn’t in the source), see “things that will trip you up” above — it’s almost always a Snuffleupagus rule, not a PHP bug.
Will Snuffleupagus break my WordPress plugins?
A few will misbehave on wordpress-strict.rules if they shell out to external binaries. Switch that pool to wordpress-lax.rules and most things start working. If a specific plugin still complains, check the FPM log for the rule that fired and either tighten the plugin’s behaviour or add a narrow allow-rule for that one script.
What’s the difference between .drop() and .kill() in a rule?
.drop() makes the blocked function return false — the PHP script continues, just without the dangerous call succeeding. .kill() terminates the PHP process immediately. Default to .drop(); use .kill() only when you’re certain an active exploitation is in progress and you’d rather drop the request than let the script proceed.
Does cookie encryption log everyone out?
When you first enable sp.cookie_encryption on a cookie name, existing unencrypted cookies of that name become unreadable and users get logged out. Plan for that — deploy at low-traffic time, or whitelist the existing session cookie name temporarily during the transition.
Can I change the rulebook later?
Yes. Edit the sp.configuration_file line in your pool’s .conf and reload FPM. Each pool can use a different rulebook, and you can switch between strict/lax mid-life without reinstalling anything.
Where on earth does the name come from?
Mr. Snuffleupagus — “Snuffy” to his friends — is Big Bird’s enormous, woolly best friend on Sesame Street. For years he was invisible to every adult on the show; only kids could see him. The security extension is invisible the same way: it sits quietly inside PHP, watching everything, and an attacker reaching your server has no idea it’s there. Good name.

What to do tomorrow

You did it. You’ve installed a real, production-grade hardening layer on your first day. The plant is watered. The website is harder to break. Tomorrow, do this:

  • Visit your site as a normal user. Click around. Log in, log out. Check that tail -f /var/log/php8.4-fpm.log shows nothing unusual.
  • If you run cron jobs, run them manually once and watch the log.
  • If you administer plugins, install one and uninstall it — that’s where weird shell-outs happen.
  • Pick a small page you don’t mind breaking and try a few WordPress admin actions that touch files (theme editor, plugin install). If a Snuffleupagus rule fires on a legitimate action, you’ll see it in the log and you can decide if that plugin should be allowed the exception.

Welcome to the job. You’re going to be alright.

Related posts