Set up the Proxy page-gate with nginx
By the end of this tutorial the gate will be running in front of a site behind nginx: an un-cleared visitor is sent to a game, a solved game sets a cookie, and later requests pass straight through. You will watch the whole thing in preview mode first, so nothing is blocked until you have confirmed the wiring. nginx is used here; Traefik, Caddy, and Authelia recipes follow the same shape.
1. Turn on Preview mode
In the dashboard, open the troop and turn on Preview mode. While it is on, the gate authorizer records challenge-vs-passthrough decisions to the statistics page but never blocks. You will turn it off at the end.
2. Enable the gate on your site key
Open your site key and go to the Proxy gate page. Turn on Status → Enabled, then set:
- Clearance TTL — how long one solve clears a visitor (default 30 minutes). Shorter means more re-checks but a smaller window if a cookie is stolen.
- Fail mode — what your proxy does when it cannot reach Caputchin's authorizer. Fail closed blocks (safer for a login portal); fail open lets requests through (avoids downtime on a public site).
The page also shows a ready-to-copy nginx snippet with your site key already filled in.
3. Allow the origin you are gating
The challenge only ever redirects back to an allowed origin, the same origin allowlist the widget uses. Make sure it includes the origin you are gating, for example https://auth.example.com. Set it on the site key's Cap configuration if it is not there already.
4. Wire the authorizer and the challenge redirect
Two location blocks: an internal authorizer subrequest, and a challenge redirect the proxy jumps to on a 401. Replace YOUR_SITE_KEY with your public key.
# Gate the protected paths.
location / {
auth_request /_cpt_authz; # 204 = allow, 401 = challenge
error_page 401 = @cpt_challenge;
# ...proxy_pass to your app...
}
location = /_cpt_authz {
internal;
proxy_pass https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Cookie $http_cookie; # forward the cpt_gate cookie
}
location @cpt_challenge {
return 302 https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=$scheme://$host$request_uri;
}Gate the entry paths (the portal HTML and the login POST). Do not gate the authorizer subrequest, health checks, or the callback below.
5. Add the callback that sets the cookie
The challenge page finishes by auto-submitting a small POST to /__cpt/callback on your origin, with cpt_gate (the pass) and to (where to send the visitor) in the body. A tiny handler sets the cookie and redirects. The full contract, including why each cookie attribute matters, is in reverse-proxy recipes. The minimum:
app.post("/__cpt/callback", express.urlencoded({ extended: false }), (req, res) => {
const pass = String(req.body.cpt_gate || "");
const to = String(req.body.to || "/");
const target = new URL(to, `https://${req.headers.host}`);
if (target.host !== req.headers.host) return res.status(400).end(); // same-origin only
res.setHeader(
"Set-Cookie",
`cpt_gate=${pass}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1800`
);
res.redirect(303, target.pathname + target.search);
});6. Watch it in preview
Load the gated URL. Because preview is on, you pass straight through, but the statistics page records a challenge_served (you had no cookie) and, once you solve and the callback runs, a pass_issued. Reload and you should see passthrough climb. If challenges never appear, the authorizer block is not being hit; if the callback 400s, check the to origin.
7. Go live
When the numbers look right, turn Preview mode off. The gate now enforces: un-cleared visitors are sent to the game, solved visitors roam until their pass expires.
Where to go next
- Reverse-proxy recipes — Traefik, Caddy, and the full Authelia walkthrough.
- Proxy page-gate statistics — what each number means once you are live.
- Overview — the concept and what the gate deliberately does not do.