Reverse proxy + WAF for hosted sites on Debian 13
How Jabali Panel uses nginx, Let's Encrypt, and CrowdSec AppSec to front per-user PHP-FPM pools, panel APIs, and webmail — declaratively, on Debian 13.
If you ask a sysadmin for a “Docker proxy” they usually mean Traefik or Caddy in front of containers. Jabali Panel does something different: it uses Debian-native nginx as the single reverse proxy in front of every hosted workload — per-user PHP-FPM pools, the panel API, the webmail service, Stalwart admin, anything bound to a Unix socket on the host. There is no container layer between nginx and the backend.
This post explains the proxy architecture, the WAF that runs inline with it, and the reconciler pattern that keeps nginx config in sync with the panel database.
Why no Docker host
Jabali Panel does not run inside Docker. Docker and systemd-nspawn are explicitly unsupported as the panel host. The panel depends on systemd-machined operations (used by some migrations and the nspawn helper) plus cgroup v2 semantics and systemd-user timers that don’t work inside unprivileged containers. The supported deployment is KVM / QEMU on a clean Debian 13 cloud image, or LXD privileged on a recent host.
The panel manages hosted workloads. Those can absolutely be containerized (a tenant might run a Node app under Docker on their user-owned PHP-FPM pool host), but the panel itself runs natively on Debian.
nginx as the single entry point
nginx (Debian native package) listens on :80 and :443. On :80 it serves only http-01 ACME challenges and HTTP→HTTPS redirects; on :443 it terminates TLS and routes by Host header to one of:
- A per-user PHP-FPM Unix socket (the hosted website path)
- The panel API on its loopback port (administrative routes)
- The webmail service (Roundcube)
- Stalwart admin HTTP on
127.0.0.1:8080 - The reconciler-managed default vhost (the panel hostname)
Backends are addressed by Unix socket where possible. Stalwart admin and the panel API both bind loopback only — nothing exposes admin auth over TCP outside the SMTP / IMAP ports themselves.
The installer purges sury-nginx if present and pins nginx to the Debian package. Sury dropped nginx packaging in 2026; staying on Debian-native means staying on the security-maintained version.
Let’s Encrypt, automated
Toggling SSL on a domain in the panel triggers a certbot run. The reconciler:
- Writes a temporary nginx config that serves the
http-01challenge from the docroot - Runs
certbot certonly --webrootfor the domain (+ www and any aliases) - On success, writes the production vhost config with
ssl_certificate/ssl_certificate_keypaths - Reloads nginx atomically (no dropped requests)
End-to-end takes under 60 seconds in the common case. Renewal is a systemd timer; the cert + reload happens silently within the standard 30-day window.
DNS-01 is available for wildcards, configured per-domain in the UI.
CrowdSec AppSec inline
The WAF is CrowdSec AppSec, running inline with nginx via the cs-nginx-bouncer. Every request passes through the AppSec inspector over a local socket before nginx forwards to the backend:
client → nginx → cs-nginx-bouncer ↔ crowdsec-appsec → backend
↓ (block decision)
403 to client
AppSec ships virtual patches for the common LFI, RCE, SSRF, and XSS patterns plus a custom rules surface for site-specific guards. Decisions made by CrowdSec elsewhere (community blocklists, brute-force scenarios, throttle violations) are enforced at the IP layer by the same bouncer.
This replaces the ModSecurity stack the panel shipped in an earlier generation. The reasons for moving off ModSec are documented in Removed Features.
The reconciler
nginx config in Jabali Panel is never hand-edited. The reconciler is a loop in jabali-agent:
- Source of truth: the panel’s MariaDB database
- Trigger: 60-second tick, or fired immediately when the panel writes any nginx-relevant row (domain create, SSL toggle, PHP version change, redirect add, etc.)
- Output: atomically-rendered site files in
/etc/nginx/sites-available/, symlinked fromsites-enabled/, followed bynginx -t && nginx -s reload
Any manual change to a site file is overwritten on the next tick. The model is intentional — config drift between panel UI and host reality is the source of most operator-induced outages on traditional panels.
Per-user rate limits
Each hosted user gets a dedicated limit_req zone scoped to their domains:
limit_req_zone $binary_remote_addr zone=user_<id>:10m rate=<r>r/s;
Rate and burst come from the user’s hosting package. A noisy tenant cannot consume all of nginx’s request budget — their rate-limit zone has a fixed slice.
This combines with the global CrowdSec decisions (which block scrapers and brute-forcers IP-wide before they reach the per-user zone) to give a layered limiter.
Try it
Spin up a Debian 13 host, run the installer, add a domain in the panel, and enable SSL. The whole flow — DNS prep, A record, certbot, nginx vhost — takes a couple of minutes. The quickstart walks through it; the demo panel lets you see the Domains UI live.
Frequently Asked Questions
- Does Jabali Panel support Docker as the panel host?
- No. Docker and nspawn are explicitly not supported as the panel host environment. Jabali Panel requires a bare-metal or KVM/QEMU Debian 13 (Trixie) host because it depends on systemd cgroup v2 and systemd-machined operations that do not work in unprivileged containers.
- What does Jabali Panel use as its reverse proxy?
- Jabali Panel uses nginx (Debian native package) as its reverse proxy. nginx listens on ports 80 and 443, terminates TLS, and routes traffic to per-user PHP-FPM Unix sockets, the panel API, the webmail service, and other internal Unix-socket backends.
- Does the managed nginx proxy include SSL?
- Yes. Jabali Panel issues Let's Encrypt certificates via certbot automatically when SSL is enabled on a domain. Certificate issuance completes within 60 seconds of enabling SSL. nginx is reloaded atomically by the reconciler after each certificate change.
- What WAF is included with the reverse proxy?
- CrowdSec AppSec WAF runs inline with nginx via the cs-nginx-bouncer. Every request passes through the AppSec inspector over a local socket before nginx forwards it to the backend. This replaces the previously-shipped ModSecurity stack.
- How does Jabali manage nginx configuration?
- Jabali Panel uses a reconciler loop (60-second tick, or triggered on any write) that diffs the panel database against the host state and atomically re-renders nginx site files. You never edit nginx configs by hand — any manual change is overwritten on the next reconciler tick.
- What OS does Jabali Panel support?
- Debian 13 (Trixie) only. The installer detects and rejects earlier Debian releases and Ubuntu. LXD privileged containers on a Debian 13 host work; unprivileged LXC does not.
- What rate-limiting does the reverse proxy apply?
- Jabali Panel configures nginx limit_req per user. Each hosted user's vhost zones are scoped to their account, preventing one noisy tenant from consuming all of nginx's request budget.