This page collects every limit and defence xTQ Web has, for traffic spikes and attacks alike.
| Layer | Limit | Default | Over the limit |
|---|---|---|---|
| Connections | XTQW_MAX_CONNS |
16384 | New connections are closed at once |
| TLS handshakes at once | XTQW_MAX_HANDSHAKES |
2 per CPU | A connection waits up to XTQW_HANDSHAKE_WAIT_MS (800), then is closed |
| Time for TLS handshake and request headers | XTQW_HEADER_TIMEOUT |
10 s | The connection is closed |
| Requests in flight per proxy pool | limit in proxy |
1024, wait 3 s | 503 Server busy, retry in a moment |
| Open WebSockets | XTQW_MAX_TUNNELS |
16384 | 503 Server busy, retry in a moment |
| PHP requests per client address | XTQW_PER_IP_SLOTS |
4 | 503 Too many concurrent requests from your address, retry shortly |
| PHP requests in total | learned from xTQ PHP | workers plus queue | Wait up to XTQW_EDGE_WAIT_MS (3000), then 503 Server busy, retry in a moment |
| Unknown browsers under load | challenge, or built in for php |
on under load | The browser check page |
| Scanner probes | probes in a site |
on | 404 |
Backend pools
Every proxy pool has a cap on requests in flight, 1024 by default. Past it, requests wait up to 3 seconds for a slot and then get a fast 503 with Server busy, retry in a moment and Retry-After: 2. The backends never see more than the cap. Change it with limit <n> [wait], or remove it with limit off. Details in Reverse proxy.
PHP sites
php sites have their own protection, all on by default:
- a waiting room that lets only as many requests into xTQ PHP as it can hold,
- a fair share of 4 requests in PHP at once per client address,
- the browser check while xTQ PHP is overloaded,
- the page cache, which keeps serving cached and stale pages to everyone.
Details in Handlers.
Browser check
The browser check sits in front of a route and lets real browsers through while keeping out clients that don't run JavaScript. Turn it on with challenge in a site or route block. For php sites it is built in and controlled by XTQW_CHALLENGE.
When it is shown
| Setting | Shown to visitors without a pass |
|---|---|
challenge (same as challenge overload) |
While the backend that would answer is under heavy load |
challenge always |
Always |
php sites with XTQW_CHALLENGE=1 |
While xTQ PHP is overloaded |
For a proxy pool, "under heavy load" starts when 75% of its in-flight cap is in use, or when any request is waiting for a slot. It ends when use drops below 50%. The gap keeps it from flickering. With limit off the share is measured against 1024. The change is logged:
xtq-web: example.com /checkout/*: under heavy load, browser check on (75% of its in-flight cap)
xtq-web: example.com /checkout/*: load back to normal, browser check off (49% of its in-flight cap)
For a php site, xTQ Web asks xTQ PHP for its load every 150 ms. It counts as overloaded while at least XTQW_OVERLOAD_BUSY_PCT percent of the workers are busy (default 90), or the queue is at least XTQW_OVERLOAD_QUEUE_PCT percent full (default 60), or PHP can't be reached. It also switches on the moment xTQ PHP answers that its queue is full.
In overload mode, routes answered by files, redirect, respond or abort are never checked. They cost next to nothing.
What the visitor sees
A small page titled "Verifying your browser" with status 503, Retry-After: 1, Cache-Control: no-store, X-Xtq-Cache: CHALLENGE and a noindex robots tag. The page finds a number such that the SHA-256 hash of the challenge and that number starts with a given count of zero bits. It runs in a Web Worker so it keeps going in a background tab, and falls back to the page itself where Workers are blocked. A browser takes a moment. Then it posts the answer to /.xtq/verify, receives a pass cookie and reloads the page.
| Setting | Default | Meaning |
|---|---|---|
XTQW_POW_BITS |
18 | Zero bits required. Each extra bit doubles the work. Kept between 8 and 28 |
XTQW_SECRET |
random at each start | Key that signs challenges and passes |
A challenge must be solved within 5 minutes. The pass is the xtq_pass cookie, valid for 24 hours, set with Path=/, SameSite=Lax and HttpOnly. It is good on every route of the site that has a challenge. Passes are signed, so nothing is stored per visitor.
The cookie is also marked Secure when the visitor came over HTTPS: on an https listener, or through a proxy in trust that sends X-Forwarded-Proto: https. On plain HTTP it is set without Secure, so the check works there too.
Only a valid xtq_pass counts as a pass. Other cookies, such as an app's own login cookie, don't let a visitor skip the check.
/.xtq/verify answers 204 with the cookie on success, 403 invalid solution for a wrong or expired answer, and 400 for a malformed request. It reads at most 4 KB of body within 5 seconds.
Things to know
- Without
XTQW_SECRET, every restart and every reload makes a new key, and every visitor has to pass again. Set it to a long random string in/etc/xtq-web/env. - Clients that don't run JavaScript, such as
curl, API clients, webhooks and most crawlers, never get a pass. While the check is on they get the503page. Keepchallengeoff API routes that machines call, or put it only on the routes browsers load. - In overload mode the check is off almost all the time. Use
challenge alwaysif you want to see it on a quiet site.
Scanner probes
Bots request paths like /.env or /../../etc/passwd on every server they find. xTQ Web answers them with 404 as soon as the site is chosen, before any route is looked at, so they never reach your apps.
A request is a probe when its path, lowercased, contains /../, ..\, %2e%2e, ..%2f, %252e or %c0%ae, or ends in /...
It is also a probe when the path, once percent-decoded, has any of:
- a segment named
.envor starting with.env., - a segment named
.git,.svn,.hg,.aws,.ssh,.docker,.kube,.npmrc,.htpasswd,.bash_history,id_rsa,.vscode,.idea,.ds_storeor.htaccess, - a segment named
phpinfo.php,info.php,server-status,actuator,cgi-bin,boaform,shell.phporeval-stdin.php, - a segment named
wp-config.bak,config.php.bak,backup.sql,dump.sqlordatabase.sql, or anything containing/wp-config.php.or/wp-config.php~, - the segments
etc/passwd,etc/shadow,proc/selforvendor/phpunitin a row.
These match at any depth, so /api/.env and /static/cgi-bin/x count too. Paths like /.well-known/..., /.github-pages/, /environment or /blog/git-tips are not probes.
The answer is 404 with Cache-Control: no-store and a short plain-text note that repeats the path. Set XTQW_PROBE_MEMES=0 to answer with a plain Not Found instead. Probes are counted on the dashboard and as xtq_probes_total. A request for a hostname no site answers gets the plain 404 Not Found, whatever its path.
The check is on for every site. If an app of yours really serves one of these paths, such as Spring Boot's /actuator/health, turn it off for that site with probes off, directly in the site block:
site app.example.com {
probes off
proxy 127.0.0.1:8080
}
probes on is the default. probes goes only directly in a site block, not in a route, and covers the whole site. Other sites keep the check.
Connections and handshakes
Each open client connection takes one of XTQW_MAX_CONNS slots (default 16384). The number is capped at half the process's open-file limit, which xTQ Web raises to the hard limit at startup. The packaged unit sets that limit to 1048576. A connection over the cap is accepted and closed at once.
TLS handshakes are the most CPU-heavy work xTQ Web does, so at most XTQW_MAX_HANDSHAKES run at once (default two per CPU). A new TLS connection waits up to XTQW_HANDSHAKE_WAIT_MS (default 800) for a slot. If none frees up, the connection is closed before any cryptography starts. Under a connection flood the CPU stays bounded and the visitors already connected keep being served.
The TLS handshake must finish, and an HTTP/1.1 client must send its request headers, within XTQW_HEADER_TIMEOUT seconds (default 10). An HTTP/2 connection carries at most 256 requests at once and is pinged every 30 seconds.
The listen queue is 65535 deep, so a burst of new connections waits in the kernel instead of being refused. The kernel caps it at net.core.somaxconn.
TLS session resumption is on, so returning visitors skip most of the handshake cost.
