# Installation - [Meet NiceTryBot](#meet-nicetrybot) - [Why NiceTryBot?](#why-nicetrybot) - [Installing NiceTryBot](#installing-nicetrybot) - [Getting Started Using AI](#getting-started-using-ai) - [Server Requirements](#server-requirements) - [Running the Installer](#running-the-installer) - [Unattended Installation](#unattended-installation) - [What Gets Installed](#what-gets-installed) - [Initial Configuration](#initial-configuration) - [Environment Based Configuration](#environment-based-configuration) - [Managed Postgres and Valkey](#managed-postgres-and-valkey) - [Teams and Registration](#teams-and-registration) - [Installing With Docker Compose](#installing-with-docker-compose) - [The NiceTryBot CLI](#the-nicetrybot-cli) - [Next Steps](#next-steps) ## Meet NiceTryBot NiceTryBot is a batteries-included, bot-fighting framework for all of your websites. It places an edge in front of your sites that sees every request, gives you rules you can read out loud, and sends the bots you choose to a gym where they can burn off some energy. Your real visitors never notice a thing. NiceTryBot runs on your own servers. There is no hosted service to sign up for and no account is needed to run it. Your traffic, your rules, and your visitors' data stay on infrastructure you control. ### Why NiceTryBot? There are plenty of ways to put a wall in front of a website. NiceTryBot takes a different approach: it watches first, explains what it sees, and only acts when you decide it should. #### An Edge That Sees Everything Every request passes through NiceTryBot's edge, a build of [Caddy](https://caddyserver.com) that terminates TLS for your domains. Because the edge reads each visitor's own TLS handshake, it can fingerprint the software that connected, not just the user agent it claims to be. A script pretending to be Chrome has a very hard time hiding. #### Rules You Can Read Out Loud Rules are written in a small language that reads like SQL. Before a rule ever runs, NiceTryBot tests it against your real traffic so you can see exactly who it would have caught: ```rule -- Anyone fishing for secrets, admin panels or exploits. BAN ip, ja4 FOR 30 DAYS WHERE category = 'probe' ``` #### A Gym, Not a Wall Instead of simply blocking a suspicious visitor, NiceTryBot can send it through the bot gym: a series of obstacles that cost a bot CPU time and patience but only cost a person a moment. Bots get a workout. People get to their page. #### An Agent Ready Product Every page of this documentation is available as plain Markdown, NiceTryBot's control panel ships with an [MCP server](https://docs.nicetrybot.com/docs/0.x/mcp.md), and most setup tasks come with a prompt you can paste straight into Claude Code, Codex, or Cursor. If you would rather have an agent do the typing, you are in good company. ## Installing NiceTryBot ### Getting Started Using AI If you are using an AI coding agent like [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [Codex](https://developers.openai.com/codex), you can hand it the installation entirely. The prompt below points your agent at NiceTryBot's deployment brief and these docs, then asks it to check its plan with you before it changes anything. Paste this into your agent to get started: ```text I'd like to install NiceTryBot on a server I control. Fetch and follow https://nicetrybot.com/deploy/agent.md, and use https://docs.nicetrybot.com/llms.txt for anything else you need to know. Treat both as the source of truth. Check the server and the DNS records with me before you change them. ``` The agent should walk you through choosing a server, pointing your panel's hostname at it, and running the installer described below. ### Server Requirements NiceTryBot runs as a small set of Docker containers on a single Linux server. Before installing, make sure you have:
- A fresh server running **Ubuntu 24.04 LTS** or a recent Debian release. Intel and ARM servers both work. - At least **2 vCPU, 4 GB of RAM, and 40 GB of disk** for production use. A 1 vCPU, 2 GB server is enough for a trial. - Ports **80** and **443** open to the world. Nothing else needs to be public. - A hostname for the control panel, such as `panel.example.com`, with an `A` record pointing at the server. - Access to the DNS for every site you plan to protect.
The installer will install Docker Engine 27 or newer and the Compose plugin for you if they are missing. > [!WARNING] > Nothing may terminate TLS in front of the edge. The edge fingerprints each visitor's own TLS handshake, so a load balancer in front of it must pass raw TCP: an AWS Network Load Balancer with TCP listeners, a DigitalOcean load balancer in TCP passthrough mode, or HAProxy in `mode tcp`. Application load balancers, CloudFront, nginx, and Cloudflare's orange-cloud proxy all terminate TLS and should not sit in front of NiceTryBot. ### Running the Installer Once your panel's hostname points at the server, SSH into it and run the installer: ```shell curl -fsSL https://nicetrybot.com/install.sh | sudo bash ``` The installer will ask for three things: your control panel's hostname, an email address for Let's Encrypt certificate notices, and the email address of your first administrator. It then:
- Installs Docker, if needed. - Writes the stack to `/opt/nicetrybot`, including a `compose.yml` file and an `.env` file with freshly generated secrets. - Pulls the images, starts the stack, and creates your first administrator. - Prints the DNS records your protected sites will need. - Installs the `nicetrybot` command at `/usr/local/bin/nicetrybot`.
When the installer finishes, it prints your administrator's password exactly once. Copy it somewhere safe, then visit `https://panel.example.com` and sign in. > [!NOTE] > Running the installer again on a server where NiceTryBot is already running is perfectly safe. It hands over to `nicetrybot update`, which is described in the [upgrade guide](https://docs.nicetrybot.com/docs/0.x/upgrade.md). ### Unattended Installation When provisioning servers with cloud-init or another automation tool, you may pass every answer to the installer as an option and skip the questions entirely: ```shell curl -fsSL https://nicetrybot.com/install.sh | sudo bash -s -- --unattended \ --panel-host=panel.example.com \ --acme-email=you@example.com \ --admin-email=you@example.com ``` The installer accepts the following options: | Option | Description | | --- | --- | | `--dir=PATH` | Where the stack lives. Defaults to `/opt/nicetrybot`. | | `--version=VERSION` | The release to run, such as `0.1.0`. Defaults to the latest release. | | `--compose-only` | Only write `./nicetrybot/compose.yml` and `./nicetrybot/.env`, then stop. | | `--unattended` | Ask nothing. Requires `--panel-host` and `--acme-email`. | | `--panel-host=HOST` | The control panel's hostname. | | `--acme-email=EMAIL` | Where Let's Encrypt sends certificate notices. | | `--admin-email=EMAIL` | The first administrator. Defaults to the ACME email when unattended. | | `--admin-password=PASS` | Their password. Defaults to a random password, shown once. | | `--addons=LIST` | Marketplace [add-ons](https://docs.nicetrybot.com/docs/0.x/addons.md) to build in, or `all`. | | `--composer-token=TOKEN` | The Composer token from your account on nicetrybot.com. | ## What Gets Installed A NiceTryBot installation is a Docker Compose stack. Each service has one job: | Service | Description | | --- | --- | | `edge` | Caddy with NiceTryBot's modules. Terminates TLS, fingerprints visitors, applies rules, and runs the gym. Serves the control panel and every verified domain, issuing certificates on demand. | | `vector` | Tails the edge's access log, adds location data, redacts sensitive values, and ships each line to the app. | | `app` | The control panel and the edge's API. Runs database migrations every time it starts. | | `worker` | Processes the queue. | | `scheduler` | Runs scheduled tasks, such as evaluating your rules every minute. | | `postgres` | The database. NiceTryBot uses Postgres 17 in production. | | `valkey` | Cache, queue, sessions, and locks. | Nothing in the stack calls home. The only outbound requests NiceTryBot makes are for TLS certificates, the monthly DB-IP Lite location databases, pulling images, a daily check for new releases, and, if you have entered a paid add-on's licence key, a daily check that the key is still valid. You may disable the release check by setting `NICETRY_UPDATE_CHECK=false`. ## Initial Configuration ### Environment Based Configuration NiceTryBot's settings live in the `.env` file beside `compose.yml`, which is `/opt/nicetrybot/.env` by default. The installer fills it in for you, and every key is described in the file itself. A few you are likely to reach for: | Key | Description | | --- | --- | | `NICETRY_PANEL_HOST` | The control panel's hostname. | | `NICETRY_EDGE_HOSTNAME` | The `CNAME` target the Domains page tells people to use. Defaults to the panel host. | | `NICETRY_EDGE_ADDRESSES` | The IPv4 addresses `A` records should point at, separated by commas. | | `NICETRY_REGISTRATION` | Who may create an account: `invitations` (the default), `open`, or `closed`. | | `NICETRY_PREVIEW_ZONE` | A wildcard zone for preview hostnames. Previews are off when this is empty. | | `NICETRY_VERSION` | The release to run, such as `0.1.0`, or `latest`. | | `NICETRY_UPDATE_CHECK` | Set to `false` to turn off the daily release check. | After editing the `.env` file, apply your changes by restarting the stack: ```shell nicetrybot apply ``` > [!WARNING] > The installer generated `APP_KEY`, `NICETRY_EDGE_TOKEN`, and your database passwords. Keep a backup of your `.env` file somewhere safe, and never commit it to source control. ### Managed Postgres and Valkey By default, NiceTryBot runs Postgres and Valkey alongside the rest of the stack. If you would rather use a managed database or cache, remove the matching profile from `COMPOSE_PROFILES` and point the usual `DB_*` and `REDIS_*` keys at your managed service. The bottom of the `.env` file includes ready-made presets for DigitalOcean and AWS. You may also run NiceTryBot without Redis at all. Set `CACHE_STORE`, `QUEUE_CONNECTION`, and `SESSION_DRIVER` to `database` and set `COMPOSE_PROFILES=postgres`. ### Teams and Registration Everything in the control panel belongs to a team. Your first administrator gets a personal team automatically, and you may invite teammates from **Settings → Teams**. Each member has one of three roles:
- **Owner** may do everything. - **Admin** may update the team, invite members, and manage domains, rules, the gym, and visitor data. - **Member** has read-only access.
Administrators are a separate, installation-wide role. They see the **Admin** pages for add-ons, administrators, and API tokens. To make another user an administrator, run: ```shell nicetrybot artisan admin:grant you@example.com ``` ## Installing With Docker Compose If you would rather manage Docker yourself, the installer can write the stack's files and stop there. You may then start the stack and create your first administrator by hand: ```shell curl -fsSL https://nicetrybot.com/install.sh | bash -s -- --compose-only cd nicetrybot docker compose up -d docker compose exec app php artisan nicetry:install ``` The `nicetry:install` command creates your first administrator, prints the password once, and prints the DNS records your sites will need. Running it again when an administrator already exists changes nothing. > [!NOTE] > Docker is the supported way to run NiceTryBot. The control panel is a standard Laravel application and can run on a plain PHP host, but the edge cannot sit behind nginx and always needs a server of its own. ## The NiceTryBot CLI The installer adds a `nicetrybot` command to your server. It wraps the Docker commands you would otherwise type by hand: ```shell nicetrybot status # The release, the containers, and the add-ons nicetrybot doctor # Check the server, containers, panel and app, with advice nicetrybot logs edge # Follow a service's logs nicetrybot apply # Restart the stack with what you changed in .env nicetrybot artisan about # Run an Artisan command in the app ``` If something doesn't look right, `nicetrybot doctor` is the best place to start. It is also a great command to let your agent run, since its output explains what it found. The same command can add domains and work with the rest of the control panel, on the server or from your laptop. See [NiceTryBot CLI](https://docs.nicetrybot.com/docs/0.x/cli.md) for everything it does. ## Next Steps Now that NiceTryBot is running, you are ready to put your first site behind it. We recommend reading the following documentation next:
- [Adding Domains](https://docs.nicetrybot.com/docs/0.x/domains.md) - [Protecting Your Origins](https://docs.nicetrybot.com/docs/0.x/origins.md) - [Core Concepts](https://docs.nicetrybot.com/docs/0.x/concepts.md)
--- # Adding Domains - [Introduction](#introduction) - [Adding a Domain](#adding-a-domain) - [Adding a Domain Using AI](#adding-a-domain-using-ai) - [Adding a Domain From the Command Line](#adding-a-domain-from-the-command-line) - [Domain Fields](#domain-fields) - [Host Headers](#host-headers) - [Hosting Platforms](#hosting-platforms) - [Your Own Server](#your-own-server) - [Laravel Vapor](#laravel-vapor) - [AWS CloudFront](#aws-cloudfront) - [Vercel](#vercel) - [Laravel Cloud, Netlify, Heroku, and Render](#laravel-cloud-netlify-heroku-and-render) - [Reading the Visitor's Host](#reading-the-visitors-host) - [Going Live](#going-live) - [Verifying Ownership](#verifying-ownership) - [Trying the Preview Link](#trying-the-preview-link) - [Pointing DNS at the Edge](#pointing-dns-at-the-edge) - [Protection Modes](#protection-modes) - [Origin Settings](#origin-settings) - [Pausing and Removing Domains](#pausing-and-removing-domains) - [Domain Limits](#domain-limits) ## Introduction A domain is a front door: the hostname your visitors type, such as `shop.example.com`. When you add a domain to NiceTryBot, you also tell it where your application actually lives, which we call the **origin**. Once the domain's DNS points at the edge, every visitor passes through NiceTryBot on the way to your origin. Adding a domain never interrupts your visitors. Nothing changes until you verify the domain and point its DNS records at the edge, and even then every domain starts in [observe mode](#protection-modes), where NiceTryBot watches and tags traffic but lets everyone through. ## Adding a Domain To add a domain, open the cog menu in the top right of the control panel and choose **Domains & origins**. Then click **Add domain**. ### Adding a Domain Using AI The **Add domain** modal includes an **Add with AI** button, which copies a prompt for Claude Code, Cursor, or another coding agent. Paste it into an agent that is working in your application's repository and it will:
- Ask you to connect it to NiceTryBot's [MCP server](https://docs.nicetrybot.com/docs/0.x/mcp.md) with an API token, which an administrator creates in **Admin → API tokens**. - Work out your public hostname, an origin hostname, and the endpoints machines call, and check them with you. - Serve your application at the origin hostname. - Add the domain to NiceTryBot and verify it. - Make your origin refuse requests that skip the edge. - Test everything, then stop so you can try it before any DNS changes. - Point DNS at the edge only when you say so.
If you would rather point an agent at this page directly, you may use a prompt like the following: ```text Read https://docs.nicetrybot.com/docs/0.x/domains.md and https://docs.nicetrybot.com/docs/0.x/origins.md, then help me put my app behind NiceTryBot. Check every hostname and DNS record with me before you change it. ``` ### Adding a Domain From the Command Line You may also add a domain using the [`nicetrybot` command](https://docs.nicetrybot.com/docs/0.x/cli.md), on your server or from your laptop: ```shell nicetrybot domains add claims.example.com claims-origin.example.com ``` The command accepts the same settings as the modal, such as `--www` and `--host-header=rewrite`, and prints the records and origin secret you need for the steps below. ### Domain Fields The **Add domain** modal asks for the following: | Field | Description | | --- | --- | | **Domain** | The hostname visitors type, without `https://` or a path. For example, `claims.example.com`. Each domain may only be added once per installation. | | **Serve www. too** | When checked, visitors to `www.` in front of your domain get the same site, counted under the same domain. Turn it off for a subdomain nobody types `www.` in front of. | | **Origin URL** | Where the edge forwards human traffic, such as `https://app.example.net`. It must use `http` or `https` and must be a different host than the domain, or requests would loop back to the edge. | | **Host header** | Which `Host` header your origin receives. See [Host Headers](#host-headers) below. | Once you click **Add domain**, NiceTryBot creates the domain, generates its verification token and origin secret, and takes you to the domain's page to finish setup. ### Host Headers By default, NiceTryBot will **pass the visitor's host through**, so your origin receives the same `Host` header the visitor sent. This is what most applications expect. Some platforms route requests by `Host` header, such as Laravel Vapor, Vercel, Netlify, Heroku, Render, and CloudFront. For these, choose **Rewrite to the origin's host** instead. [Hosting Platforms](#hosting-platforms) has the exact command for each. Your origin will receive the origin URL's host, or the value you enter under **Host header sent to the origin**. When rewriting, your application should build links from its configured URL rather than the incoming `Host` header. If a shared origin needs to know which site a visitor asked for, enter one or more header names under **Forward the visitor's host in a header**, such as `User-Custom-Domain`. The edge always sends `X-Forwarded-Host` as well. ## Hosting Platforms The right settings depend on where your origin is hosted. Find your platform below, copy its command, and swap in your own hostnames. Each command prints the steps to [go live](#going-live), including the origin secret. > [!TIP] > Not sure which platform you're on? Your agent can tell from your repository. Files like `vapor.yml`, `vercel.json`, `netlify.toml`, `Procfile`, and `render.yaml` give it away. Ask it to read this section and give you the command for your app. ### Your Own Server If your application runs on a server you manage, such as one provisioned by Laravel Forge or Ploi, or any server running Nginx, Caddy, or Apache, the defaults are all you need. Point a second hostname, such as `shop-origin.example.com`, at your server and add it to your site's domains (an **alias** in Forge) so it gets a TLS certificate. Then add the domain: ```shell nicetrybot domains add shop.example.com shop-origin.example.com ``` Your application keeps receiving the visitor's own `Host` header, so nothing else about it needs to change. ### Laravel Vapor Vapor serves your application through CloudFront, which only answers hostnames it knows. Give your environment an origin hostname, as you would any Vapor custom domain, and deploy: ```yaml filename=vapor.yml environments: production: domain: - docs-edge.example.com ``` Then add the domain, rewriting the `Host` header to the origin's own: ```shell nicetrybot domains add docs.example.com docs-edge.example.com \ --host-header=rewrite \ --forwarded-host-header=X-Forwarded-Host ``` Your application now receives `docs-edge.example.com` as its host, so set `APP_URL` to your public URL and Laravel will build links that point back through the edge: ```ini APP_URL=https://docs.example.com ``` ### AWS CloudFront If your origin sits behind a CloudFront distribution you manage, such as one in front of a load balancer or an EC2 instance, use the distribution's domain name as the origin: ```shell nicetrybot domains add shop.example.com d111111abcdef8.cloudfront.net \ --host-header=rewrite \ --forwarded-host-header=X-Forwarded-Host ``` CloudFront only passes along the request headers its origin request policy allows, so by default your application would never see the origin secret. In the distribution's behavior, set the **origin request policy** to the managed `AllViewerExceptHostHeader` policy, which forwards every header the edge sends, including `X-NiceTry-Origin-Secret` and `X-Forwarded-Host`. > [!WARNING] > CloudFront answers cached responses without asking your origin, and anyone can request your distribution's domain directly. Use the managed `CachingDisabled` cache policy for your pages so every request reaches your [origin lock](https://docs.nicetrybot.com/docs/0.x/origins.md). Caching static assets is fine. ### Vercel Vercel replaces `X-Forwarded-Host` with its own hostname before your application sees it, so name a header of your own to carry the visitor's host: ```shell nicetrybot domains add shop.example.com my-app.vercel.app \ --host-header=rewrite \ --forwarded-host-header=X-Visitor-Host ``` Use your project's production hostname as the origin, not a preview deployment URL. Then configure your application's public URL, such as `https://shop.example.com`, so the links it builds point back through the edge. ### Laravel Cloud, Netlify, Heroku, and Render These platforms route by `Host` header too. Use the hostname your platform gave your application as the origin: | Platform | Origin | | --- | --- | | Laravel Cloud | `my-app-main-a1b2c3.laravel.cloud` | | Netlify | `my-site.netlify.app` | | Heroku | `my-app-1a2b3c4d5e6f.herokuapp.com` | | Render | `my-app.onrender.com` | Then add the domain. A header name of your own, such as `X-Visitor-Host`, reaches your application untouched on every platform: ```shell nicetrybot domains add shop.example.com my-site.netlify.app \ --host-header=rewrite \ --forwarded-host-header=X-Visitor-Host ``` As with Vercel, configure your application's public URL so its links point back through the edge. For a Laravel application, that is `APP_URL`. ### Reading the Visitor's Host An application that serves a single site only needs its public URL configured. If one application serves several sites, read the visitor's host from the header you chose: ```php $site = $request->header('X-Visitor-Host', $request->getHost()); ``` Anyone can send that header to an unlocked origin, so only trust it once your origin [refuses requests that skip the edge](https://docs.nicetrybot.com/docs/0.x/origins.md#locking-your-origin). ## Going Live Each domain's page includes a **Go live** panel with the steps needed to route the domain through NiceTryBot. The steps are designed to be completed in order. ### Verifying Ownership First, prove the domain is yours by adding a `TXT` record at your DNS provider. The record's name and value are shown on the domain's page, and look like this: | Type | Name | Value | | --- | --- | --- | | `TXT` | `_nicetrybot.shop.example.com` | `nicetrybot-verify=3f9a…` | Once the record exists, click **Check TXT record**. NiceTryBot looks up the record using public DNS and marks the domain as **Live** when the value matches. A brand new record can take a few minutes to appear, so if the check doesn't find it right away, wait a moment and try again. After the domain is verified, you may remove the `TXT` record whenever you like. > [!NOTE] > The verification check runs on your queue. If the check never finishes, make sure the `worker` service is running using `nicetrybot status`. ### Trying the Preview Link If your installation has a preview zone configured using the `NICETRY_PREVIEW_ZONE` environment variable, each domain gets a preview hostname. For example, `shop.example.com` becomes `shop-example-com.preview.example.com`. The preview link serves your site through the edge before you change any DNS, and your origin is asked for the real hostname, so it serves exactly what your visitors will get. Preview hostnames are disabled when `NICETRY_PREVIEW_ZONE` is empty, which is the default. ### Pointing DNS at the Edge Next, point the domain at your NiceTryBot server. Subdomains use a `CNAME` record pointing at your edge's hostname, which is the value of `NICETRY_EDGE_HOSTNAME`: | Type | Name | Value | | --- | --- | --- | | `CNAME` | `shop.example.com` | `panel.example.com` | | `CNAME` | `www.shop.example.com` | `panel.example.com` | Apex domains, such as `example.com`, can't use a `CNAME` record. Instead, add an `A` record for each of your edge's addresses, which are the values of `NICETRY_EDGE_ADDRESSES`: | Type | Name | Value | | --- | --- | --- | | `A` | `example.com` | `203.0.113.10` | The domain's page shows the exact records for your installation. Then, click **Check DNS** to confirm the records are in place. > [!WARNING] > Delete any other `A`, `AAAA`, or `ALIAS` records for the name first. The edge only listens on IPv4, so a leftover `AAAA` record would let IPv6 visitors skip NiceTryBot entirely. The **Check DNS** button only passes once every address points at the edge and no `AAAA` records remain. If you would like to be able to roll back quickly, lower the TTL on your existing records a few hours before you switch them. Once DNS points at the edge, TLS certificates are issued automatically the first time a visitor arrives. Finally, the last step of the **Go live** panel is to lock your origin so that only the edge may reach it. This step is important enough to have its own page: [Protecting Your Origins](https://docs.nicetrybot.com/docs/0.x/origins.md). ## Protection Modes Every domain has a protection mode, which decides what NiceTryBot does with the traffic it tags. You may change a domain's mode at any time from the **Protection mode** panel on its page: | Mode | Description | | --- | --- | | **Off** | Proxy and log only. No tagging. | | **Observe** | Tag suspicious traffic, but let it through. This is the default for new domains. | | **Enforce** | Send tagged traffic to the gym or block it. | We recommend leaving a new domain in **Observe** mode for a few days. You can watch what NiceTryBot would have done in **Activity**, tune your [rules](https://docs.nicetrybot.com/docs/0.x/concepts.md#rules), and switch to **Enforce** once you are comfortable. The [bot gym](https://docs.nicetrybot.com/docs/0.x/concepts.md#the-bot-gym) and tarpits only hold visitors on domains in **Enforce** mode. > [!NOTE] > If the edge can't reach the control panel, it fails open and sends everyone to your origin. An outage of the panel will never take your sites down with it. ## Origin Settings The **Origin** panel on a domain's page controls how the edge talks to your application: | Setting | Description | | --- | --- | | **Origin URL** | Where human traffic is forwarded. | | **Timeout (seconds)** | How long the edge waits for your origin, from 5 to 120 seconds. Defaults to 30. | | **Host header** | Pass the visitor's host through, or rewrite it. See [Host Headers](#host-headers). | | **Server header visitors see** | The `Server` header the edge sends. Defaults to `nginx`. Pretending to be something old and unpatched can make a honeypot more tempting. | | **HSTS** | Off, on for a year, or on with subdomains and preload. Preloading is hard to undo, so enable it with care. | | **Let browsers cache /img, /css and /js** | Caches images for a day and CSS and JavaScript for a week. | | **Serve www. too** | Whether `www.` in front of the domain serves the same site. | The edge picks up new domains and changed settings within about 30 seconds. There is no need to restart anything. ## Pausing and Removing Domains Once a domain is verified, you may **Pause** protection from its page. A paused domain still routes through the edge, but protection stops and traffic flows straight to your origin. Click **Resume** to turn protection back on. To remove a domain entirely, click **Remove domain**, or run `nicetrybot domains remove` with its hostname. Removing a domain stops protection and deletes its request history, which can't be undone. > [!WARNING] > Point the domain's DNS back at your origin **before** removing it. Otherwise, visitors will still arrive at the edge, which no longer knows where to send them. ## Domain Limits Each NiceTryBot installation protects up to 10 domains for free. Every domain on the **Domains & origins** page counts toward the limit, including domains that are still awaiting verification. To protect more, an administrator may add a **More domains** licence key in **Admin → Add-ons**. Each pack adds 100 domains, and packs stack. You can learn more about licence keys in the [add-ons documentation](https://docs.nicetrybot.com/docs/0.x/addons.md#licence-keys). --- # Protecting Your Origins - [Introduction](#introduction) - [The Origin Secret](#the-origin-secret) - [Headers Sent to Your Origin](#headers-sent-to-your-origin) - [Locking Your Origin](#locking-your-origin) - [Locking Your Origin Using AI](#locking-your-origin-using-ai) - [Laravel](#laravel) - [Nginx](#nginx) - [Caddy](#caddy) - [Testing the Lock](#testing-the-lock) - [Rotating the Secret](#rotating-the-secret) - [Additional Hardening](#additional-hardening) ## Introduction Once your domain's DNS points at the edge, every visitor who types your domain passes through NiceTryBot. However, your origin is still on the internet. A scanner that already knows your origin's address, or simply finds it, can send requests straight to it and skip the edge entirely. To close that back door, the edge signs every request it forwards with a secret header. Your origin's only job is to refuse any request that doesn't carry it. ## The Origin Secret Each domain has its own randomly generated, 48 character origin secret. You will find it in the **Lock the origin to the edge** step of the domain's **Go live** panel, alongside the header name: ```text X-NiceTry-Origin-Secret ``` Every request the edge forwards to your origin includes this header. Store the secret in your application's environment, such as an `NICETRY_ORIGIN_SECRET` environment variable, and never commit it to source control. > [!NOTE] > If you use the Origins add-on to serve many domains from one backend, the secret belongs to the origin rather than each domain, and every site behind that origin shares it. ### Headers Sent to Your Origin Along with the secret, the edge sends a few headers your application may find useful: | Header | Description | | --- | --- | | `Host` | The visitor's host, or your rewrite host. See [Host Headers](https://docs.nicetrybot.com/docs/0.x/domains.md#host-headers). | | `X-Forwarded-Host` | The site the visitor asked for. | | `X-Forwarded-For` | The visitor's IP address. | | `X-Forwarded-Proto` | The scheme the visitor used, which is always `https`. | | `X-NiceTry-Origin-Secret` | The domain's origin secret. | | `X-NiceTry-JA3` | The visitor's JA3 TLS fingerprint. | | `X-NiceTry-JA4` | The visitor's JA4 TLS fingerprint. | Since your origin now sits behind a proxy, configure your application to trust the edge so that it reads the visitor's IP address from `X-Forwarded-For`. In Laravel, this is done using the [TrustProxies middleware](https://laravel.com/docs/requests#configuring-trusted-proxies). ## Locking Your Origin Your origin should refuse any request without a matching `X-NiceTry-Origin-Secret` header, before any other code runs. A few guidelines apply no matter what your origin is built with:
- Compare the secret in constant time, and respond with `403 Forbidden` when it is missing or wrong. - In production, refuse every request if the secret has not been configured, rather than silently letting everything through. - Until your DNS points at the edge, lock only the origin's own hostname, such as `www-origin.example.com`, so your live site keeps working. Once DNS moves, lock every hostname. - Match against the `Host` header the request arrived with, not `X-Forwarded-Host`, which anyone can send. - If your hosting platform needs a health check path, allow only that path.
### Locking Your Origin Using AI Locking an origin is a small, well-defined change, which makes it a great job for a coding agent. Open your agent in your application's repository and paste the following prompt: ```text Read https://docs.nicetrybot.com/docs/0.x/origins.md. Then add an origin lock to this application that follows it exactly: refuse requests without a valid X-NiceTry-Origin-Secret header with a 403, read the secret from NICETRY_ORIGIN_SECRET, and add tests. Show me the plan before you change any code. ``` ### Laravel If your origin is a Laravel application, you may use the middleware below. It is the same middleware the NiceTryBot website runs in production. First, add the configuration to your application's `config/services.php` file: ```php 'nicetry' => [ 'origin_secret' => env('NICETRY_ORIGIN_SECRET'), // Hosts whose requests must come through the edge, separated by commas, or * for every request. 'locked_hosts' => env('NICETRY_LOCKED_HOSTS') ?: '*', ], ``` Next, create the middleware: ```php isProduction() ? $this->refuse() : $next($request); } if (! $this->locks($request)) { return $next($request); } $sent = $request->headers->get('X-NiceTry-Origin-Secret'); if (! is_string($sent) || ! hash_equals($secret, $sent)) { return $this->refuse(); } URL::forceRootUrl(config('app.url')); return $next($request); } /** * Determine if the request's host is locked to the edge. */ protected function locks(Request $request): bool { $locked = array_map(trim(...), explode(',', strtolower((string) config('services.nicetry.locked_hosts')))); if (in_array('*', $locked, true)) { return true; } $hosts = [ strtolower($request->getHost()), strtolower(preg_replace('/:\d+$/', '', (string) $request->server('HTTP_HOST'))), ]; return array_intersect($hosts, $locked) !== []; } protected function refuse(): Response { return response('Forbidden', 403, ['Content-Type' => 'text/plain', 'Cache-Control' => 'no-store']); } } ``` Finally, prepend the middleware in your application's `bootstrap/app.php` file so it runs before anything else: ```php use App\Http\Middleware\RequireNiceTryOrigin; ->withMiddleware(function (Middleware $middleware): void { $middleware->prepend(RequireNiceTryOrigin::class); }) ``` While you are still testing, set `NICETRY_LOCKED_HOSTS` to your origin hostname. Once your DNS points at the edge, remove it, and every request will be locked. ### Nginx If your origin is served by Nginx, you may refuse requests without the secret in the `server` block for your site. Replace the example value with your domain's secret: ```nginx server { server_name www-origin.example.com; if ($http_x_nicetry_origin_secret != "your-origin-secret") { return 403; } # ... } ``` ### Caddy If your origin is served by Caddy, you may use a named matcher that matches requests without the secret: ```caddyfile www-origin.example.com { @outsider not header X-NiceTry-Origin-Secret {env.NICETRY_ORIGIN_SECRET} respond @outsider 403 # ... } ``` > [!NOTE] > The Nginx and Caddy examples compare the secret with a plain string match rather than in constant time. They are a reasonable starting point, but checking the secret in your application, as the Laravel middleware does, is the stronger option. ## Testing the Lock Once your origin is locked, request it directly. Without the header, it should respond with a `403`: ```shell curl -sI https://www-origin.example.com/ ``` With the header, the request should be served as usual: ```shell curl -sI https://www-origin.example.com/ \ -H "X-NiceTry-Origin-Secret: $NICETRY_ORIGIN_SECRET" ``` If you have connected an agent to NiceTryBot's [MCP server](https://docs.nicetrybot.com/docs/0.x/mcp.md), the `check-domain` tool runs the same test for you. It requests your origin's `/` path twice, once without the secret and once with it, and reports the origin as locked when the first request is refused with a `4xx` response and the second succeeds. ## Rotating the Secret If you believe a domain's secret has leaked, you may issue a new one using the **Rotate secret** button on the domain's page. > [!WARNING] > Rotation takes effect immediately. The edge starts sending the new secret right away and the old secret stops working at the same moment, so requests will fail at your origin until it accepts the new value. To rotate without any failed requests, briefly let your origin accept both the old and the new secret:
1. Copy the current secret, then click **Rotate secret** and copy the new one. 2. Deploy your origin so that it accepts either value. 3. Once the deployment is live, deploy again with only the new secret.
Since step 2 can't happen before the rotation, expect a short window of refused requests between rotating and deploying. If that is not acceptable, deploy during a quiet period. ## Additional Hardening The origin secret is the primary way to lock an origin, and on its own it is enough. If your hosting provider offers a firewall, you may additionally allow inbound traffic to your origin only from your NiceTryBot server. Note that the address your server uses for **outbound** connections may differ from the addresses in `NICETRY_EDGE_ADDRESSES`, so confirm it before you write the rule. It is also a good idea to give your origin a hostname nobody would guess from your public one, and to keep it out of public links, sitemaps, and certificates you share with other hostnames. --- # Upgrading Versions - [Introduction](#introduction) - [Versioning Scheme](#versioning-scheme) - [Documentation Versions](#documentation-versions) - [Updating NiceTryBot](#updating-nicetrybot) - [Updating Using AI](#updating-using-ai) - [What an Update Does](#what-an-update-does) - [Updating to a Specific Release](#updating-to-a-specific-release) - [Rolling Back](#rolling-back) - [Update Notices](#update-notices) - [Updating Manually](#updating-manually) - [Docker Compose](#docker-compose) - [Without Docker](#without-docker) ## Introduction NiceTryBot is released often, and updating is a single command. Before every update, NiceTryBot backs up your database, and if the new release doesn't come up healthy, it puts the previous release back on its own. This guide explains how releases are numbered, how to update, and how to roll back if you need to. ## Versioning Scheme NiceTryBot releases are tagged on [GitHub](https://github.com/NiceTryBot/nicetrybot/releases) as `vMAJOR.MINOR.PATCH`. The current release is `v0.1.0`. Each release's notes are published with its tag on GitHub, and each release attaches the files the installer uses: `compose.yml`, `compose.addons.yml`, `install.sh`, and an example environment file. NiceTryBot is in its `0.x` series, so any release may change how things work while the product settles. Always read the release notes before you update. When a release needs something from you beyond running `nicetrybot update`, its notes will say so. Docker images are published to `ghcr.io/nicetrybot` and tagged with the bare version number, such as `0.1.0`, as well as `latest`. Pre-releases, such as `v0.2.0-beta.1`, never move the `latest` tag. > [!NOTE] > Marketplace add-ons are versioned separately from NiceTryBot itself. Updating NiceTryBot also rebuilds your add-ons with the newest versions your licence allows. ### Documentation Versions This documentation is versioned alongside NiceTryBot. The version switcher at the top of every page lists each documented series, and the docs for `0.x` cover every `0.x` release. When a new major series is released, its documentation will appear in the switcher, and the previous series' documentation will remain available. ## Updating NiceTryBot To update NiceTryBot to the newest release, SSH into your server and run: ```shell nicetrybot update ``` Running the installer again on a server where NiceTryBot is already running does exactly the same thing. ### Updating Using AI If an agent has access to your server, it can run the update and read the release notes for you. You may give it the following prompt: ```text Read https://docs.nicetrybot.com/docs/0.x/upgrade.md. Then check which NiceTryBot release my server runs with `nicetrybot version`, read the release notes for every release since then on GitHub, and tell me about anything I need to do before I update. Don't run the update until I say so. ``` ### What an Update Does When you run `nicetrybot update`, NiceTryBot will:
1. Tell you which release it is updating from, and which it is updating to. 2. Back up the bundled Postgres database to `/opt/nicetrybot/backups`, keeping the newest three backups. 3. Download the new release's `compose.yml`, keep your `.env` file, and pin `NICETRY_VERSION` to the new release. 4. Pull the new images, rebuild your add-ons into the app, and restart whatever changed. The app runs any new database migrations as it starts. 5. Check that the app came up healthy. If it didn't, NiceTryBot puts the previous release back, including the database if migrations ran.
Docker's output is written to `/opt/nicetrybot/.updates/update.log`, which is the first place to look if an update doesn't go as planned. > [!WARNING] > Backups only cover the bundled Postgres service. If you use a managed database, take a snapshot with your provider before updating. You may skip NiceTryBot's own backup with `--no-backup`. ### Updating to a Specific Release To update to, or go back to, a specific release, pass the `--version` option: ```shell nicetrybot update --version=v0.1.0 ``` You may check which release is running, and which is the newest, at any time: ```shell nicetrybot version ``` ## Rolling Back If you are unhappy with an update, you may undo it: ```shell nicetrybot rollback ``` Rolling back restores the previous release, its `compose.yml`, your add-ons, and the database backup taken before the update. Because the database is restored, any changes made in the control panel since the update are lost, so the command asks for confirmation first. Pass `--yes` to skip the question. > [!NOTE] > A managed database is never backed up or restored by `nicetrybot rollback`. Restore it from your provider's snapshot instead. ## Update Notices Once a day, NiceTryBot checks whether a newer release is available. When one is, administrators will see a notice in the control panel with a link to what's new. NiceTryBot never updates itself: the notice simply lets you know it's time to run `nicetrybot update`. If you would rather NiceTryBot didn't check, set the following in your `.env` file and run `nicetrybot apply`: ```ini NICETRY_UPDATE_CHECK=false ``` ## Updating Manually ### Docker Compose If you manage the stack with Docker Compose yourself, set `NICETRY_VERSION` in your `.env` file to the release you want, then pull the new images and restart the stack: ```shell cd /opt/nicetrybot docker compose pull docker compose up -d --remove-orphans ``` If you have add-ons installed, rebuild the app image as well: ```shell docker pull ghcr.io/nicetrybot/app:latest docker compose build app docker compose up -d --remove-orphans ``` Remember to back up your database before updating by hand, since only `nicetrybot update` does it for you. ### Without Docker If you run the control panel on a plain PHP host, update it like any other Laravel application: ```shell git checkout composer.json composer.lock git pull composer install --no-dev --optimize-autoloader php artisan addons:install php artisan migrate --force npm ci && npm run build php artisan optimize ``` Your edge server is updated separately, by pulling the new `ghcr.io/nicetrybot/edge` image. --- # Core Concepts - [Introduction](#introduction) - [The Edge](#the-edge) - [How Requests Flow](#how-requests-flow) - [Clients and Fingerprints](#clients-and-fingerprints) - [Activity](#activity) - [Request Actions](#request-actions) - [Analytics and 404s](#analytics-and-404s) - [Rules](#rules) - [Writing Rules](#writing-rules) - [Counting Over Time](#counting-over-time) - [Available Fields](#available-fields) - [Testing Rules](#testing-rules) - [Tags and Bans](#tags-and-bans) - [The Bot Gym](#the-bot-gym) - [Routines](#routines) - [Obstacles](#obstacles) - [Exits](#exits) - [Connecting an Agent](#connecting-an-agent) - [Searching in Plain English](#searching-in-plain-english) ## Introduction NiceTryBot has a small number of moving parts, and once you understand how they fit together, the rest of the control panel will feel familiar. In short: the **edge** sees every request, **Activity** shows you what it saw, **rules** decide who is up to no good, **tags and bans** record those decisions, and the **bot gym** gives the guilty something to do. ## The Edge The edge is NiceTryBot's front door. It is a build of [Caddy](https://caddyserver.com) that terminates TLS for every domain you protect, records each request, and decides where it should go. Along with the usual details, such as the path, status, and user agent, the edge records how each visitor connected: its JA3 and JA4 TLS fingerprints, a JA4H fingerprint of its headers, its client hints, and its `Accept-Language` header. It also looks up the visitor's network and approximate location using local databases, so nothing is sent to a third party. The edge also handles a few chores on its own. It tags requests for paths that only scanners ask for, such as `.env`, `.git`, and `wp-admin`. It introduces itself as `nginx`, with nginx-style error pages, so nothing a visitor sees names NiceTryBot. And WebSockets pass straight through. ## How Requests Flow When a request arrives, the edge checks it against your rules and the domain's [protection mode](https://docs.nicetrybot.com/docs/0.x/domains.md#protection-modes):
- **Humans** always continue to your origin. - **Tagged visitors** on a domain in **Observe** mode continue to your origin as well, but are marked in Activity so you can see what NiceTryBot would have done. - **Tagged visitors** on a domain in **Enforce** mode are sent to the bot gym or blocked.
If the edge can't reach the control panel for any reason, it fails open and sends everyone to your origin. ### Clients and Fingerprints NiceTryBot thinks about visitors as **clients**. A client is an IP address together with its JA4 TLS fingerprint. Many people share a browser build, so many people share a JA4 fingerprint, but the fingerprint describes the software that actually connected, which is very hard for a script to fake. When a client's details don't add up, NiceTryBot gives it a trait: | Trait | Description | | --- | --- | | `spoofed` | Its user agent or client hints disagree with how it connected, such as a user agent claiming to be Chrome from a client whose TLS handshake isn't Chrome's. | | `outdated` | Its browser is several versions behind the current release. | | `corporate` | It appears to be behind a corporate security gateway. | | `hopped` | It took over another IP address's visit. | NiceTryBot also refreshes the published IP ranges of well-known crawlers every day, so a real crawler can be told apart from an impostor borrowing its name. ## Activity **Activity** is a live, filterable log of every request that reached the edge, what it asked for, and what NiceTryBot did about it. You may search it using the same `WHERE` syntax as [rules](#writing-rules), save searches you use often, and follow new requests as they arrive. You may also label a client. Labels give a name to an IP address, fingerprint, user agent, or set of client hints, such as "office" or "uptime monitor", and appear everywhere that client does. ### Request Actions Each request in Activity shows the action the edge took: | Action | Description | | --- | --- | | **Passed** | The request went to your origin. | | **Observed** | The request was tagged, but went to your origin because its domain is in **Observe** mode. | | **In the gym** | The request was sent to the bot gym. | | **Blocked** | The request was refused. | ### Analytics and 404s **Analytics** shows who is knocking: the networks, countries, user agents, and TLS fingerprints behind your traffic, along with a flow diagram of where requests went and a live map. **404s** shows which domains get the most "not found" responses, what was asked for, and whether it was a person or a probe. Scanners tend to ask for the same handful of paths, so this page is often the quickest way to spot them. ## Rules Rules decide which clients deserve attention. They are written in NiceTryBot's rule language, which reads like SQL but isn't SQL. Every rule either **tags** or **bans** the clients it matches, for as long as you specify. Enabled rules run every minute over newly arrived requests. Single-request rules are also judged at the edge as requests arrive. You will find a gallery of ready-made rules on the **Rules** page, and the free [Rule packs](https://docs.nicetrybot.com/docs/0.x/addons.md#first-party-add-ons) add-on includes more. ### Writing Rules A rule names an action, the subjects it applies to, a duration, and the requests it matches. For example, the following rule bans any IP address and fingerprint that goes fishing for secrets, admin panels, or exploits: ```rule -- Anyone fishing for secrets, admin panels or exploits. BAN ip, ja4 FOR 30 DAYS WHERE category = 'probe' ``` A rule may tag clients instead of banning them. Tags are named using `AS`, and are a great way to mark clients you would like to send to the gym rather than refuse: ```rule -- Claims to be Chrome or Firefox, but its TLS offers no ALPN: a script. TAG ip, ja4 AS 'ua-tls-mismatch' FOR 1 DAY WHERE (ua LIKE '%Chrome/%' OR ua LIKE '%Firefox/%') AND ja4.alpn = '00' ``` Keywords are case-insensitive. Strings use single quotes, `LIKE` ignores case and supports the `%` and `_` wildcards, and `--` starts a comment. `AND` binds more tightly than `OR`, so use parentheses when combining them. Durations may use `SECOND`, `MINUTE`, `HOUR`, `DAY`, or `WEEK`, or `FOREVER`. ### Counting Over Time Using `WITHIN` and `HAVING`, a rule may count a client's requests over a window of up to 24 hours. For example, the following rule bans an IP address that collects 404 responses across several of your sites: ```rule -- One IP collecting 404s across several of your sites. BAN ip FOR 1 HOUR WHERE status = 404 WITHIN 10 MINUTES HAVING COUNT(*) >= 2 AND COUNT(DISTINCT host) >= 2 ``` Similarly, this rule bans a flood of requests from a cloud or hosting network: ```rule -- A flood of requests from a cloud or hosting network. BAN ip, ja4 FOR 6 HOURS WHERE network.kind = 'hosting' WITHIN 5 MINUTES HAVING COUNT(*) >= 60 ``` You may also end a verdict early using `UNTIL`, followed by a condition. The complete grammar of the rule language is as follows: ```text script := action subjects [AS 'tag'] FOR duration [WHERE expr] [WITHIN span HAVING having] [UNTIL expr] action := BAN | TAG subjects := subject {, subject} subject := ip | ja4 duration := number unit | FOREVER span := number unit unit := SECOND | MINUTE | HOUR | DAY | WEEK, or the plural expr := term {OR term} term := factor {AND factor} factor := NOT factor | ( expr ) | field predicate predicate := op literal | [NOT] LIKE 'pattern' | [NOT] IN (literal, …) | IS [NOT] NULL op := = | != | < | <= | > | >= having := hterm {OR hterm} hterm := hfactor {AND hfactor} hfactor := NOT hfactor | ( having ) | COUNT ( * | DISTINCT field ) [FILTER ( WHERE expr )] op number ``` ### Available Fields Rules and Activity searches may use the following fields. Add-ons may add more. | Field | Description | | --- | --- | | `ip` | The client's IP address. Accepts CIDR ranges, such as `'66.249.64.0/19'`. | | `ja4` | The client's JA4 TLS fingerprint. | | `ja4.alpn` | The ALPN the client offered. `'00'` means none: a script, not a browser. | | `ja3` | The client's JA3 TLS fingerprint. | | `ja4h` | The client's header fingerprint. | | `ua` | The user agent. | | `hints` | The client hints, such as `LIKE '%HeadlessChrome%'`. | | `client` | The client's trait: `'spoofed'`, `'outdated'`, `'corporate'`, or `'hopped'`. | | `lang` | The `Accept-Language` header. A missing value is a tell-tale sign of a script. | | `host` | The domain requested. | | `path` | The path requested. | | `category` | The path's category: a family like `'probe'`, or a slug like `'probe/creds'`. | | `status` | The response status, such as `404`. | | `method` | The request method, such as `GET` or `POST`. | | `duration` | The response time, in milliseconds. | | `action` | The edge's action: `'passed'`, `'observed'`, `'diverted'`, or `'blocked'`. | | `tag` | The edge's tag, such as `'probe'`. | | `asn` | The network's autonomous system number. | | `network` | The network's name. | | `network.kind` | The kind of network. `'hosting'` covers cloud and datacenter networks. | | `country` | The country, as an ISO 3166 code such as `'NL'`. | | `region` | The region. | | `city` | The city. | ### Testing Rules Every rule is tested against your real traffic before it runs. When you write or edit a rule, NiceTryBot backtests it against past requests and shows you exactly which clients it would have caught. If the list includes your own office, you'll know before anyone gets banned. ## Tags and Bans When a rule matches a client, it reaches a **verdict**: a tag or a ban, along with when it first and last matched and when it expires. The **Tags & bans** page lists every client your rules have marked. You may lift a verdict at any time to let the client go immediately, although the rule may tag it again if it keeps matching. Verdicts on their own don't block anyone. A ban is enforced on domains in **Enforce** mode, and a tag is something you can act on, most often by sending tagged clients to the bot gym. Ended verdicts are kept for 30 days. ## The Bot Gym Instead of refusing a suspicious visitor outright, you may send it to the bot gym. The gym puts visitors through obstacles that cost a bot CPU time and patience, but only cost a person a moment. The gym is served in place, at the address the visitor asked for, so its address bar never changes. The gym only holds visitors on domains in **Enforce** mode. ### Routines A **routine** describes who goes to the gym and what they do there. Each routine has entry conditions, an ordered list of obstacles, an exit, and a pass time. Entry conditions may match a visitor's network, country, browser, user agent, IP address, TLS fingerprint, domain, path category, path, edge tag, or a rule's verdict. When a visitor matches several routines, their obstacles stack into one pipeline and the strictest exit wins. Once a visitor completes the gym, they receive a pass that lasts for the routine's pass time, which may be anywhere from one minute to one week. You may also send a rule's clients straight to the gym using the **Send its clients to the gym** option when editing a rule. ### Obstacles NiceTryBot includes the following obstacles: | Obstacle | Description | | --- | --- | | **Proof of work** | The browser solves a SHA-256 puzzle in the background. People just wait a moment. | | **Waiting room** | A queue that keeps the visitor's CPU busy, one proof of work after another, for as long as you set. | | **Type the code** | A short code drawn as a noisy image. The visitor reads it and types it back. | | **Browser check** | A silent look for automation, such as webdriver flags, headless windows, and a missing Chrome runtime. | [Add-ons](https://docs.nicetrybot.com/docs/0.x/addons.md) may add more obstacles. For example, the Captchas add-on adds a set of puzzles people solve in a second and bots get lost in. ### Exits After the last obstacle, a routine's exit decides what happens next: | Exit | Description | | --- | --- | | **Continue to their page** | The visitor gets a pass and lands where they were going. | | **Show a not-found page** | The gym answers with a `404` itself. Your origin never sees the request. | | **Turn them away** | The gym answers with a `403`. | ## Connecting an Agent NiceTryBot's control panel includes an [MCP](https://modelcontextprotocol.io) server, so agents like Claude Code, Codex, and Cursor can read your traffic, add domains, write rules, and build gym routines on your behalf. The same tools are available from your terminal using the [`nicetrybot` command](https://docs.nicetrybot.com/docs/0.x/cli.md). The [MCP Server](https://docs.nicetrybot.com/docs/0.x/mcp.md) documentation covers API tokens, connecting an agent, and every tool the server offers. ### Searching in Plain English If you add an Anthropic API key in **Settings → Artificial Intelligence**, you may search Activity in plain English, such as "phones from the Netherlands that hit the login page yesterday". This feature is off until you add a key. It is the only feature that sends anything outside of your servers: your question, the search or rule you are editing, and your team's hostnames are sent to Anthropic using your own key. --- # NiceTryBot CLI - [Introduction](#introduction) - [Installing the Command](#installing-the-command) - [On Your Server](#on-your-server) - [On Your Laptop](#on-your-laptop) - [Updating the Command](#updating-the-command) - [Managing Your Server](#managing-your-server) - [Working With the Control Panel](#working-with-the-control-panel) - [Domains](#domains) - [Rules and Gyms](#rules-and-gyms) - [Calling Any Tool](#calling-any-tool) - [Where Commands Run](#where-commands-run) - [Options](#options) - [Scripting](#scripting) - [Using the Command With AI](#using-the-command-with-ai) ## Introduction NiceTryBot ships with a `nicetrybot` command that looks after your installation and works with your control panel, on your server or from your laptop. It does two jobs:
- **On your server**, it looks after the installation itself: updating, rolling back, installing add-ons, checking health, and reading logs. - **On your server or your laptop**, it works with the control panel: adding and removing domains, changing protection modes, listing rules and gyms, and anything else an AI agent can do through the [MCP server](https://docs.nicetrybot.com/docs/0.x/mcp.md).
The control panel commands are calls to the same tools the MCP server gives agents, so the command, the control panel, and your agent all follow the same rules. A domain the command adds is checked exactly like one added in the control panel. ## Installing the Command ### On Your Server The [installer](https://docs.nicetrybot.com/docs/0.x/installation.md) puts `nicetrybot` in `/usr/local/bin`, and every `nicetrybot update` replaces it with the copy that ships with the new release. There's nothing else to install. > [!NOTE] > The control panel commands and [remotes](https://docs.nicetrybot.com/docs/0.x/remotes.md) came after release `0.5.0`. If `nicetrybot domains` says it's an unknown command, run `nicetrybot update`. ### On Your Laptop The same command runs on macOS and Linux, where it works with your control panel over HTTPS. It needs `curl` and `jq`. macOS 15 and newer ship with both; on older versions, install `jq` using `brew install jq`. Download the command from the newest release and make it executable: ```shell mkdir -p ~/.local/bin curl -fsSL https://github.com/NiceTryBot/nicetrybot/releases/latest/download/nicetrybot \ -o ~/.local/bin/nicetrybot chmod +x ~/.local/bin/nicetrybot ``` Make sure `~/.local/bin` is on your `PATH`, then connect the command to your control panel by [adding a remote](https://docs.nicetrybot.com/docs/0.x/remotes.md#adding-a-remote): ```shell nicetrybot remote add panel.example.com ``` ### Updating the Command To update the command on your laptop to the newest release: ```shell nicetrybot cli update ``` It downloads the release's copy, checks that it's a complete, working script, and only then swaps it in place of the old one. If you installed the command somewhere only `root` may write, run it with `sudo`. To go to a specific release, pass `--version=vX.Y.Z`. `nicetrybot self-update` does the same thing. To see which release your copy came from, and whether a newer one is out: ```shell nicetrybot cli version ``` On your server, there's no need: `nicetrybot update` updates NiceTryBot and the command together, so they always come from the same release. ## Managing Your Server These commands only ever work on the server they run on. Run them as `root`, or with `sudo`: | Command | Description | | --- | --- | | `nicetrybot update` | Update to the newest release, backing up the database first. See [Upgrading Versions](https://docs.nicetrybot.com/docs/0.x/upgrade.md). | | `nicetrybot update --version=vX.Y.Z` | Update to, or go back to, a specific release. | | `nicetrybot rollback` | Put back the release and database from before the last update. | | `nicetrybot addons` | List the marketplace add-ons built into the app. See [Installing Add-ons](https://docs.nicetrybot.com/docs/0.x/addons.md). | | `nicetrybot addons add NAME…` | Build add-ons in. `NAME=KEY` also enters an add-on's licence key. | | `nicetrybot addons remove NAME…` | Take add-ons out. | | `nicetrybot addons keys` | Enter the licence keys your paid add-ons still need. | | `nicetrybot apply` | Start the stack again with what you changed in `.env`. | | `nicetrybot doctor` | Check the server, the containers, the control panel, and the app, with advice. | | `nicetrybot status` | The release, the containers, and the add-ons. | | `nicetrybot version` | The release running, and the newest one. | | `nicetrybot cli update` | Update just this command, to the newest release. | | `nicetrybot logs [SERVICE]` | Follow the logs of `app`, `worker`, `scheduler`, `edge`, `vector`, `postgres`, or `valkey`. | | `nicetrybot artisan COMMAND…` | Run an Artisan command in the app, such as `admin:grant you@example.com`. | The stack lives in `/opt/nicetrybot`, unless the installer was told to put it somewhere else, in which case `/etc/default/nicetrybot` says where. ## Working With the Control Panel The following commands work on your server or, from your laptop, on a [remote](https://docs.nicetrybot.com/docs/0.x/remotes.md). To check who the commands act as, run `whoami`: ```shell nicetrybot whoami ``` It prints the team, the user, what the commands are allowed to do, and when the token expires. With a token for all of your teams, `nicetrybot teams` lists them and `nicetrybot team use SLUG` picks the one the commands work on. See [Working With Several Teams](https://docs.nicetrybot.com/docs/0.x/remotes.md#working-with-several-teams). ### Domains To list your team's domains: ```shell nicetrybot domains ``` ```text HOSTNAME MODE STATUS DNS ORIGIN docs.example.com observe pending_verification - https://docs-origin.example.com shop.example.com enforce active edge https://shop-origin.example.com ``` To [add a domain](https://docs.nicetrybot.com/docs/0.x/domains.md), give the hostname visitors type and the origin it should forward to. An origin without a scheme is assumed to use `https://`: ```shell nicetrybot domains add docs.example.com docs-origin.example.com ``` Adding a domain prints the three steps that finish setup, in order: the ownership TXT record to add, the `X-NiceTry-Origin-Secret` header to [lock your origin](https://docs.nicetrybot.com/docs/0.x/origins.md) to so nobody can reach your site around the edge, and the DNS records that point at the edge. As in the control panel, nothing changes for your visitors until the domain is verified and its DNS points at the edge. As you work through them, `nicetrybot domains check` shows where the domain stands. It looks up the TXT record, and verifies the domain as soon as the record appears. It then asks the origin whether it refuses requests without the header, and checks where public DNS points: ```text ✓ Ownership: Found the TXT record: the domain is verified. ✗ Origin locked to the edge: The origin served a request without the secret header (200), so anyone can go around the edge. The header, and how to lock the origin: nicetrybot domains secret docs.example.com ✗ DNS at the edge: No records found for docs.example.com yet. CNAME docs.example.com edge.example.net Next: lock the origin (nicetrybot domains secret docs.example.com), then check again. ``` You may pass the same settings as the **Add domain** modal: | Option | Description | | --- | --- | | `--www` | Serve `www.` in front of the domain as the same site. | | `--host-header=rewrite` | Send the origin its own `Host` header rather than the visitor's. Use this for Laravel Vapor, Vercel, Netlify, Heroku, Render, and CloudFront. See [Host Headers](https://docs.nicetrybot.com/docs/0.x/domains.md#host-headers). | | `--origin-host-header=HOST` | With `rewrite`, send this `Host` header instead of the origin's own. | | `--forwarded-host-header=NAME` | A header that carries the visitor's hostname to the origin, such as `X-Forwarded-Host`. Separate several with commas. | For example, to put a site hosted on a platform that routes by `Host` header behind NiceTryBot: ```shell nicetrybot domains add docs.example.com docs-edge.example.com \ --host-header=rewrite \ --forwarded-host-header=X-Forwarded-Host ``` [Hosting Platforms](https://docs.nicetrybot.com/docs/0.x/domains.md#hosting-platforms) has the exact command for Laravel Vapor, CloudFront, Vercel, and more. `nicetrybot add domain` and `nicetrybot domain add` work too, as do `--rewrite-to-origin-host` and `--fwd-host-header` in place of the longer options. The rest of a domain's life happens one command at a time: ```shell nicetrybot domains check docs.example.com # Ownership, the origin lock and DNS, and the next step nicetrybot domains verify docs.example.com # Check only the ownership TXT record nicetrybot domains secret docs.example.com # The origin secret header, and how to lock the origin to it nicetrybot domains mode docs.example.com enforce # off, observe or enforce nicetrybot domains remove docs.example.com # Take the domain out of NiceTryBot ``` > [!WARNING] > Removing a domain deletes its request history, and the edge stops serving it straight away. Point the domain's DNS back at your origin first. The command asks before it removes anything; pass `--yes` to skip the question. ### Rules and Gyms To list your team's [rules](https://docs.nicetrybot.com/docs/0.x/concepts.md#rules) and [gyms](https://docs.nicetrybot.com/docs/0.x/concepts.md#the-bot-gym), or to delete one by its ID: ```shell nicetrybot rules nicetrybot rules 12 # Just rule 12 nicetrybot rules remove 12 nicetrybot gyms nicetrybot gyms remove 4 ``` Deleting asks for confirmation first, unless you pass `--yes`. To create or change rules and gyms, use the control panel, an agent, or [any tool](#calling-any-tool). ### Calling Any Tool Every tool the [MCP server](https://docs.nicetrybot.com/docs/0.x/mcp.md#available-tools) offers is available from the command line, including those your add-ons add. To list the tools your token may use: ```shell nicetrybot tools ``` Then call one with `call`, passing its arguments as `key=value` pairs: ```shell nicetrybot call query-traffic group_by=country period=7d nicetrybot call backtest-rule source="TAG ip, ja4 AS 'dutch' FOR 1 DAY WHERE country = 'NL'" days=3 ``` A value that reads as JSON, such as a number, `true`, or a list, is sent as that; anything else is sent as a string. You may also pass the arguments as a single JSON object: ```shell nicetrybot call backtest-rule '{"id": 12, "days": 3}' ``` Add `--json` to see a tool's whole answer exactly as it sent it. ## Where Commands Run The control panel commands run in the first of these places that applies:
1. On this server's stack, when you pass `--local`. 2. On the remote named by `--remote=NAME`, or by the `NICETRYBOT_REMOTE` environment variable. 3. On your [current remote](https://docs.nicetrybot.com/docs/0.x/remotes.md#switching-remotes). 4. On this server's stack, when NiceTryBot is installed here.
On a terminal, the command prints where it's working before it does anything, such as `→ prod (https://panel.example.com), team Acme`, so a change never lands somewhere you didn't expect. On your server, the command acts as the owner of the first administrator's current team, with every ability. Being able to run commands as `root` on the server is all the permission it needs. To work on another team, pass its slug: ```shell nicetrybot domains --team=acme ``` ## Options | Option | Description | | --- | --- | | `--yes` | Don't ask before a rollback, or before removing a domain, rule, or gym. | | `--json` | Print the control panel's answer as JSON. | | `--remote=NAME` | Run a control panel command on that remote, rather than the current one. | | `--local` | Run a control panel command on this server's stack, even when you have a current remote. | | `--team=SLUG` | The team to work on. On a remote whose token works on all of your teams, for this command only. On your server, the first administrator's current team when left out. | | `--token=TOKEN` | With `remote add` or `remote auth`: the API token. With `addons add`: your Composer token. When you leave it out, the command asks for the token without showing it, which also keeps it out of your shell's history. | | `--no-backup` | Update without backing up the database first. `rollback` can't restore the database afterwards. | | `--version=vX.Y.Z` | With `update`: the release to update to. | To see every command, run `nicetrybot help`. ## Scripting Every control panel command accepts `--json`, which prints the tool's answer as JSON for `jq` or your own scripts: ```shell nicetrybot domains --json | jq -r '.domains[] | select(.protection_mode == "observe") | .hostname' ``` The command exits with a non-zero status when anything goes wrong, such as a validation error, a domain that doesn't exist, or a token that no longer works, and prints the reason to standard error. ## Using the Command With AI The command is a good fit for agents working in a terminal, since every answer explains itself and `--json` is there when they want structure. You may give your agent a prompt like the following: ```text Read https://docs.nicetrybot.com/docs/0.x/cli.md. Use the nicetrybot command to list my domains and tell me which ones are still in observe mode. Don't add, change, or remove anything until I say so. ``` If your agent supports MCP, you may connect it to the [MCP server](https://docs.nicetrybot.com/docs/0.x/mcp.md) directly instead. It offers the same tools. --- # Remotes - [Introduction](#introduction) - [Adding a Remote](#adding-a-remote) - [Creating a Token](#creating-a-token) - [Saving the Remote](#saving-the-remote) - [Listing Remotes](#listing-remotes) - [Switching Remotes](#switching-remotes) - [Working With Several Teams](#working-with-several-teams) - [Renewing a Token](#renewing-a-token) - [Removing a Remote](#removing-a-remote) - [How Remotes Are Stored](#how-remotes-are-stored) - [Troubleshooting](#troubleshooting) ## Introduction A remote is a NiceTryBot control panel that the [`nicetrybot` command](https://docs.nicetrybot.com/docs/0.x/cli.md) on your laptop works with. Once you've added one, the control panel commands, such as `nicetrybot domains add`, run there instead of on the machine you're typing on. Remotes work a lot like Git's: you may save as many as you like, such as one for production and one for staging, give each a short name, and switch between them. The command talks to each remote's [MCP server](https://docs.nicetrybot.com/docs/0.x/mcp.md) over HTTPS, using an API token you create in that control panel. ## Adding a Remote ### Creating a Token Every remote needs an API token. In the control panel, an administrator opens **Admin → API tokens** and creates one:
- Give it a name you'll recognize later, such as `Laptop`. - Choose the team the command should work on, or **All my teams**, so one token works on every team you're on. See [Working With Several Teams](#working-with-several-teams). - Tick only the [abilities](https://docs.nicetrybot.com/docs/0.x/mcp.md#api-tokens) you need. Every token may read; adding and removing domains needs **Add and change domains**. - Choose how long it lasts, from one hour to 90 days.
The token is shown once. The page also shows the exact `nicetrybot remote add` command for your control panel, ready to copy. ### Saving the Remote Pass your control panel's address to `remote add`. When you leave the scheme out, `https://` is assumed: ```shell nicetrybot remote add panel.example.com ``` The command asks for the token without showing it as you paste, checks it with the control panel, saves the remote, and makes it your current one: ```text Make a token at https://panel.example.com/admin/tokens (pick its abilities and how long it lasts). API token: ✓ Saved panel.example.com (https://panel.example.com), team Acme, expires in 30 days. It's the current remote. ``` By default, a remote is named after its host. To give it a shorter name, put the name first: ```shell nicetrybot remote add prod panel.example.com nicetrybot remote add staging staging-panel.example.com ``` > [!NOTE] > You may pass the token with `--token=ntb_…` instead, such as in a script, but letting the command ask for it keeps the token out of your shell's history. ## Listing Remotes To list your saved remotes: ```shell nicetrybot remote list ``` ```text NAME URL TEAM TOKEN * prod https://panel.example.com Acme expires in 29 days staging https://staging-panel.example.com Acme expired: nicetrybot remote auth ``` The `*` marks the current remote. To see just the current one, run `nicetrybot remote` on its own. ## Switching Remotes To make another remote the current one: ```shell nicetrybot remote use staging ``` Running `nicetrybot remote` with a remote's name, or a control panel's address, does the same thing. If no saved remote has that address, the command adds one: ```shell nicetrybot remote prod nicetrybot remote staging-panel.example.com ``` To run a single command somewhere else without switching, use `--remote`: ```shell nicetrybot domains --remote=staging ``` If you run the command on a NiceTryBot server that also has a current remote, `--local` runs a command on the server's own stack instead. To stop using remotes on that machine altogether, run: ```shell nicetrybot remote use --local ``` The full order in which the command decides where to run is described in [Where Commands Run](https://docs.nicetrybot.com/docs/0.x/cli.md#where-commands-run). ## Working With Several Teams A token made for **All my teams** works on every team you're on, so one remote can look after all of them. When you add the remote, the command lists your teams and asks you to pick one: ```text ✓ Saved prod (https://panel.example.com), no team picked yet (token for all teams), expires in 30 days. It's the current remote. Its token works on all your teams (acme, globex). Pick the one to work on: nicetrybot team use SLUG ``` To list the teams, with a `*` beside the one in use, and to switch: ```shell nicetrybot teams nicetrybot team use globex ``` ```text SLUG TEAM * acme Acme globex Globex ``` The remote remembers the team, including when you renew its token. Run `nicetrybot team` to see which team you're on. To run a single command on another team without switching, pass `--team`: ```shell nicetrybot domains --team=acme ``` On every team, the token can only do what you can do there. A token with **Add and change domains** can't add domains to a team where you're a plain member. Teams you join later are included straight away, and teams you leave are gone straight away. A token for one team keeps working on that team only. Naming another team gives an error that says which team the token is for. ## Renewing a Token API tokens always expire. When a remote's token has expired, create a new one in **Admin → API tokens** and give it to the remote. A token for all teams stays on the team you picked: ```shell nicetrybot remote auth prod ``` Leave the name out to renew the current remote's token. ## Removing a Remote To forget a remote: ```shell nicetrybot remote remove staging ``` This only removes the remote from your laptop. Its token keeps working until it expires, so if you're done with it, delete it in **Admin → API tokens** as well. ## How Remotes Are Stored Each remote is a small file in `~/.config/nicetrybot/remotes`, holding the control panel's address, its token, its team (and whether the token works on all of them), and when the token expires. The directory and its files are readable only by you. If you set `XDG_CONFIG_HOME`, they live under that instead, and `NICETRYBOT_CONFIG_DIR` may point the command somewhere else entirely. The token is sent only to the remote's own `/mcp` path, in an `Authorization` header. The command hands it to `curl` on standard input rather than on the command line, so other users on your machine can't see it in the process list. > [!WARNING] > Treat your remotes directory like a password manager's file. Anyone who can read it may act as you on the tokens' teams until they expire. Prefer short-lived tokens with only the abilities you use. ## Troubleshooting **The command says the remote "doesn't take that token".** The token has expired or been deleted, its user is no longer an administrator or no longer on the team, or there's no NiceTryBot control panel at that address. Without a working token, the MCP server answers exactly as if it didn't exist, so the command can't tell which. Create a new token and run `nicetrybot remote auth`. **The command says the remote "has no" tool.** The control panel runs an older release that doesn't have that tool yet, or your token isn't allowed to use it. `nicetrybot tools` lists what your token may use. Update the control panel with `nicetrybot update`, or create a token with the ability you need. **The command asks for `jq`.** Install it with `brew install jq` on macOS, or `apt-get install jq` on Linux. --- # MCP Server - [Introduction](#introduction) - [API Tokens](#api-tokens) - [Abilities](#abilities) - [Tokens for All Teams](#tokens-for-all-teams) - [When Tokens Stop Working](#when-tokens-stop-working) - [Connecting an Agent](#connecting-an-agent) - [Claude Code](#claude-code) - [Other Agents](#other-agents) - [NiceTryBot CLI](#nicetrybot-cli) - [Available Tools](#available-tools) - [Calling the Server Yourself](#calling-the-server-yourself) - [Security](#security) - [Adding Tools From Add-ons](#adding-tools-from-add-ons) ## Introduction NiceTryBot's control panel includes an [MCP](https://modelcontextprotocol.io) server, so agents like Claude Code, Codex, and Cursor can read your traffic, add domains, write rules, and build gym routines for you. The server lives at the `/mcp` path of your control panel, such as `https://panel.example.com/mcp`. The MCP server is also what the [`nicetrybot` command](https://docs.nicetrybot.com/docs/0.x/cli.md) uses. Whether a domain is added by an agent, by the command, or in the control panel, it goes through the same checks. ## API Tokens Every request to the MCP server carries an API token. Tokens are only ever created by hand: an administrator opens **Admin → API tokens** and chooses:
- A **name**, so you can tell your tokens apart. - The **team** it works on. A token acts as you, on that one team only, or on **All my teams**. - **What it may do**, from the abilities below. - When it **expires**: after 1 hour, 4 hours, 1 day, 1 week, 30 days, or 90 days. One hour is the default, and no token lasts forever.
The token is shown once, alongside the command that connects Claude Code, the `mcp.json` entry other agents read, and the `nicetrybot remote add` command. Only a hash of the token is stored, so a lost token can't be shown again: create another one instead. ### Abilities Every token may read. Tick only the abilities a job needs: | Ability | Description | | --- | --- | | Read traffic, domains, rules and gyms | Always granted. | | Add and change domains | Add, verify, configure, and remove domains. | | Create and change rules | Write, backtest, and delete rules. | | Create and change gyms | Build, edit, and delete gym routines. | | Lock down and release domains | Use the [Lockdown](https://docs.nicetrybot.com/docs/0.x/addons.md) add-on. | A token can never do more than its user can. If you can't manage domains on a team, neither can your token, whatever was ticked. An agent only ever sees the tools its token may use. ### Tokens for All Teams A token made for **All my teams** works on every team its user is on, at the moment of each request: teams you join are included straight away, and teams you leave are gone straight away. Each tool then takes the team to work on as a `team` argument, holding the team's slug, and refuses a call that leaves it out. The `whoami` tool lists the teams, and the server tells agents to check which team you mean before changing anything. On each team, the token can only do what you can do there, whatever abilities were ticked. A token for one team stays on that team: a call that names another team is refused. The [Transfer](https://docs.nicetrybot.com/docs/0.x/addons.md) add-on only accepts tokens for one team. ### When Tokens Stop Working A token stops working as soon as any of the following is true:
- It has expired. - It was deleted in **Admin → API tokens**. - Its user is no longer an administrator. - Its user is no longer a member of the token's team. A token for all teams loses only that team.
**Admin → API tokens** shows when each token was last used, so you can spot ones nobody needs any more. ## Connecting an Agent > [!WARNING] > Treat API tokens like passwords. Prefer short lifetimes, grant only the abilities a task needs, and add the token in your own terminal rather than pasting it into a chat, where it would become part of the conversation. ### Claude Code Run the following in your own terminal, replacing the address and token with yours. **Admin → API tokens** shows this command, filled in, when you create a token: ```shell claude mcp add --transport http nicetrybot https://panel.example.com/mcp \ --header "Authorization: Bearer " ``` ### Other Agents Cursor, Claude Desktop, VS Code, and most other agents read MCP servers from an `mcp.json` file. Add NiceTryBot as an HTTP server: ```json { "mcpServers": { "nicetrybot": { "type": "http", "url": "https://panel.example.com/mcp", "headers": { "Authorization": "Bearer " } } } } ``` ### NiceTryBot CLI To work with the MCP server's tools from your own terminal, [add a remote](https://docs.nicetrybot.com/docs/0.x/remotes.md): ```shell nicetrybot remote add panel.example.com ``` ## Available Tools The MCP server offers the following tools. Each one describes itself to the agent, including what it needs and what it returns, so you rarely need to name them yourself. | Area | Tools | | --- | --- | | Token | `whoami` | | Domains | `list-domains`, `add-domain`, `check-domain`, `verify-domain`, `get-origin-secret`, `set-protection-mode`, `remove-domain` | | Traffic | `describe-traffic-fields`, `query-traffic` | | Rules | `describe-rule-language`, `list-rules`, `create-rule`, `update-rule`, `delete-rule`, `backtest-rule` | | Gyms | `list-gyms`, `create-gym`, `update-gym`, `delete-gym`, `add-obstacle`, `remove-obstacle`, `list-obstacle-types` | | Lockdown add-on | `get-lockdown-status`, `lock-down-domains`, `release-domains`, `lift-lockdown` | The tools that remove or delete something, and those that change what visitors see, tell agents to confirm with you first. Add-ons may add their own tools, which only appear while the add-on is turned on. To see the tools your token may use from the command line, run `nicetrybot tools`. ## Calling the Server Yourself The MCP server speaks JSON-RPC over HTTP. It supports the stateless `2026-07-28` version of the protocol, in which every request stands on its own: there's no `initialize` handshake and no session. Each request names the protocol version in its `_meta` and repeats its method and tool name in headers: ```shell curl https://panel.example.com/mcp \ -H "Authorization: Bearer $NICETRYBOT_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2026-07-28" \ -H "Mcp-Method: tools/call" \ -H "Mcp-Name: list-domains" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list-domains", "arguments": {}, "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {} } } }' ``` Agents that use the older handshake, which starts with an `initialize` request, work too. A tool's answer arrives in `result.structuredContent` as JSON. When a tool refuses, such as for a validation error or a domain that isn't on the team, `result.isError` is `true` and `result.content` explains why. On your server, `php artisan nicetry:mcp` accepts the same JSON-RPC message, on standard input or as its argument, and prints the server's answer. It acts as the owner of the first administrator's current team, or of the team you pass with `--team`, and is what the `nicetrybot` command uses when it runs on the server. ## Security Without a working token, the MCP server isn't there at all. Every request to `/mcp` without one gets exactly the same `404` response as a path that was never routed: the same body, no cookie, no `WWW-Authenticate` header, and no sign of a database lookup in the `Server-Timing` header. Someone scanning your control panel can't tell an API exists, let alone which product it belongs to. With a token, everything an agent does is limited to the token's team (or the teams its user is on), its abilities, and its user's permissions on each team. An agent can't see or change another team's domains, rules, or gyms, even by guessing their names or IDs. ## Adding Tools From Add-ons Add-ons may give agents their own tools by listing them in their service provider's `$mcpTools` property. See [Creating Add-ons](https://docs.nicetrybot.com/docs/0.x/creating-addons.md) for how, and `packages/lockdown` in NiceTryBot's repository for a working example. --- # Installing Add-ons - [Introduction](#introduction) - [First-Party Add-ons](#first-party-add-ons) - [Free Add-ons](#free-add-ons) - [Paid Add-ons](#paid-add-ons) - [Installing Add-ons](#installing-add-ons) - [Installing Add-ons Using AI](#installing-add-ons-using-ai) - [Removing Add-ons](#removing-add-ons) - [Installing Add-ons Manually](#installing-add-ons-manually) - [Licence Keys](#licence-keys) - [Entering Keys](#entering-keys) - [Trials](#trials) - [Turning Add-ons On and Off](#turning-add-ons-on-and-off) ## Introduction Add-ons extend NiceTryBot with new pages, rule fields, gym obstacles, and more. Under the hood, an add-on is simply a Composer package that the control panel discovers on its own. Some add-ons ship with NiceTryBot and are free, while others are available from the [NiceTryBot marketplace](https://nicetrybot.com/marketplace). Add-ons are built into the control panel's image before it runs, so nothing is ever installed from inside the control panel itself. Instead, you install add-ons on your server using the `nicetrybot` command. ## First-Party Add-ons ### Free Add-ons The following add-ons ship with every NiceTryBot installation: | Add-on | Description | | --- | --- | | **Lockdown** | Close every site behind a maintenance page in one click, then let people back in. Administrators get through using an IP allowlist or a signed pass. | | **Privacy mode** | Redact the control panel before you share your screen. Swaps hostnames for stand-ins, masks IP addresses, and hides the words you choose. Toggle it from the top bar or with ⌘ + /. | | **Rule packs** | Ready-made rules and gym routines, tested on real traffic. Packs cover scanners, PHP exploits, AI crawlers, browser impostors, and form scripts. Installed rules only tag, and installed routines start off. | | **Tarpit** | Make the worst bots wait a little longer with every request, held at the edge. | | **Transfer** | Move an origin, along with its sites, secret, rules, and gym routines, from one NiceTryBot installation to another. Requires the Origins add-on on both installations. | ### Paid Add-ons The following add-ons are available from the [marketplace](https://nicetrybot.com/marketplace). Each is licensed per installation and includes a year of updates: | Add-on | Description | | --- | --- | | **Captchas** | Puzzles people solve in a second and bots get lost in, as a new gym obstacle. | | **Fingerprints** | A device fingerprint taken inside the gym, so you can spot one machine hiding behind a thousand home IP addresses. | | **Request Monitor** | See what your origin answered, and catch watchwords in what visitors send. Adds the **Captures** page and the `response` rule fields. | | **Threats** | Watch the pages that matter, graded from calm to severe against their normal traffic. | | **Origins** | One backend, hundreds of sites. Protect every domain an application serves, and add new ones automatically. | The marketplace also sells **More domains** packs. These aren't packages at all, just licence keys, and each adds 100 domains to your installation's [domain limit](https://docs.nicetrybot.com/docs/0.x/domains.md#domain-limits). ## Installing Add-ons To install marketplace add-ons, SSH into your server and pass their names to `nicetrybot addons add`: ```shell nicetrybot addons add captchas threats ``` The first time you install a paid add-on, the command asks for the Composer token from your account on nicetrybot.com. It then rebuilds the control panel's image with the add-ons included and restarts the stack. Building needs about 2 GB of memory, so on a small server, the build may take a little while. You may list the add-ons built into your installation at any time: ```shell nicetrybot addons ``` Your choices are stored in your `.env` file, so they survive updates. Every time you run `nicetrybot update`, your add-ons are rebuilt with the newest versions your licences allow: ```ini NICETRY_ADDONS="nicetry/captchas nicetry/threats" NICETRY_COMPOSER_TOKEN=ntbc_... ``` If you set `NICETRY_ADDONS=all`, NiceTryBot installs every add-on your token may install, so new purchases arrive on the next update without any extra steps. > [!NOTE] > Your Composer token is passed to the build as a Docker build secret. It never ends up in the image, its layers, or the running containers. ### Installing Add-ons Using AI If you would like help choosing add-ons, or an agent has access to your server, you may give it the following prompt: ```text Read https://docs.nicetrybot.com/docs/0.x/addons.md. Then look at my NiceTryBot installation with `nicetrybot status`, suggest which add-ons would help with the traffic I describe, and install the ones I choose. Ask me for my Composer token rather than guessing it. ``` ### Removing Add-ons To remove an add-on, use `nicetrybot addons remove`: ```shell nicetrybot addons remove threats ``` ### Installing Add-ons Manually If you manage the stack with Docker Compose yourself, add the add-ons build file to `COMPOSE_FILE`, set `NICETRY_ADDONS` and `NICETRY_COMPOSER_TOKEN` in your `.env` file, and rebuild the app image: ```shell echo "COMPOSE_FILE=compose.yml:compose.addons.yml" >> .env docker pull ghcr.io/nicetrybot/app:latest docker compose build app docker compose up -d ``` If you run the control panel without Docker, set the same environment variables and run the `addons:install` Artisan command, then run your migrations and rebuild your front-end assets: ```shell php artisan addons:install php artisan migrate --force npm run build ``` ## Licence Keys Paid add-ons and **More domains** packs are unlocked using licence keys, which you will find in your account on nicetrybot.com after purchasing. A key keeps working after its year of updates ends; it simply stops receiving new versions. Keys are checked offline against NiceTryBot's public key, then confirmed once a day. If nicetrybot.com can't be reached, your add-ons keep working. ### Entering Keys Administrators may enter keys from **Admin → Add-ons** in the control panel using **Enter licence key…**. You may also enter any missing keys from your server: ```shell nicetrybot addons keys ``` The **Add-ons** page shows each add-on's state: **Free**, **Licensed**, **Trial**, **Trial ended**, or **Licence revoked**. ### Trials You may try any paid add-on before buying it. Without a key, a paid add-on runs for a 14 day trial from the first time NiceTryBot sees it, then turns itself off until you enter a key. ## Turning Add-ons On and Off Administrators may turn any installed add-on off, and back on, from **Admin → Add-ons**. The change reaches every server on its next request, with no rebuild needed. Turning an add-on off hides its pages, menus, and hooks, and its routes return `404`. Its data stays put, and NiceTryBot keeps honouring its retention and erasure duties for any visitor data it stored, so turning it back on picks up right where you left off. --- # Creating Add-ons - [Introduction](#introduction) - [Creating Add-ons Using AI](#creating-add-ons-using-ai) - [Local Development](#local-development) - [Package Structure](#package-structure) - [The Composer File](#the-composer-file) - [The Service Provider](#the-service-provider) - [Conventions](#conventions) - [Routes](#routes) - [Navigation](#navigation) - [View Slots](#view-slots) - [Gym Obstacles](#gym-obstacles) - [Rule Fields](#rule-fields) - [Visitor Data](#visitor-data) - [Other Extension Points](#other-extension-points) - [Testing Add-ons](#testing-add-ons) ## Introduction Everything NiceTryBot's own add-ons can do, yours can too. An add-on is a Composer package with a service provider. It may add pages to the sidebar and settings, panels to a domain's page, obstacles to the gym, fields to the rule language, columns to Activity, and tools to the MCP server, and it can say how long the visitor data it keeps should live. If you have written a Laravel package before, you will feel right at home. NiceTryBot's control panel is a Laravel application, and add-ons are discovered using Laravel's [package discovery](https://laravel.com/docs/packages#package-discovery). ### Creating Add-ons Using AI The extension points below are regular and well-defined, which makes add-ons a natural job for a coding agent. Open your agent in a checkout of NiceTryBot and give it a prompt like the following: ```text Read https://docs.nicetrybot.com/docs/0.x/creating-addons.md, then study packages/privacy-mode in this repository as a working example. Help me build a NiceTryBot add-on in packages/ that does the following: ... Follow the add-on conventions exactly, write Pest tests, and run them before you tell me it's done. ``` ## Local Development To develop an add-on, clone NiceTryBot and set it up locally. You will need PHP 8.5, Composer, and Node 22: ```shell git clone https://github.com/NiceTryBot/nicetrybot.git cd nicetrybot composer setup composer dev ``` NiceTryBot's root `composer.json` file already includes a path repository for `packages/*`, so the easiest place to build your add-on is a new directory inside `packages`. Once its `composer.json` file exists, require it like any other package: ```shell composer require acme/hello ``` ## Package Structure A typical add-on looks like this. Every directory is optional except `src`: ```text packages/hello/ ├── composer.json ├── config/hello.php ├── database/migrations/ ├── resources/views/ │ └── livewire/ ├── routes/team.php ├── src/HelloServiceProvider.php └── tests/Feature/ ``` ### The Composer File An add-on's `composer.json` file must use the `nicetry-addon` type and register its service provider for discovery. The `extra.nicetry` section describes the add-on to the control panel: ```json { "name": "acme/hello", "description": "One line that shows on the Add-ons page.", "type": "nicetry-addon", "version": "1.0.0", "license": "Elastic-2.0", "autoload": { "psr-4": { "Acme\\Hello\\": "src/" } }, "extra": { "nicetry": { "name": "Hello", "edition": "free" }, "laravel": { "providers": [ "Acme\\Hello\\HelloServiceProvider" ] } } } ``` The `extra.nicetry` section accepts the following keys: | Key | Description | | --- | --- | | `name` | The add-on's display name. Defaults to a headline of its slug. | | `slug` | The add-on's slug. Defaults to the part of the package name after the `/`. | | `edition` | Either `free`, the default, or `paid`. | ### The Service Provider An add-on's service provider extends `App\Addons\AddonServiceProvider`. Most of what an add-on contributes is declared using properties and methods on this class, and NiceTryBot wires everything up for you: ```php */ protected array $routes = [ 'team' => 'routes/team.php', ]; /** * The views drawn in the control panel's view slots. * * @var array */ protected array $slots = [ ViewSlot::DomainPanels->value => 'hello::domain-panel', ]; /** * The add-on's sidebar links. * * @return list */ protected function nav(): array { return [ new NavItem('Hello', 'hand-raised', 'hello.index', order: 75), ]; } } ``` The provider's `register` and `boot` methods are reserved for NiceTryBot. If your add-on needs to do anything else, override the `registerAddon` and `bootAddon` methods instead: ```php /** * Bootstrap the add-on. */ protected function bootAddon(): void { // ... } ``` ### Conventions Like Laravel itself, add-ons favour convention over configuration. NiceTryBot loads the following from your package's root automatically, using the add-on's slug:
- `config/{slug}.php` is merged into `config('{slug}')`. - `database/migrations` run alongside NiceTryBot's own migrations. - `resources/views` are available under the `{slug}::` namespace. - `resources/views/livewire` become Livewire components under the `{slug}::` namespace. - `gym/obstacles/{Vendor}/{Name}` holds the front-end of any gym obstacles.
## Routes Add-ons declare their route files using the `$routes` property. Each key determines how the routes are loaded: | Key | Description | | --- | --- | | `team` | Signed-in pages, served under the current team, such as `/{team}/hello`. They use the `web`, `auth`, and `verified` middleware and require team membership. | | `gym` | The visitor's side of the gym, served under the gym path. These routes have no session, CSRF protection, or Livewire. | | `web` | Loaded as-is. | For example, the following `routes/team.php` file serves a Livewire page from the add-on's `resources/views/livewire` directory: ```php use Illuminate\Support\Facades\Route; Route::livewire('hello', 'hello::index')->name('hello.index'); ``` Every route an add-on registers returns a `404` while the add-on is turned off, so you never need to check for that yourself. ## Navigation Add-ons may add links to three menus by overriding the corresponding method on their service provider: | Method | Returns | Menu | | --- | --- | --- | | `nav` | `list` | The sidebar. | | `settingsNav` | `list` | The **Settings** menu. | | `workspaceMenu` | `list` | The cog menu in the top right. | Each item accepts an `order`, which determines where it appears among NiceTryBot's own links. Core sidebar links are spaced ten apart, from **Overview** at `10` to **Bot gym** at `90`, so there is always room to slot yours in between. The cog menu's core links are **Domains & origins** at `10` and **Visitor data** at `20`. ## View Slots View slots are places in the control panel where an add-on may draw its own views. Map a slot to a view, or a list of views, using the `$slots` property: | Slot | Where it draws | | --- | --- | | `head` | The `` of every control panel page. | | `public-nav` | Buttons in the public header. | | `welcome-sections` | Sections on the welcome page. | | `login-actions` | Below the login form. | | `app-banner` | Notices above signed-in pages. | | `topbar-actions` | Small buttons before the cog menu. | | `body-end` | Scripts and modals at the end of the ``. | | `domains-actions` | Buttons beside **Add domain**. Receives `$team`. | | `domains-sections` | Sections on the **Domains & origins** page. Receives `$team`. | | `domain-panels` | Panels in a domain page's side column. Receives `$domain`. | | `rules-gallery` | Below the rule template gallery. Receives `$team`. | | `rule-panels` | Below **Send its clients to the gym** when editing a rule. Receives `$team` and `$rule`. | ## Gym Obstacles An add-on may add obstacles to the [bot gym](https://docs.nicetrybot.com/docs/0.x/concepts.md#the-bot-gym) by listing them in the `$obstacles` property. Each obstacle implements the `App\Gym\Contracts\Obstacle` interface, and most obstacles extend `App\Gym\BaseObstacle`, which provides sensible defaults: ```php protected array $obstacles = [ StretchingObstacle::class, ]; ``` The interface asks an obstacle to describe itself to the routine editor, and to run its challenge for the visitor: | Method | Description | | --- | --- | | `key()` | A stable key that routines store, such as `proof-of-work`. Never change it once released. | | `name()`, `tagline()`, `icon()` | How the obstacle appears in the routine editor. Icons are Heroicon names. | | `settings()`, `configure()`, `summarize()` | The obstacle's settings, and a short summary of a configured obstacle. | | `humanSeconds()`, `workHashes()` | How long the obstacle takes a person, and how much work it costs a bot. | | `begin()`, `view()`, `viewData()` | Start the challenge and render it. | | `attempt()` | Check a visitor's answer, returning an `ObstacleOutcome`. | The obstacles that ship with NiceTryBot, in `gym/obstacles/NiceTryBot`, are the best reference for writing your own. ## Rule Fields Add-ons may add fields to the [rule language](https://docs.nicetrybot.com/docs/0.x/concepts.md#available-fields) and Activity search. List a backed enum implementing `App\Support\RuleFieldDefinition` in the `$ruleFields` property, and each case becomes a field. The interface's methods describe the database column behind each field, whether it holds text or numbers, and the English name and hint shown in the editor. The Tarpit add-on's `tarpit` and `tarpit.outcome` fields are a good example. Add-ons may also offer ready-made rules and gym routines through the `$ruleTemplates` and `$routineTemplates` properties. ## Visitor Data If your add-on stores anything about visitors, it **must** tell NiceTryBot how to keep that data in check. List a class implementing `App\Support\VisitorDataHolder` in the `$visitorData` property. It answers three questions: | Method | Description | | --- | --- | | `retention()` | How long each kind of data the add-on stores is kept. | | `prune()` | Delete a team's data older than its retention period. | | `forget()` | Erase everything stored about a visitor, by IP address or fingerprint. | This keeps your add-on's data inside the retention periods and erasure requests described in the [privacy documentation](https://docs.nicetrybot.com/docs/0.x/privacy.md), and those duties continue even while the add-on is turned off. ## Other Extension Points The service provider offers a number of other properties for more specialised add-ons: | Property | Description | | --- | --- | | `$policies` | Authorization policies to register, keyed by model. | | `$mcpTools` | Tools to add to the control panel's [MCP server](https://docs.nicetrybot.com/docs/0.x/mcp.md). | | `$requestAddons` | Marks, details, and actions for requests in Activity. | | `$actionAddons` | Badges for Activity's action column. | | `$clientAddons` | Extra details for client cells. | | `$edgeLineAddons` | Keep your own data from each line of the edge's access log. | | `$attemptListeners` | Listen to every obstacle attempt in the gym. | | `$gymAddons` | Add a setting to the routine editor, and HTML to the gym's page. | | `$captureFeed`, `$lockdownFeed`, `$tarpitFeed` | Feed instructions to the edge. | To see these in use, the free add-ons in NiceTryBot's `packages` directory are complete, working examples. `packages/privacy-mode` is the smallest place to start, `packages/tarpit` shows rule fields and edge feeds, and `packages/lockdown` shows policies, MCP tools, and the cog menu. > [!NOTE] > Paid add-ons are licence-gated by NiceTryBot automatically when their `edition` is `paid`, so add-on authors never write licence code of their own. ## Testing Add-ons NiceTryBot is tested using [Pest](https://pestphp.com), and your add-on's tests may live in its own `tests` directory alongside its code. Before sharing your add-on, run NiceTryBot's checks, which include Pint, PHPStan, and the test suite: ```shell composer ci:check ``` --- # Privacy - [Introduction](#introduction) - [What Leaves Your Servers](#what-leaves-your-servers) - [What NiceTryBot Stores](#what-nicetrybot-stores) - [Requests](#requests) - [Gym Visits](#gym-visits) - [Add-on Data](#add-on-data) - [The Edge's Access Log](#the-edges-access-log) - [Retention](#retention) - [Erasing a Visitor](#erasing-a-visitor) - [Cookies](#cookies) - [Telling Your Visitors](#telling-your-visitors) - [Writing a Privacy Notice Using AI](#writing-a-privacy-notice-using-ai) - [Privacy Mode](#privacy-mode) ## Introduction To tell a bot from a person, NiceTryBot has to look at every request, and some of what it looks at, such as an IP address, is personal data. We think you should know exactly what NiceTryBot keeps, for how long, and where it goes, so this page spells it out. Because NiceTryBot is self-hosted, the data it collects lives on your servers and in your database. You decide how long it is kept, and you are the one your visitors trust with it. > [!NOTE] > This page describes how NiceTryBot handles data. It is not legal advice. If you are subject to the GDPR or similar laws, you are the controller of the data NiceTryBot collects on your behalf, and you should review your obligations with someone qualified to advise you. ## What Leaves Your Servers Nothing NiceTryBot collects about your visitors is sent to a third party. IP addresses are geolocated using local [DB-IP](https://db-ip.com) Lite databases, which are downloaded once a month, so no lookup ever leaves your server. NiceTryBot only makes the following outbound requests: | Request | What is sent | | --- | --- | | TLS certificates | Your domains' hostnames, to Let's Encrypt or ZeroSSL. | | Location databases | Nothing about your visitors. The databases are downloaded once a month. | | Release check | Nothing about your visitors. Turn it off with `NICETRY_UPDATE_CHECK=false`. | | Licence check | Only if you have entered a paid add-on's key: the key's ID, once a day. | | Plain English search | Only if you add an Anthropic API key: your question, the search or rule you are editing, and your team's hostnames, sent to Anthropic using your key. | ## What NiceTryBot Stores ### Requests For every request that reaches the edge, NiceTryBot stores:
- The time, domain, path, method, status, response time, and the edge's action and tag. - The visitor's IP address and user agent. - The visitor's JA3, JA4, and JA4H fingerprints, client hints, and `Accept-Language` header. - The visitor's network, its ASN, and an approximate location: country, region, and city.
Query strings are **not** stored with requests. Full IP addresses are kept because rules and bans depend on them. ### Gym Visits When a visitor is sent to the [bot gym](https://docs.nicetrybot.com/docs/0.x/concepts.md#the-bot-gym), NiceTryBot stores the visit: the visitor's IP address, user agent, JA4 fingerprint, client hints, country, and network, the site and address they asked for, including the query string, and their progress through each obstacle. ### Add-on Data Some add-ons store data of their own: | Add-on | What it stores | | --- | --- | | Captchas | Whether each answer passed or failed. The answer itself is discarded. | | Fingerprints | A device fingerprint, taken only inside the gym. | | Request Monitor | Captured requests and responses on the routes you choose, with sensitive values redacted. Passwords are never stored, only their length. Captures are kept for one day, or 30 days if flagged. | Every add-on that stores visitor data is required to follow your retention settings and erasure requests, even while it is turned off. ### The Edge's Access Log The edge writes every request to a rolling access log on your server before it is imported. This log includes the full URL, but `Cookie` and `Authorization` values are always redacted. Query strings are dropped and sensitive values are redacted before each line is imported. The log rolls over at 100 MB and keeps the last ten files, so older lines expire on their own. ## Retention Each team decides how long its visitor data is kept from the **Visitor data** page in the cog menu. You may choose anywhere from 1 to 730 days. The installation-wide defaults are: | Data | Default | | --- | --- | | Requests | 90 days | | Gym visits | 90 days | | Tags and bans | 30 days after they end | Administrators may change these defaults using the following environment variables: ```ini VISITOR_DATA_REQUEST_DAYS=90 VISITOR_DATA_GYM_DAYS=90 VISITOR_DATA_VERDICT_DAYS=30 ``` NiceTryBot deletes expired data every hour. Client labels you create are kept until you delete them. ## Erasing a Visitor If a visitor asks you to delete their data, use **Erase this visitor** on the **Visitor data** page. You may find a visitor using the reference shown on the gym's page, their IP address, or their JA4 fingerprint. Erasing a visitor deletes their requests, gym visits, captcha answers, fingerprints, tags, bans, and labels, then records that the erasure happened using a keyed hash rather than the visitor's details. The edge's rolling access log isn't affected, but it expires on its own. > [!WARNING] > Only erase by JA4 fingerprint when the fingerprint belongs to a bot. Everyone using the same browser build shares a JA4 fingerprint, so erasing a common one would erase many people's data at once. ## Cookies NiceTryBot never sets analytics or advertising cookies. The gym and the Lockdown add-on set the following cookies, which are strictly necessary for them to work: | Cookie | Purpose | Expires | | --- | --- | --- | | `_q_session` | Keeps track of a visit to the gym. | After 15 minutes of inactivity. | | `_q_pass` | Remembers that a visitor passed the gym. | After the routine's pass time, from one minute to one week. | | `_q_pass_k` | Lets an administrator through a lockdown. | After 12 hours. | ## Telling Your Visitors Your site's privacy notice should mention that you use bot protection. A short paragraph is usually enough, such as: ```text Security and bot protection. To keep this site available and to stop automated abuse and fraud, we process technical information about each request, including your IP address, approximate location, browser details and how your browser connects. We keep this information for up to 90 days. ``` If you would like visitors to be able to reach you about their data, set **Contact for visitors** on the **Visitor data** page. It is shown beneath the gym's challenges, along with a reference the visitor can quote when they get in touch. ### Writing a Privacy Notice Using AI Your agent can draft a privacy notice paragraph that matches your actual settings. Give it the following prompt: ```text Read https://docs.nicetrybot.com/docs/0.x/privacy.md. My NiceTryBot installation keeps requests for [N] days and gym visits for [N] days, and I use these add-ons: [list]. Draft a short, plain-English paragraph for my site's privacy notice that describes the bot protection accurately. Don't claim anything the docs don't support. ``` ## Privacy Mode The free Privacy mode add-on redacts the control panel before you share your screen. It swaps your hostnames for stand-ins, masks IP addresses to their first two parts, such as `193.148.x.x`, and hides any words you list in **Settings → Privacy**. You may toggle it from the top bar or by pressing ⌘ + /. Privacy mode only changes what the control panel displays. It does not change what NiceTryBot stores. --- # Reporting Vulnerabilities - [Introduction](#introduction) - [Reporting a Vulnerability](#reporting-a-vulnerability) - [What to Include](#what-to-include) - [Reporting With the Help of an Agent](#reporting-with-the-help-of-an-agent) - [What Happens Next](#what-happens-next) - [Scope](#scope) - [Keeping Your Installation Secure](#keeping-your-installation-secure) ## Introduction NiceTryBot stands in front of other people's websites, so we take its security seriously. If you discover a security vulnerability in NiceTryBot, its add-ons, or its installer, we would love to hear about it, and we will work with you to fix it before it becomes public. ## Reporting a Vulnerability Please **do not** open a GitHub issue or discussion for a security problem, since everything there is public. Instead, send an email to [hello@nicetrybot.com](mailto:hello@nicetrybot.com) with the word **"security"** in the subject line. ### What to Include A good report helps us confirm and fix the problem quickly. Where you can, please include:
- The NiceTryBot release you tested, which `nicetrybot version` will tell you, and any add-ons involved. - The component affected, such as the edge, the control panel, the gym, the installer, or an add-on. - Step-by-step instructions to reproduce the problem, along with any proof of concept. - What an attacker could achieve, and what they would need to get there.
Please test against an installation you own. Never test against someone else's NiceTryBot installation, or the sites it protects, without their permission. ### Reporting With the Help of an Agent If an agent helped you find the problem, it can also help you write the report. You may give it the following prompt: ```text Read https://docs.nicetrybot.com/docs/0.x/vulnerabilities.md. Then help me write a private vulnerability report for NiceTryBot about the issue we found, including the release, the affected component, exact reproduction steps, and the impact. Draft the email for me to send; don't send it or post anything publicly. ``` ## What Happens Next A person reads every message sent to hello@nicetrybot.com, and we usually reply within two business days. We will confirm whether we can reproduce the problem, keep you updated as we work on a fix, and let you know when a release containing the fix is available. Please give us a reasonable chance to fix the problem, and for installations to update, before you disclose it publicly. Security fixes are announced in the release notes on [GitHub](https://github.com/NiceTryBot/nicetrybot/releases), and the control panel will show administrators an update notice once the fixed release is available. ## Scope We are interested in any vulnerability that could affect the security of a NiceTryBot installation, the sites it protects, or their visitors. For example:
- Ways around the edge, the gym, or a domain's protection mode. - Access to the control panel, its API, or its MCP server without valid credentials, or beyond a user's team or role. - Ways to leak a domain's origin secret, an API token, or visitor data. - Server-side request forgery, injection, or remote code execution in any component. - Problems in the installer or the `nicetrybot` command that could compromise a server.
Bots that solve the gym's obstacles are not vulnerabilities on their own. Obstacles are designed to cost bots time and CPU rather than to be unbeatable. That said, if you have found a way to pass an obstacle without doing its work, we would still like to know. ## Keeping Your Installation Secure The best way to stay secure is to stay current. We recommend that you:
- Update promptly when the control panel shows a new release, using `nicetrybot update`. - Keep your server's operating system patched, and expose only ports 80 and 443 to the world. - [Lock your origins](https://docs.nicetrybot.com/docs/0.x/origins.md) so that nobody can skip the edge. - Give API tokens only the abilities they need, and the shortest lifetime that works. - Keep your `.env` file, which holds your installation's secrets, out of source control and backups others can read.