---
type: tutorial
title: Set up the Proxy page-gate with nginx
summary: Enable the gate on a site key, wire an nginx reverse proxy end to end, and watch it in preview mode before it blocks a single real visitor.
---

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](/docs/proxy-page-gate/reverse-proxy-recipes) follow the same shape.

<Callout type="tip">
Do step 1 before you touch your proxy. Preview mode lets the gate run in monitor-only: the dashboard records what it *would* have done, but every request still passes, so a wiring mistake never locks anyone out.
</Callout>

## 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](/docs/proxy-page-gate/statistics) 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.

```nginx
# 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](/docs/proxy-page-gate/reverse-proxy-recipes#the-callback). The minimum:

```js
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](/docs/proxy-page-gate/statistics) 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](/docs/proxy-page-gate/reverse-proxy-recipes) — Traefik, Caddy, and the full Authelia walkthrough.
- [Proxy page-gate statistics](/docs/proxy-page-gate/statistics) — what each number means once you are live.
- [Overview](/docs/proxy-page-gate/overview) — the concept and what the gate deliberately does not do.
