This is a preview release. Do not use it in production environments.
Download the release bundle from Download Edge-Native Defender (Preview). New to Edge-Native Defender? Start with Edge-Native Defender overview.
Watch the install (2 minutes): downloading the release, building and running OpenResty with Defender, allowed and blocked requests, Defender's own counters, and the lines to copy into your own nginx.
At a glance
| Deployment | Lua modules and an engine library added to your nginx, and a few lines in nginx.conf. No extra service, no routing changes. |
| Added latency | About 0.2 ms at the median, measured on Kong, which runs the same engine. No call to Cequence on the request path. |
| Cost | About half a CPU core per 1,000 requests a second and about 250 MB of memory per host, on the nginx you already run. Nothing from Cequence for the infrastructure. |
| If something fails | Your APIs stay up. If Cequence is unreachable, Defender keeps enforcing its last policies; if Defender cannot start, nginx forwards requests unchanged. |
| Upgrades | Replace the modules and library from the new bundle and reload nginx, or rebuild the image. Roll back the same way. |
Deployment architecture
Defender runs inside the nginx you already operate: OpenResty, or nginx built with lua-nginx-module. It is the same engine as Defender for Kong, which is also nginx underneath. Each request is decided in the nginx worker that already holds it, from a policy set kept in memory, so there is no extra hop and no network call on the request path.
BEFORE
client ---> nginx ------------------------------------> your upstream
+-----------------------------+
| your servers and locations |
+-----------------------------+
AFTER
client ---> nginx ------------------------------------> your upstream
+----------+ +-----------------------------+
| Defender | | your servers and locations |
| decides | | unchanged |
+----------+ +-----------------------------+
|
+---> your Cequence tenant: policies in, analytics out,
in the background
What stays the same. Your servers, locations, upstreams and proxy_pass
targets. Defender is added at http level and covers every server and location
below it; remove its lines and reload to undo it.
What Defender does with a request. A blocked request is answered by nginx and never reaches your upstream. An allowed request is forwarded byte for byte, and your client receives the response byte for byte: Defender inspects a copy of each body, up to your tenant's capture settings, and never changes what it forwards.
What is sent to Cequence. For each request: its headers and URL, the response status and headers, and the inspected body copies, masked first with your tenant's Sensitive Data rules. Your traffic filters decide whether a request is reported at all. Until the rules have been fetched, nothing is sent unmasked.
Installation
Prerequisites
- OpenResty 1.21.4 or later, or nginx with lua-nginx-module and LuaJIT, on Linux (amd64 or arm64) with glibc 2.28 or later: Debian, Ubuntu or RHEL based. Tested on OpenResty 1.27.1.2 (Debian bookworm). Alpine images use musl and cannot load the engine.
-
lua-resty-http(opm get ledgetech/lua-resty-http). The bundle's Dockerfile installs it. - Outbound HTTPS from every nginx host to your Cequence tenant.
- Your tenant's name, client ID and client secret.
- The
defender-for-nginxrelease bundle. It holds the Lua modules (lua/), the engine for each architecture (lib/linux-amd64/libdefender.so,lib/linux-arm64/libdefender.so), aDockerfile, a completenginx.confand aREADME.mdwith the full reference.
1. Try it with Docker
From the unpacked bundle, for a tenant named acme.1:
docker build -t nginx-with-defender . docker run -d --name defender -p 8080:8080 \ -e CEQUENCE_ORIGIN=https://api.example.com \ -e CEQUENCE_EDGE_URL=https://edge.acme.1.cequence.cloud \ -e CEQUENCE_AUTH_URL=https://auth.acme.1.cequence.cloud/auth/realms/cequence/protocol/openid-connect/token \ -e CEQUENCE_CLIENT_ID=<client id> \ -e CEQUENCE_CLIENT_SECRET=<client secret> \ -e CEQUENCE_INSTANCE_ID=nginx-trial \ nginx-with-defender
Requests to port 8080 are inspected and forwarded to CEQUENCE_ORIGIN. Pass
--build-arg OPENRESTY_IMAGE=<image> to start from your own glibc-based
OpenResty image, and use docker buildx build --platform linux/arm64 for arm64.
2. Or add it to your own nginx
- Copy
lua/into a directory onlua_package_path. OpenResty's/usr/local/openresty/site/lualib/is on it already. - Copy the engine for your architecture to
/usr/local/lib/defender/libdefender.so, or setlibrary_path. - Copy the lines marked
DEFENDERfrom the bundle'snginx.confinto yourhttpblock: - the
envlines, so workers can read the settings; lua_shared_dict cequence_defender 32m;-
resolverandlua_ssl_trusted_certificate, which the connection to your tenant needs because nginx gives Lua neither by default; - the
init_worker_by_lua_block, which holds the settings below; - the three phase blocks:
access_by_lua_block,body_filter_by_lua_blockandlog_by_lua_block. - Reload nginx.
A location that sets its own access_by_lua*, body_filter_by_lua* or
log_by_lua* replaces the inherited one, so add the Defender call to that
block too.
Settings
Passed to init in the init_worker_by_lua_block. The bundle's nginx.conf
reads each from an environment variable.
| Setting | What it does |
edge_url, auth_url
|
Your tenant's addresses, as in step 1. Required |
client_id, client_secret
|
Your tenant's credentials. Required. Read the secret from the environment (os.getenv), not from the file |
instance_id, pool_id
|
Name this deployment in your tenant. Both default to nginx
|
client_ip_header |
The header carrying the real client address, when a load balancer in front of nginx sets one. Without it, the connecting address is used |
telemetry, capture_bodies
|
Send analytics, and include body copies in them. Both on by default |
diagnostics |
Serve /__end/status and answer x-end-echo. Off by default; CEQUENCE_DIAGNOSTICS=on in the bundle's nginx.conf
|
library_path |
Where the engine is installed. Default /usr/local/lib/defender/libdefender.so
|
The bundle's README.md lists the rest, including geo-match databases and the
local traffic configuration (CEQUENCE_TRAFFIC_CONFIG).
3. Check it before you go live
- Turn diagnostics on (
CEQUENCE_DIAGNOSTICS=on, ordiagnostics = true) on a test host, reload, and request/__end/statusthrough nginx. Within a few secondspolicies_loadedshows your tenant's policy count. - Send a request that one of your policies blocks, and check it is blocked.
- Turn diagnostics off again. The status page is readable by anyone who can reach the server.
Defender also serves request counters on 127.0.0.1:9121/metrics, in
Prometheus format, as the bundle's nginx.conf shows. They are the quickest
way to see it working, as the bundle's nginx.conf turns the access log off:
mitigator_requests_received, mitigator_requests_allowed and
mitigator_requests_blocked count requests, and mitigator_cache_num_policies
your tenant's policies loaded. With the Docker trial:
docker exec defender curl -s 127.0.0.1:9121/metrics | grep -E '^mitigator_(requests_|cache_num_policies)'
Upgrades and rollback
Each release publishes a new defender-for-nginx bundle.
On your own nginx, from the new unpacked bundle:
cp -r lua/cequence /usr/local/openresty/site/lualib/ cp lib/linux-amd64/libdefender.so /usr/local/lib/defender/libdefender.so.new mv /usr/local/lib/defender/libdefender.so.new /usr/local/lib/defender/libdefender.so nginx -s reload
Use lib/linux-arm64 on an arm64 host, and openresty -s reload where that is
how you run it. Moving the library into place, rather than copying over it,
leaves the running workers' copy untouched. A reload is graceful: requests in
flight finish on the old workers, and the new workers decide with the policies
nginx already holds. Up to a few seconds of analytics not yet sent can be lost.
With Docker or Kubernetes, rebuild the image from the new bundle's Dockerfile and roll it out. With a rolling restart there is always a node serving; a freshly started container forwards requests until it has fetched your policies, within a few seconds.
Check /__end/status as in "Check it before you go live". To roll back,
install the previous bundle the same way. Your nginx.conf lines do not
change between releases.
Performance and sizing
| Added latency, median | about 0.2 ms |
| Added latency, 95th percentile | about 6 ms near CPU saturation |
| Added CPU | about 0.55 core per 1,000 requests a second |
| Added memory | about 250 MB per host |
Measured on Kong, which runs the same engine: a 4 vCPU host at 1,000 requests a
second with two workers, about 6,000 policies, analytics and masking on, and
1 KB responses. Measurements on nginx itself are being made. Most of the added
CPU is building and masking analytics; with telemetry off, Defender adds about
0.17 core per 1,000 requests a second. More policies, larger bodies or more
Sensitive Data rules raise the cost, so measure with your own traffic, and keep
nginx's workers clear of saturation: on a busy worker the slowest requests slow
down first.
Cost
Cequence charges nothing for the infrastructure. Defender runs on the nginx
hosts you already operate and adds the CPU and memory above. OpenResty and
lua-resty-http are open source.
Availability
- If Cequence is slow or unreachable, your traffic is unaffected: no request waits on Cequence. Defender keeps enforcing the policies it last received. Each worker holds a bounded backlog of analytics and retries; a long outage can drop some of them.
- If Defender itself fails, nginx keeps serving. A missing setting, a missing shared memory zone or an engine that cannot be loaded is logged in nginx's error log, and requests are forwarded unchanged.
-
If you need Defender off the path at once, remove the
DEFENDERlines fromnginx.conf(at the least, the three phase blocks) and reload nginx. With Docker, run your previous image. - A fresh install blocks nothing until your tenant's first policy publish arrives.
Known limits
- Every policy action is carried out:
block,add-header,challenge,redirectandrate-limit. Challenge pages are served on your own hostname at/__cq/auth. A rate limit is counted per nginx host, shared by all of its workers. - Honeytrap redirects need the location's
proxy_passto read its target from a variable, as the bundle'snginx.confdoes with$cequence_origin. Setredirect_variableif yours uses another name. Whereproxy_passnames a fixed upstream, a redirect policy forwards the request and the error log says so. - Alpine and other musl-based images are not supported.
- Right after a publish, workers adopt the new policies within about two seconds of each other.
- Each worker holds the analytics it has not yet sent; a reload or restart can lose a few seconds of them. Enforcement is not affected.
If something is wrong
Requests keep flowing, and nginx's error log says why on lines starting with
defender:.
| Log message | What to do |
lua_shared_dict cequence_defender is not declared |
Add lua_shared_dict cequence_defender 32m; to the http block |
cannot load /usr/local/lib/defender/libdefender.so |
The engine is not at library_path, or is built for the other CPU architecture |
edge_url is not set (or another setting) |
The setting is empty. With the bundle's nginx.conf, check the environment variable and its env line |
token request failed or data request failed
|
nginx cannot reach the host named. Check outbound HTTPS, resolver, lua_ssl_trusted_certificate and your credentials |
The bundle's README.md has the full reference.
Removing it
Remove the DEFENDER lines from nginx.conf and reload nginx; Defender stops
at once. Then delete cequence/defender from your Lua path and
/usr/local/lib/defender/ if you no longer want the files.