Protecting Your Origins
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:
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. |
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.
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 Forbiddenwhen 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
Hostheader the request arrived with, notX-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:
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:
'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:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
use Symfony\Component\HttpFoundation\Response;
class RequireNiceTryOrigin
{
/**
* Refuse requests that didn't come through the NiceTryBot edge.
*/
public function handle(Request $request, Closure $next): Response
{
$secret = config('services.nicetry.origin_secret');
if (blank($secret)) {
return app()->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:
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:
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:
www-origin.example.com {
not header X-NiceTry-Origin-Secret {env.NICETRY_ORIGIN_SECRET}
respond 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:
curl -sI https://www-origin.example.com/
With the header, the request should be served as usual:
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, 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:
- Copy the current secret, then click Rotate secret and copy the new one.
- Deploy your origin so that it accepts either value.
- 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.