Blog · Engineering

Self-hosted face recognition and liveness: run KYC on-prem as one Rust binary

One statically-linked Rust binary, MIT-licensed face models, no onnxruntime, no libpq, no GPU and embedded database migrations — how the engine runs air-gapped on your own infrastructure, with the vendor never holding a biometric.

31 August 2026Neetix Engineering9 min read

Why a SaaS vendor structurally cannot hand you a binary

For a bank or a lender, the strongest data-residency posture is not a region setting — it is that biometric images never leave your infrastructure at all. Most verification APIs cannot offer that shape, and the reason is not technical timidity. Their business is the cloud: the meter, the managed models, the lock-in. A vendor whose margin depends on your traffic flowing through their servers is not going to ship you a binary that removes their servers from the loop.

We ship that binary. This post is the engineering description of what it is, what is deliberately not inside it, and where running it yourself is the right call rather than a vanity checkbox.

One binary, and what is deliberately not in it

The self-hosted deployment is a single pure-Rust binary with database migrations embedded. There is no onnxruntime to install, no libpq to link against, and no separate model server or GPU runtime to stand up. Inference runs on CPU through tract, a pure-Rust runtime, so the same artifact that runs in a container runs on a bare VM inside a locked-down network.

Self-hosted: same REST surface, your host
# one process, migrations run themselves on boot
./neetix-liveliness            # listens on :8090 by default

# license-clean models, no vendor agreement required
[engine]
backend = "clean"              # YuNet + AdaFace + MiniFASNet

# identical calls to the cloud API, only the base URL changes
curl -X POST http://localhost:8090/v1/face/face-match \
  -H "Authorization: Bearer $LIVELINESS_API_KEY" \
  -F "person=@selfie.jpg" \
  -F "card=@id_card.jpg"

“No onnxruntime, no libpq” is not a slogan — it is the difference between an install a security team will sign off on and one that drags in native dependencies and a package manager’s worth of transitive risk. Embedded migrations mean the same binary owns its schema, so there is no sidecar to version-match. The API surface is identical to the cloud: point an existing integration at your own host and the request and response contracts do not change.

License-clean by construction

A self-hosted engine is only safe to run commercially if the models inside it are licensed for it. Model licensing is invisible in a sales deck and very visible in procurement, so the self-host build runs exclusively on permissively-licensed models, with the full license texts shipped next to the model files.

ModelRoleLicense
YuNetFace detection + 5-pt landmarksMIT
AdaFace IR-50 / WebFace4MFace recognition (512-d)MIT
MiniFASNet V2 / V1SEPassive anti-spoofApache-2.0
tractRust inference runtimeMIT / Apache-2.0

From THIRD_PARTY_LICENSES.md. The default engine backend is the InspireFace SDK with the Megatron pack — an InsightFace research release with no commercial grant — which is why the self-host answer is backend = "clean". SFace (Apache-2.0) ships for benchmark comparison only and is not served.

The full account of that swap — why the pipeline mattered more than the model, and the number that came out ahead of the engine it replaced — is in the license-clean engine write-up.

Accuracy did not regress to buy the license

The point of a license-clean stack is undermined if it is a downgrade. It is not. On the LFW View-2 protocol — 6,000 pairs, engine-side detect and align — the clean Rust backend scored an EER of 3.05% at cosine 0.126 with zero skipped pairs, against 3.23% for the research-only Megatron engine it replaces, which failed to embed 19 of those pairs. In a benchmark a skip is an excluded row; in production it is a user who cannot complete KYC.

What that number does not say

LFW is easy, celebrity-heavy and not India-representative. It says nothing about selfie-versus-Aadhaar matching or low-light capture, and an absolute EER near 3% is mediocre against the big modern research packs. The claim is license-clean parity plus zero detection failures — not state of the art, and not an India-accuracy number. An India-representative evaluation is the next accuracy work; per our benchmarks policy those numbers ship with the dataset and demographic breakdown attached or not at all.

Self-hosting is not a certification. Running the engine on your own hardware improves data residency; it does not make the deployment iBeta, NIST, ISO or SOC 2 certified — we hold none of those, and the honest status is on the trust page.

Same engine, production-hardened, on your terms

Self-host does not mean a stripped-down build. The single binary carries the production surface: Prometheus metrics with a per-route latency histogram, per-caller token-bucket rate limiting, and webhook delivery retries. For a multi-replica deployment a shared store (Redis) fans replay-dedup, rate-limit and the stream registry across replicas, and a secrets manager resolves signing and attestation keys from env:, file: or HashiCorp Vault so keys stay out of config. Every POST lands in an audit log, one row per call.

Because there is no mandatory outbound dependency, the deployment is air-gappable: the vendor never sees a biometric, because the biometric never reaches the vendor. That is the same face-verification contract you would call in the cloud — 1:1 match on 512-d embeddings, 1:N search, Ed25519-signed responses — running where your compliance perimeter already is.

Where self-host is actually the right call

Self-hosting earns its operational cost in specific places, not everywhere. Data-localization pressure — where a regulator or an internal policy wants biometric data to stay on infrastructure you control. Air-gapped or tightly-segmented networks where an outbound call to a SaaS API is simply not allowed. And volume economics, where a metered per-call bill eventually crosses the cost of running the engine yourself. If none of those apply, the cloud API is the lower-friction choice and shares the same code.

The self-host deployment ships on enterprise terms: one pure-Rust binary, embedded migrations, postpaid invoicing, and commercial support — the same engine, with the meter left behind.

Check our numbers.

Every figure we publish states its dataset and conditions, and the harnesses that produced them ship in the repository.

1000 free credits at signup · API key is shown once at signup.