OpenResty + Lua security pipeline
How the custom OpenResty build and its LuaJIT security pipeline protect every request before it reaches your Magento application.
The nginx container is not a stock nginx build. It is a custom OpenResty image — OpenResty bundles nginx with LuaJIT, so Lua code runs inside the nginx worker process at native speed. No sidecar, no proxy to an external service, no added network hop. Security decisions happen inline with request handling.
Why Lua in nginx
Standard nginx can rate-limit and block IPs through static configuration. Bot detection, proof-of-work challenges and the WAF need a decision for every single request, based on what is happening right now.
LuaJIT runs compiled Lua code directly inside the nginx worker. That lets these checks run on every request without a separate service in front of your shop, so they add no noticeable latency.
What the image contains
The image is built from OpenResty (nginx + LuaJIT) with HTTP/2 fingerprinting, Brotli compression, GeoIP, mlcache, automatic TLS certificates, and a CrowdSec bouncer compiled in. A proof-of-work challenge service is bundled alongside it.
HTTP/2 and HTTP/3 (QUIC) are supported. TLS uses TLSv1.2 and TLSv1.3 only.
How a request is checked
Every request passes through several layers inside the nginx container before it reaches Magento. Real visitors don't notice any of it.
- Connection checks. TLS 1.2 and 1.3 only. Each client's TLS and HTTP/2 behaviour is fingerprinted to separate real browsers from scripted clients.
- Reputation. IPs with a bad record, on your environment or across the CrowdSec community network, are challenged or blocked.
- Bot scoring. Request patterns, crawler identity and request rates per IP and per network are combined into one score. Google, Bing and other search engines are verified and never challenged, so SEO is not affected.
- Web application firewall. Request payloads are checked against the OWASP Core Rule Set and Magento-specific virtual patches.
What visitors see
| Outcome | Response |
|---|---|
| Pass | The normal page |
| Challenge | A short browser check that runs by itself. A visitor who passes is not asked again for some time. |
| Interactive challenge | Click to verify, for higher-risk traffic |
| Rate limited | 429 Too Many Requests |
| Blocked | 403 Forbidden |
Rate limiting
The default is 150 requests per second per IP, with a burst allowance of 900. Requests over the burst allowance get 429 Too Many Requests.
Separately, a per-IP connection concurrency limit and a per-ASN rate limit protect against distributed attacks from a single network.
The rate limit defaults are configurable:
NGINX__RATELIMIT_ENABLED='yes'
NGINX__RATELIMIT_RPS='150'
NGINX__RATELIMIT_BURST='900'CrowdSec AppSec (WAF)
CrowdSec AppSec is an inline WAF using the Coraza engine with OWASP Core Rule Set rules plus Magento-specific virtual patching rules (crowdsecurity/appsec-virtual-patching). Requests it flags are blocked before they reach Magento.
For the CrowdSec agent and its community blocklist, see CrowdSec.
Observability
Every request produces a JSON access log entry. The security-relevant fields are:
| Field | Content |
|---|---|
security_flags | All signals that fired (e.g., bot_trusted:googlebot.com, rate_limited) |
security_action | The verdict applied |
security_detail | Human-readable detail for the verdict |
Filter security events on a customer VM:
docker logs nginx 2>&1 | grep '"security_action": "[^"]' | grep -v '"security_action": "pass"'Configuring the pipeline
The NGINX__ keys in .env control the pipeline. Key settings:
NGINX__POW_ENABLED='yes'
NGINX__POW_DIFFICULTY='1'
NGINX__POW_SESSION_EXPIRE='14400'
NGINX__RATELIMIT_ENABLED='yes'
NGINX__RATELIMIT_RPS='150'
NGINX__RATELIMIT_BURST='900'
NGINX__BLOCKLIST_ENABLED='yes'
NGINX__WAF_ENABLED='yes'| Setting | What it does |
|---|---|
NGINX__POW_ENABLED | Turns browser challenges on or off |
NGINX__POW_DIFFICULTY | How much work a challenge asks of the browser, from 1 (lightest) to 5 |
NGINX__POW_SESSION_EXPIRE | How long, in seconds, a visitor who passed a challenge is not asked again |
NGINX__RATELIMIT_RPS, NGINX__RATELIMIT_BURST | Requests per second per IP, and the burst allowance above it |
NGINX__BLOCKLIST_ENABLED | Blocks IPs listed in NGINX__NGINX_BLACKLIST with a 403 |
NGINX__WAF_ENABLED | Turns the web application firewall on or off |
Don't put comments on the same line as a value; the platform reads them as part of the value. After editing, open the environment's Containers tab and choose ⋯ → Install on the nginx container. Restarting the container does not apply changes.
To test the pipeline manually, visit https://your-domain.com/.pow to trigger a challenge. After solving, refresh to confirm the session cookie grants access.
Request flow summary
Client
│
▼
TLS Fingerprint + certificate
│
Security checks Reputation, bot scoring, WAF
│
Verdict Pass, challenge, rate limit or block
│
▼
Varnish (when enabled), then PHP
│
Access log Structured JSON, including the security verdict