---
title: "PHP security middleware: quickstart · camada"
description: "Keep credential stuffers, scrapers and exploit scanners out of your PHP app: install from GitHub, two lines and one env var."
url: https://www.camada.app/docs/quickstart/php
last_updated: 2026-10-30
---

Quickstart

# PHP quickstart

Installed from GitHub ([camada-app/camada-php](https://github.com/camada-app/camada-php)).

## 1. Install

Not on Packagist yet. Run these in your project's folder: Composer installs it straight from GitHub, composer.lock pins the commit, and composer update camada/camada moves it.

```sh
composer config repositories.camada vcs https://github.com/camada-app/camada-php
composer require camada/camada:dev-main
```

## 2. Environment

The key is printed once when you create a project in the app. Set these as environment variables: export them in the shell that runs php -S; under PHP-FPM, which clears the environment, add env[CAMADA_KEY] = … to the pool config. PHP reads no .env file.

shell

```sh
export CAMADA_KEY=<ingest_token>.<snap_token>
export CAMADA_INGEST_URL=http://localhost:8787   # dev only; defaults to production ingest
```

## 3. Wire it in

```php
<?php
// public/index.php — any front controller
require __DIR__ . '/../vendor/autoload.php';

use Camada\Camada;
use Camada\Http\Sapi;

$cam = Camada::default();      // built lazily from the environment, once per request
$ctx = Sapi::run($cam);        // null: camada answered (block, challenge, beacon) and the script must stop
if ($ctx === null) {
    return;
}
// your app: x-rid and the _sfp cookie are already on the response
```

- Needs nothing but $_SERVER, php://input and header(): FPM, php -S, mod_php.
- The snapshot and event spool live in a file-backed cache every worker shares; both refresh after the response has left.

## 4. Add the beacon to HTML pages

Optional but recommended. The beacon cuts false positives by showing which sessions are real browsers. Use the helper.

```php
echo '<html><head>', $cam->scriptTag($ctx), '</head>…';
```

## 5. Send app outcomes

Login failures, signups and checkout declines are the signals no edge position sees. The identifier is HMAC-hashed in your process. How they catch [credential stuffing](https://www.camada.app/use-cases/credential-stuffing).

```php
$cam->track($ctx, 'login_failed', $email);
```

Runtime

## What it does per request

1. Keeps the snapshot in a file-backed cache every worker shares (CAMADA_CACHE_DIR) and refreshes it in the post-response phase, after the client has its bytes. An empty cache answers the first request open.
2. Resolves the client IP from REMOTE_ADDR per your trusted-proxy setting. Raw X-Forwarded-For is never trusted without it; behind nginx real_ip the peer is already the client, so say none.
3. Your ordered custom rules first, then allow, block, challenge. A block gets a 403 with x-block-reason before your app runs; a challenge serves the proof-of-work page on a page load, and an API call reaches your app with an x-camada-challenge: 1 response header. The event still ships.
4. Serves /_cam/b.js (the beacon, first-party, from opcache) and relays /_cam/fp posts onto the event spool with the resolved client IP.
5. Sets x-rid and the _sfp session cookie, and after the response has ended appends one redacted event to the spool; a due spool ships in that same post-response phase.

Limits

## Caveats at this position

- Enforcement at this position covers IP, path, user-agent and request-header conditions. ASN, country and TLS-fingerprint entries fail open in-app.
- Header order is whatever getallheaders() yields, so the analyst reads no HEADER_ORDER signal from this tap and never scores its absence.
- Under FPM and FrankenPHP the post-response work runs after fastcgi_finish_request(); under php -S and mod_php the buffer is flushed with a Content-Length first. Run the CLI server with PHP_CLI_SERVER_WORKERS=4 or more.
- The cache dir must be a local, writable directory (flock and rename are the concurrency model). Each host keeps its own copy and ships its own spool.
- Events ship when the spool is due, on the next request that finds it due: a quiet site ships its last events on its next hit, not on a timer.

Kill switch: `CAMADA_DISABLED=1`, checked per request. All other variables are listed in the [docs index](https://www.camada.app/docs); the package's options and deployment notes are on the [PHP page](https://www.camada.app/docs/php).

## Sitemap

[Every page on this site](https://www.camada.app/sitemap.md)
