Integration recipes
Este conteúdo não está disponível em sua língua ainda.
Find your infrastructure below and paste the recipe. If you are still choosing between the ways to connect, start at Choose the integration that fits your infrastructure.
Pick yours
Section titled “Pick yours”| Integration | How it connects | Status |
|---|---|---|
| NGINX | auth_request in the server {} you already have | Available |
| OpenResty | The NGINX recipe, unchanged | Available |
| Nginx Proxy Manager | The NGINX recipe in “Custom Nginx Configuration” | Available |
| ingress-nginx | Four annotations on your Ingress | Available |
| Traefik | forwardAuth middleware, in a YAML file or in Docker labels | Available |
| Dokploy | The Traefik recipe in the app’s Traefik editor | Available |
| Coolify | The Traefik labels on your resource | Available |
| Caddy | forward_auth inside a route block | Available |
| Envoy | ext_authz | Coming soon |
| Cloudflare Worker | A Worker on your domain’s routes | Available |
| Next.js | Middleware in your app | Available |
| Hosted by Qlyne | Your DNS points to Qlyne | Available |
| Qlyne proxy | A container between your web server and your app | Available |
| JavaScript tag | One script line in your pages | Coming soon |
When your web server asks Qlyne
Section titled “When your web server asks Qlyne”Use this when NGINX, Traefik or Caddy already runs your site: HTTPS, domains, routing. You keep all of it. The Qlyne proxy runs beside it in decision mode, and your web server asks it about each request by sub-request to /__qlyne/decide.
Qlyne answers with the verdict from your rules. A 200 means “go on”: your web server forwards the request to your app with the Qlyne-* headers. Anything else (the challenge page, a block, the waiting room redirect, a rate limit) goes to the visitor as Qlyne sent it. The proxy never forwards anything to your app in this mode.
What works in this mode, and what does not
Section titled “What works in this mode, and what does not”The sub-request carries neither the request body nor your app’s response. That decides what applies:
| Works | Needs the Qlyne proxy in front |
|---|---|
| Blocked and trusted IP addresses, countries, Tor, shared reputation | WAF on the request body (path and query are still inspected) |
| AI crawlers, verified bots and impostors, trap paths | Form stamps and the form decoy |
| Rate limits, the waiting room (redirect), locking a path behind an access gate, your own rules | Leaked password and login checks sent by POST, and opening an access gate with the password or the email |
| The challenge, in two steps on NGINX (the page, then the solution straight to the proxy) | The sensor injected into your HTML, so the bot score only comes from a cookie already there |
Qlyne-* headers to your app, with forged copies from the visitor removed | The consent banner, security headers on the response, the page cache and static file cache |
The right column does not break your site: those protections do not run. Each rule still follows your plan: see Plans.
Start the proxy in decision mode
Section titled “Start the proxy in decision mode”Start the proxy as in step 1 of Qlyne proxy, with the docker-compose.yml from Connector › Configuration. Skip step 2 of that page: here your web server asks the proxy instead of forwarding to it. Set these variables:
| Variable | Value |
|---|---|
QLYNE_TENANT, QLYNE_CORE_URL | Already in the file you downloaded |
QLYNE_SECRET | Your connector secret, in .env |
QLYNE_UPSTREAM | Your app’s address. Still required, even though the proxy does not forward |
QLYNE_DECIDE | 1 turns on /__qlyne/decide and the routes of the two-step challenge |
QLYNE_TRUSTED_PROXIES | The IP or CIDR range your web server connects from. Empty: only loopback may ask. Anyone else gets 404 |
QLYNE_DECIDE_DENY_STATUS | Leave it unset (403). Set 401 only for ingress-nginx |
In every recipe, qlyne:8080 is the Qlyne proxy and app:3000 is your app. Replace both with the addresses your web server reaches.
Use it when NGINX already sits in front of your app. Inside the server {} you have:
location / { auth_request /__qlyne/decide; auth_request_set $qlyne_cookie $upstream_http_set_cookie; auth_request_set $qlyne_challenge_passed $upstream_http_qlyne_challenge_passed; # one per Qlyne-* your app uses add_header Set-Cookie $qlyne_cookie always; error_page 403 500 = @qlyne_page; proxy_set_header Qlyne-Challenge-Passed $qlyne_challenge_passed; proxy_pass http://app:3000; # what you already had}location = /__qlyne/decide { internal; proxy_pass http://qlyne:8080/__qlyne/decide; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-Method $request_method; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host;}location @qlyne_page { rewrite ^ /__qlyne/page? break; proxy_pass http://qlyne:8080; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Cookie "$http_cookie; $qlyne_cookie";}location /__qlyne/ { proxy_pass http://qlyne:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; }What each part does:
auth_request /__qlyne/decidesends the sub-request, without a body, with the original request described inX-Original-URIandX-Original-Method.internalhides that route from the outside.auth_request_setkeeps one header from the verdict per variable.$upstream_http_set_cookiecarries the waiting room pass back to the visitor. Add one line perQlyne-*header your app reads, and pass it on withproxy_set_header:Qlyne-Bot-Score,Qlyne-Bot-Reasons,Qlyne-Challenge-Passed,Qlyne-Trusted-Ip,Qlyne-Verified-Bot,Qlyne-Reputation,Qlyne-Reputation-Score,Qlyne-Rule,Qlyne-Gate-User,Qlyne-Leaked-Password,Qlyne-Decoy.proxy_set_headerreplaces whatever the visitor sent under the same name, so nobody forges aQlyne-*header.error_page 403 500 = @qlyne_pageis the first of the two steps.auth_requestdrops the response body and only understands2xx,401and403; any other status becomes500.@qlyne_pageasks the proxy for/__qlyne/page, which sends the real answer for the original address: the challenge or block page with403, or the real302,429or303.location /__qlyne/withoutauth_requestis the second step. The challenge page posts its solution to/__qlyne/verifywith a body, which a sub-request cannot carry.X-Forwarded-Foris the same in the three routes, because the challenge is tied to the visitor’s IP.
Qlyne’s cookies are Secure, so the site has to be on HTTPS for browsers to keep them.
OpenResty
Section titled “OpenResty”Use the NGINX recipe as it is. auth_request comes with the NGINX inside OpenResty; no Lua needed.
Nginx Proxy Manager
Section titled “Nginx Proxy Manager”Open the host, tab Advanced, and paste into “Custom Nginx Configuration”. Nginx Proxy Manager writes location / on its own, so the verdict goes at server level and location / inherits it:
auth_request /__qlyne/decide;auth_request_set $qlyne_cookie $upstream_http_set_cookie;auth_request_set $qlyne_challenge_passed $upstream_http_qlyne_challenge_passed;add_header Set-Cookie $qlyne_cookie always;error_page 403 500 = @qlyne_page;Below these lines, in the same field, add the three Qlyne routes from the NGINX recipe (= /__qlyne/decide, @qlyne_page and /__qlyne/), with auth_request off; inside /__qlyne/. Replace qlyne:8080 with an address the Nginx Proxy Manager container reaches: the host IP, or the container name on the same Docker network.
The location / that Nginx Proxy Manager writes has its own proxy_set_header lines, which override the ones at server level. To send Qlyne-* headers to your app, create the / location in Custom Locations, pointing to your app, and put the proxy_set_header Qlyne-... lines in its configuration field.
ingress-nginx
Section titled “ingress-nginx”Add these annotations to your app’s Ingress:
nginx.ingress.kubernetes.io/auth-url: "http://qlyne.<namespace>.svc.cluster.local:8080/__qlyne/decide"nginx.ingress.kubernetes.io/auth-method: "GET"nginx.ingress.kubernetes.io/auth-response-headers: "Qlyne-Challenge-Passed, Qlyne-Bot-Score, Qlyne-Trusted-Ip"nginx.ingress.kubernetes.io/auth-signin: "https://$host/__qlyne/page?to=$escaped_request_uri"Then three more things:
- Start the proxy with
QLYNE_DECIDE_DENY_STATUS=401. ingress-nginx only sends the visitor toauth-signinon a401, so every verdict that does not go on leaves/__qlyne/decideas401.auth-signintakes the browser to/__qlyne/page, which answers with the real status: the challenge or block page, the waiting room302, the429. - Set
QLYNE_TRUSTED_PROXIESto the CIDR of the controller pods. - Add a second Ingress for the path
/__qlyne/, pointing to the Qlyne proxy Service, without the auth annotations.
ingress-nginx describes the original request in X-Original-URL, and the proxy reads path, query, host and scheme from it. No auth-snippet needed.
Traefik
Section titled “Traefik”Use it with Traefik v3, with the file provider or the Docker provider. Two pieces make it work: the forwardAuth middleware on your app’s router, and a router for /__qlyne/ without the middleware, with a higher priority. The second one carries the challenge solution, which has a body.
In a dynamic configuration file (replace the host, the app’s port and the pass cookies):
http: middlewares: qlyne: forwardAuth: address: "http://qlyne:8080/__qlyne/decide" trustForwardHeader: true authResponseHeadersRegex: "^Qlyne-" addAuthCookiesToResponse: [qlyne_pass_cap] # one qlyne_pass_<event_key> per queue event routers: qlyne-direct: rule: "Host(`loja.exemplo.com`) && PathPrefix(`/__qlyne/`)" priority: 1000 service: qlyne app: rule: "Host(`loja.exemplo.com`)" middlewares: [qlyne] service: app services: qlyne: { loadBalancer: { servers: [{ url: "http://qlyne:8080" }] } } app: { loadBalancer: { servers: [{ url: "http://app:3000" }] } }Your real routers already have entryPoints and tls; give qlyne-direct the same ones as app. addAuthCookiesToResponse lists the cookies the 200 must hand to the visitor: one qlyne_pass_<event_key> per waiting room queue. The challenge and clearance cookies need nothing, because Traefik passes those responses through whole.
In Docker labels, on your app’s service:
- traefik.http.routers.app.middlewares=qlyne- traefik.http.middlewares.qlyne.forwardauth.address=http://qlyne:8080/__qlyne/decide- traefik.http.middlewares.qlyne.forwardauth.trustforwardheader=true- traefik.http.middlewares.qlyne.forwardauth.authresponseheadersregex=^Qlyne-- traefik.http.middlewares.qlyne.forwardauth.addauthcookiestoresponse=qlyne_pass_capAnd on the qlyne service, which runs with QLYNE_DECIDE=1 and QLYNE_TRUSTED_PROXIES set to the range of Traefik’s Docker network:
- traefik.http.routers.qlyne-direct.rule=Host(`loja.exemplo.com`) && PathPrefix(`/__qlyne/`)- traefik.http.routers.qlyne-direct.priority=1000- traefik.http.services.qlyne.loadbalancer.server.port=8080Traefik, your app and qlyne have to share a Docker network, because the middleware and the service are reached by name. Keep the entrypoints and tls.certresolver labels your routers already use on qlyne-direct as well.
Dokploy
Section titled “Dokploy”The domain you add to an app becomes its own Traefik file, shown in Advanced, Traefik. Add the qlyne middleware there, put middlewares: [qlyne] on the router Dokploy wrote, and create the qlyne-direct router with the same entryPoints and tls as that router.
Run qlyne as a Compose service in the same project, or as a Docker app, on the dokploy-network network. QLYNE_TRUSTED_PROXIES is that network’s range (docker network inspect dokploy-network). In a Compose project, the labels above go straight into the file.
Coolify
Section titled “Coolify”The labels Coolify writes are in the resource’s label editor. Add the middleware labels to your app’s service. Run qlyne as a Docker Compose resource on the coolify network, with the qlyne-direct router labels.
Use it with Caddy 2. The whole recipe:
loja.exemplo.com { route { reverse_proxy /__qlyne/* qlyne:8080 forward_auth qlyne:8080 { uri /__qlyne/decide copy_headers Qlyne-Bot-Score Qlyne-Bot-Reasons Qlyne-Challenge-Passed Qlyne-Trusted-Ip Qlyne-Verified-Bot Qlyne-Reputation Qlyne-Reputation-Score Qlyne-Rule Qlyne-Gate-User Qlyne-Leaked-Password Qlyne-Decoy } @pass expression `{http.reverse_proxy.header.Set-Cookie} != ""` header @pass +Set-Cookie {http.reverse_proxy.header.Set-Cookie} reverse_proxy app:3000 }}Why each line is there:
routekeeps the order as written. Outside it, Caddy reorders the directives andforward_authwould run before the direct route.reverse_proxy /__qlyne/*comes beforeforward_auth, because the challenge solution reaches/__qlyne/verifywith a body.copy_headersnames each header. The wildcardQlyne-*copies nothing. With the names, each one reaches your app with the verdict’s value, and a copy sent by the visitor is removed.header @pass +Set-Cookiehands the waiting room pass to the visitor.forward_authalone would only add it to the request your app receives.
On the proxy, set QLYNE_TRUSTED_PROXIES to Caddy’s IP or range. With a domain, Caddy serves HTTPS on its own, which Qlyne’s Secure cookies need.
Coming soon, through Envoy’s ext_authz filter.
The other ways to connect
Section titled “The other ways to connect”| Way | When to use it | What it covers |
|---|---|---|
| Cloudflare Worker | Your domain is on Cloudflare | Almost everything (details on the Worker page); runs on Cloudflare’s edge |
| Next.js | A Next.js app, on Vercel or elsewhere | What does not depend on your app’s response |
| Hosted by Qlyne | Shared hosting, or nowhere to run anything | Everything, HTTPS included; traffic passes through Qlyne |
| Qlyne proxy | Your own server, with the proxy between your web server and your app | Everything, the right column of the table above included |
JavaScript tag
Section titled “JavaScript tag”Coming soon: one script line in your pages that scores each visitor. With decision mode, it brings back the bot score that the sub-request cannot inject.
Check it works
Section titled “Check it works”Switch the site to “Enforce” in Connector › Configuration first. In “Observe only”, Qlyne counts what it would do and lets everyone through. Then, replacing loja.exemplo.com with your site:
# 1. From an address in your blocked list: 403curl -s -o /dev/null -w '%{http_code}\n' https://loja.exemplo.com/produtos
# 2. A challenged route, with no cookie: the challenge pagecurl -s https://loja.exemplo.com/checkout | grep -o '<title>[^<]*'
# 3. A page with no rule: 200 from your appcurl -s -o /dev/null -w '%{http_code}\n' https://loja.exemplo.com/produtosThe first one needs a machine whose IP is in IP rules, in the blocked list. The second prints <title>Checking your browser for a route in “Challenged routes” (Pro; see Bot Management). Open that route in a browser too: the page solves itself and opens yours.
Troubleshooting
Section titled “Troubleshooting”Every visitor has the same IP, or the wrong one
Section titled “Every visitor has the same IP, or the wrong one”Rate limits hit everyone at once, or the blocked list never matches. A CDN or load balancer sits in front of your web server, so the last item of X-Forwarded-For is its address.
Put its ranges in QLYNE_TRUSTED_PROXIES, next to your web server’s. The proxy skips those items from right to left and judges the first address outside the list. Traefik and Caddy also have to trust that CDN, or they drop the header: forwardedHeaders.trustedIPs on the Traefik entry point, trusted_proxies in Caddy. In NGINX, set_real_ip_from with real_ip_header still works; never with 0.0.0.0/0.
The challenge page keeps coming back
Section titled “The challenge page keeps coming back”The solution is not reaching the proxy. /__qlyne/ has to go straight to the Qlyne proxy, outside the sub-request: NGINX location /__qlyne/ without auth_request (auth_request off; on Nginx Proxy Manager), the Traefik qlyne-direct router with priority 1000 and no middleware, Caddy’s reverse_proxy /__qlyne/* before forward_auth, the second Ingress on ingress-nginx. Without it, the solution gets 403 with DIRECT_ROUTE_REQUIRED.
Two other causes: on NGINX, a different X-Forwarded-For in one of the three routes (the challenge is tied to the IP); and a site on plain HTTP, where the browser drops Qlyne’s Secure cookies.
The waiting room or a rate limit answers 500 on NGINX
Section titled “The waiting room or a rate limit answers 500 on NGINX”auth_request only passes 2xx, 401 and 403. The waiting room 302, the 429 and the gate’s 303 turn into 500, and the challenge 403 arrives empty. Add error_page 403 500 = @qlyne_page; and the @qlyne_page route from the recipe. On ingress-nginx, the fix is QLYNE_DECIDE_DENY_STATUS=401 with auth-signin.
/__qlyne/decide answers 404
Section titled “/__qlyne/decide answers 404”The proxy only answers addresses in QLYNE_TRUSTED_PROXIES, or loopback when it is empty. Check that your web server’s address is in the list and that QLYNE_DECIDE=1 is set.