The proxy

Overview

You change one DNS record. Traffic to that hostname reaches Cladior, gets metered, and continues to your server. You do not import anything, call anything, or check anything, and your application does not need to know Cladior exists.

Setup

Add the app in the dashboard with the address your server already answers on, then:

DNS record
Type   CNAME
Name   app   (or any hostname you like)
Value  proxy.cladior.com

Certificates are issued and renewed automatically. If your DNS is already on Cloudflare, set that record to DNS only, the grey cloud, rather than proxied.

Where the record goes

Wherever your DNS is hosted, which is not always where your site is hosted. The two get confused constantly, and the answer is decided by one thing: whichever service your domain's nameservers point at is the one holding your DNS.

If your nameservers are atAdd the CNAME in
Your registrar (Namecheap, GoDaddy, Porkbun)The registrar's DNS or Advanced DNS panel
NetlifyDomains, your domain, DNS records, Add new record
VercelProject settings, Domains, or the account-level Domains tab
CloudflareDNS, Records. Set it to DNS only, the grey cloud
AWSRoute 53, hosted zone, Create record

Hosting your site on Netlify does not by itself mean Netlify holds your DNS. If you pointed the domain at Netlify with an A record from your registrar, the registrar is still where records are added. If you moved the nameservers to Netlify, it is Netlify.

If you are unsure, look up your domain's nameservers: whatever they say is where your records live. whois yourdomain.com in a terminal, or any online whois, lists them.

Putting it on the root domain

The record above uses a subdomain, and a subdomain CNAME works at every provider. A root domain, written @ or as the bare name, is the awkward case: the DNS standard does not allow a CNAME there, so providers each solve it their own way and one of them cannot solve it at all.

ProviderRoot domain
CloudflareAdd the CNAME normally. Cloudflare flattens it for you
NamecheapUse an ALIAS record, not a CNAME
VercelUse an ALIAS record
Route 53Not possible. Its ALIAS records only point at AWS resources, so use a subdomain

Namecheap will refuse an ALIAS while a CNAME, A, AAAA or URL redirect exists on the same name, and a URL redirect on the root is a common default, so remove that first.

Pointing your root domain takes the site off its current host. Whatever serves it today stops the moment the record changes, and we cannot serve it until your certificate has issued and the app is published. Add the record, finish publishing, and set your free paths in one sitting rather than leaving it half done.

How long it takes

Minutes to hours, occasionally longer, and none of it is under our control or yours: it depends on how the record's TTL was set and which resolvers have already cached the old answer. Your registration draft is saved throughout, so you can close the tab after adding the record and come back to the step you left.

The certificate is issued automatically once the record resolves to us, and cannot be issued before then, because the certificate authority checks the same DNS you are waiting on. So a domain that shows as pending is usually waiting on propagation rather than on anything that needs fixing.

Letting us add it for you

If your DNS is hosted at Cloudflare, you can authorise Cladior and we will write the record into your zone. It is offered during registration and again on the app afterwards, and it is only a shortcut: the result is the same record you would add by hand.

Netlify and Vercel are not available yet. Both support the kind of scoped authorisation this needs, so both are possible; neither is built. Every other provider, including Porkbun, Namecheap, GoDaddy and Route 53, offers only a long-lived API key that would give us control of your whole zone, mail records included. We are not willing to hold that to save you one copy and paste, so those stay manual by choice rather than by omission.

That is for a domain you own. If you took a cladior.com subdomain instead, there is no record to add: the hostname is inside our own zone, so it is already pointed at the proxy and already carries a certificate. The dashboard marks it Ready as soon as it exists.

What we do to your responses

HTML gets the Cladior script added before </head>, which is what draws the balance and account controls. Everything else, JSON, images, stylesheets, downloads, passes through untouched.

Redirects to your origin's own hostname are rewritten to yours, and cookies your origin scopes to its own domain are rescoped, because a browser would otherwise discard them. Without both, a login on a proxied site fails with nothing in any log to explain it.

Keeping traffic honest

Your server stays reachable at its own address. Anyone who finds it can use it without passing through Cladior, and those requests are not metered. Closing that is optional, and it is the same five lines wherever you host.

We send a secret header on every request we forward. Reject anything without it. This runs on every host and every plan including free ones, and it goes in one place: the middleware you already have. No route, endpoint or piece of application logic changes.

Once, at your entry point
const expected = process.env.CLADIOR_ORIGIN_SECRET;
if (expected && request.headers.get('x-cladior-secret') !== expected) {
  return new Response('Not found', { status: 404 });
}

Generate the secret in the dashboard under Origin protection, choosing Check it in my own app. Set the same value as CLADIOR_ORIGIN_SECRET in your host's environment variables.

The same check written for Express, Next.js, SvelteKit, Django, Laravel and Go, plus the health-check exemption that catches people out, is in Locking your server.

You can generate a replacement at any time. The new secret takes effect on the very next request we forward, and the old one stops working at that moment, so there is no overlap window. Set the new value on your server in the same sitting: between generating it and deploying it, every request we forward carries a secret your server does not recognise, and if your check is in place those requests are refused.

Keep it server side. A variable named PUBLIC_, NEXT_PUBLIC_ or VITE_ is compiled into your browser bundle, where anybody can read it, and the check then protects nothing at all. This is the most common way this gets broken.

The expected && is deliberate. Unset, the site serves normally, so a deploy that forgets the variable does not take you down. Set, the door is shut. Failing closed on a missing variable turns a configuration slip into an outage.

Where to put it

FrameworkOne place
SvelteKithooks.server.ts, inside handle
Next.jsmiddleware.ts at the project root
Expressone app.use() above your routes
FastAPI@app.middleware("http")
Djangoa middleware class in MIDDLEWARE
Laravelglobal middleware
RailsRack middleware, or before_action in ApplicationController
Gowrap the mux once
nginx, no app codeif ($http_x_cladior_secret != "…") { return 404; }

Once per deployed service, not once per endpoint. Two services, an API and a web app, means two copies. A webhook receiver is the one thing to exempt: Stripe will not send our header, so that path needs an exception.

Every managed host sells a version of this as a paid feature: Deployment Protection on Vercel, password protection on Netlify, Access on Cloudflare. They all do the same job as the five lines above, they all need a plan, and none of them is stronger. If you are already paying for one, keep using it, and set its credential as the header value.

Free pages and search engines

Everything behind your hostname is paid by default. That is the right default and the wrong one to leave alone, because a search engine has no credit: left as it is, a crawler receives the paywall, indexes nothing, and your pages do not rank.

Free pages

Set path prefixes in the dashboard that are served without credit and never billed. A prefix covers what sits under it, so /about also covers /about/team, and / means the home page alone rather than the whole site.

Stylesheets, scripts and fonts are free wherever free pages exist, because a landing page without its stylesheet is not a free page. PDFs, video and archives are not covered and stay paid, since those are usually the product rather than the page.

Images sit between the two, so their folder decides. An image in the same folder as a free page is free, which covers the logo, the icons and the social card, all of which live at the root next to your home page. An image in a subfolder is not, so a site selling photos from /photos/ or scans from /archive/ keeps them behind the meter. If your product is images served from the root, move them into a folder.

This lives in the dashboard rather than your own code, and it has to: we decide whether to charge before forwarding the request, so the same rule in your middleware would run after the charge had already happened.

Search engines

SettingWhat a crawler receives
NothingThe paywall. Paid pages do not rank. This is the default.
Free pagesOnly what is already free. No structured data needed, weaker rankings.
EverythingThe full page, with the paywall declared in the markup. Best rankings.

Crawlers are verified by address against the ranges Google and Bing publish, never by User-Agent. Anyone can claim to be Googlebot in a header, so a User-Agent check would be a paywall in name only. An unverified request is treated as an ordinary visitor.

On Everything, we add "isAccessibleForFree": false to the page for you. That declaration is what separates a permitted paywall from cloaking: serving a crawler more than a reader without it can have a site removed from the index. You do not have to write it, and you should not write your own alongside ours.

Google dropped its First Click Free rule in 2017, so a full paywall is allowed to rank and no free sample is required. A metered approach still tends to rank better, because pages everybody bounces off send poor signals, but that is a decision about conversion rather than a rule you are breaking.

What the visitor is asked

The first time somebody hits a paid page on your site, the proxy sends them to a Cladior consent screen. It shows three things:

  • Your app name, and the public domain it is registered to
  • The rate per kilobyte they will be charged
  • Any scopes you asked for, described in Scopes

The domain is shown next to the name because a name on its own can be copied by anyone. Visitors are told to check it matches the site they are actually on, so register the domain your readers will recognise.

They approve once. Every other Cladior site they visit afterwards is silent, and coming back to yours does not ask again.

The rate is fixed at that moment

Whatever rate the screen showed is the rate that visitor pays, permanently. Changing your rate later applies only to people connecting after the change: nobody is ever charged a figure they did not see and agree to.

If they revoke you

A visitor can revoke your app from their Cladior dashboard at any time. Access ends immediately, the next request through the proxy is refused, and they meet the consent screen again if they return. There is nothing for you to handle: it is the same path as a first visit.

Offering a sign-in link

Usually you do not need one. A visitor who opens a paid page meets the paywall and signs in from there, which is one fewer thing on your page and one fewer decision for them.

When you do want an explicit link, point it at /__cladior/connect on your own domain. It takes the visitor to consent in the same tab and returns them to where they were.

Anywhere on your site
<a href="/__cladior/connect">Sign in</a>

Add ?to=/wherever to choose where they land afterwards. Only paths on your own site are accepted: sending somebody who has just authenticated to an arbitrary address would be an open redirect that also hands over a session.

This is the only sign-in route. It is an ordinary navigation rather than a popup, so it works the same on mobile and cannot be eaten silently by a blocker.

Status codes

CodeMeaning
200Forwarded and billed.
401No session. A browser is sent to sign in instead of seeing this.
402The reader's balance cannot cover it.
404That hostname is not registered with Cladior.
501A WebSocket upgrade. Not supported yet, and refused rather than half-forwarded.
502Your origin did not answer, or redirected a request we could not safely replay.
503No origin address configured for this app.

Known limits

WebSocket connections are refused. Forwarding them would be worse than refusing: traffic after the handshake never returns through us, so you would have a working socket and an invoice that never mentions it.

A large upload sent without a declared length cannot follow a redirect, because the body is consumed as it is forwarded and replaying it would upload nothing while looking successful. That returns 502.

Responses your origin marks cacheable are stored by the reader's browser and served without reaching us, so they are not metered. If every request must be counted, do not send long Cache-Control lifetimes.