Locking your server to Cladior

Optional, and nothing here is part of the integration. Your app works without it. This closes a hole that costs you money rather than us, which is why it is worth ten minutes.

Why this exists

Your public address points at Cladior, but your server is still reachable at its own address. Anybody who finds it can use your app without passing through us, so you serve the bandwidth and earn nothing for it.

Nobody stumbles onto that address by accident. It leaks the ordinary ways: an old DNS record, a certificate in the public Certificate Transparency log, a stack trace, a subdomain scanner. One person finding it and posting it is all it takes, and you would have no signal that anything had happened except traffic you cannot see and earnings that do not match it.

The fix is five lines of middleware. Cladior sends a secret header on every request it forwards; your server refuses anything arriving without it. That works on every host and every framework, which is why it is first here: the host-specific options further down are better locks, but only if your host happens to offer one.

The middleware

Open your app in the dashboard, go to Origin protection and generate a secret. It is shown once and never again, so copy it then. Set it as CLADIOR_ORIGIN_SECRET where your host keeps environment variables, then reject requests that do not carry it.

Cladior sends it as X-Cladior-Secret on every forwarded request, and strips any copy a caller tried to send themselves, so the header your server sees is either ours or absent.

Express
app.use((req, res, next) => {
  if (req.get('X-Cladior-Secret') !== process.env.CLADIOR_ORIGIN_SECRET) {
    return res.status(403).send('Direct access is not allowed');
  }
  next();
});
Next.js · middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request) {
  if (request.headers.get('x-cladior-secret') !== process.env.CLADIOR_ORIGIN_SECRET) {
    return new NextResponse('Direct access is not allowed', { status: 403 });
  }
}

export const config = { matcher: '/:path*' };
SvelteKit · hooks.server.ts
export async function handle({ event, resolve }) {
  if (event.request.headers.get('x-cladior-secret') !== process.env.CLADIOR_ORIGIN_SECRET) {
    return new Response('Direct access is not allowed', { status: 403 });
  }
  return resolve(event);
}
Django · middleware.py
import os
from django.http import HttpResponseForbidden

def cladior_only(get_response):
    def middleware(request):
        if request.headers.get('X-Cladior-Secret') != os.environ['CLADIOR_ORIGIN_SECRET']:
            return HttpResponseForbidden('Direct access is not allowed')
        return get_response(request)
    return middleware
Laravel · app/Http/Middleware
public function handle($request, Closure $next)
{
    if ($request->header('X-Cladior-Secret') !== env('CLADIOR_ORIGIN_SECRET')) {
        abort(403, 'Direct access is not allowed');
    }
    return $next($request);
}
Go · net/http
func cladiorOnly(next http.Handler) http.Handler {
    secret := os.Getenv("CLADIOR_ORIGIN_SECRET")
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if subtle.ConstantTimeCompare([]byte(r.Header.Get("X-Cladior-Secret")), []byte(secret)) != 1 {
            http.Error(w, "Direct access is not allowed", http.StatusForbidden)
            return
        }
        next.ServeHTTP(w, r)
    })
}
Leave one path open. Whatever health check your host calls arrives without the header and will start failing, which on most platforms restarts your service in a loop. Exempt that path before you deploy.

Compare in constant time if your language makes it easy, as the Go example does. It matters less than it sounds, because the secret is 64 characters and never echoed back, but it costs nothing.

Rotating is generating a new secret and updating the variable. The old one stops working the moment you generate, so change the variable first if you cannot take a moment of 403s.

Or let your host do it, with no code

A header check runs inside your application, so the request still reaches it. The options below refuse traffic earlier, some before TLS even completes. They are stronger where they are available, and Cladior holds whatever credential each one expects.

Your own server

A VPS, bare metal, Docker on a box. This is the best case: you control both the firewall and the web server, so the lock is genuine rather than approximate.

Download your certificate from the dashboard, then require it.

nginx
ssl_client_certificate /etc/ssl/cladior-origin-ca.pem;
ssl_verify_client on;
Caddy
tls {
  client_auth {
    mode require_and_verify
    trust_pool file /etc/ssl/cladior-origin-ca.pem
  }
}

Connections without our certificate are refused during the TLS handshake, before your application is reached at all.

AWS and Google Cloud

An Application Load Balancer or Google Cloud Load Balancer can verify client certificates the same way. Upload our certificate as the trust store and set mutual authentication to verify.

A security group restricted to our published addresses works alongside it, though on its own it is weaker than the certificate. See the note at the bottom about why.

Vercel

  1. Project Settings, Deployment Protection, switch it on.
  2. Under Protection Bypass for Automation, generate a secret.
  3. Paste it into your Cladior dashboard.

We send it as x-vercel-protection-bypass on every request. Anyone reaching your deployment directly gets Vercel's authentication wall.

Worth knowing. Deployment Protection also blocks your own team from opening the deployment in a browser without signing in to Vercel first. Some teams find that irritating and switch it back off, which quietly removes the protection.

Netlify

Set a site password under Site configuration, Access control, then paste the same password into your Cladior dashboard. We send it as HTTP basic authentication on every forwarded request.

Cloudflare Pages and Workers

Put Cloudflare Access in front of your project and create a service token. Paste the client ID and secret into your dashboard and we send both headers.

Render and Fly.io

Neither can currently be locked down for this. It is worth being direct about rather than leaving you to discover it.

Render's inbound IP rules exist only on Scale and Enterprise plans, and its Private Services have no public address at all, which locks us out along with everybody else. Fly's Flycast makes an app private to your own organisation's network, which has the same effect.

If you are on either, use the fallbacks below. They are weaker, and they are what most sites on the internet already rely on.

If your host cannot do it

Use the middleware at the top of this page. It needs nothing from your host, so "my platform offers no allowlist" is not a reason to leave the origin open. The rest of this section is for the case where you would rather not touch the application at all.

Give your server an unguessable name. Not api.yourdomain.com but something like o-7f2k9x.yourdomain.com, never linked and never advertised. The usual way these leak is Certificate Transparency, the public log of every certificate issued. A wildcard certificate defeats that, because the log then shows only *.yourdomain.com and the specific name stays invisible.

Remember what bypassing costs your user. Sign-in lives at Cladior, so somebody who goes straight to your server arrives with no account, no preferences and nothing saved. They have not avoided a charge, they have signed themselves out. For most apps that is enough on its own.

Why we do not tell you to allow our IP addresses

That is the advice you will find everywhere, and it does not work. Our requests leave from Cloudflare's network, and those addresses are shared by every Cloudflare customer. Somebody can point their own Cloudflare-proxied domain at your server, turn their protections off, and arrive from an address on your allowlist.

The same weakness applies to Cloudflare's default origin-pull certificate, which is also shared. The certificate we give you is ours alone, which is the difference that matters.

The header check has none of that problem. It proves the request carries a secret only we hold, rather than that it left an address we happen to share with everyone else on the same network.