Domains, DNS and SSL
Your free camplax.app address, custom domains with the DNS records to add, automatic SSL certificates, and the edge controls — firewall, rate limits, maintenance — on the same page.
Every project is reachable from the first deploy at a free address:
https://<slug>.camplax.app
That address is the project's primary domain — it always works, it already has an SSL certificate (so https works), and it cannot be removed. To use your own domain, add two DNS records at your DNS provider — a CNAME (or ALIAS at the root of a domain) that sends traffic here and a TXT record that proves the name is yours — and Camplax gets the SSL certificate for you.
Add a custom domain
In the console, open Domains and add the hostname — example.com, www.example.com or app.example.com. Then at your DNS provider:
CNAME example.com → edge.camplax.app
edge.camplax.app is the Camplax edge your hostname points at. A subdomain (www.example.com, app.example.com) is a plain CNAME everywhere.
The root of a domain (example.com with nothing in front) is harder, because DNS does not allow a CNAME there:
- Cloudflare, Namecheap, Porkbun, DNSimple, Vercel and Netlify DNS can do it — they call it CNAME flattening,
ALIASorANAME. Use the same value,edge.camplax.app. - GoDaddy, Squarespace, Wix, Hover, IONOS and Amazon Route 53 cannot point a root at another name (Route 53's
ALIASonly reaches AWS). Serve the site onwww.example.comand use the DNS host's forwarding to sendexample.comtohttps://www.example.com. Camplax reads your nameservers when you add the domain and shows the exact steps for your DNS host in the domain's sheet.
Pointing a root at fixed IP addresses instead is not offered: it needs a Cloudflare Enterprise contract.
CAA records. If your domain has CAA records, they list the only certificate authorities allowed to issue for it, and a list without Cloudflare's makes the certificate fail with no other symptom. Camplax checks when you add the domain; if yours blocks it, the sheet shows the records to add — CAA 0 issue "letsencrypt.org", "pki.goog" and "ssl.com".
The certificate needs one more record: a TXT record named _acme-challenge.<your hostname>. Its value is unique to your domain on your project, and the domain's sheet shows the exact name and value a few seconds after you add it. It proves the name is yours, so nobody else can add your domain to their project and take its traffic once you point it here.
Once both records resolve, Cloudflare accepts the hostname and issues its certificate, usually within a few minutes. The domain's sheet checks every 30 seconds while it is open, Verify checks right away, and Camplax also checks on its own: within the hour, again at about 1, 2, 4, 8 and 16 hours, and once a day after that. A domain is active only when two things are both done: Cloudflare accepts the hostname and the certificate is issued. Until then the row reads pending.
The moment it turns active, your app answers on it — no redeploy. Removing a domain takes it off your app just as fast. Renewals need nothing from you: the TXT record is only used for the first certificate.
Removing a domain? Delete its CNAME at your DNS host as well, so the name stops pointing at Camplax.
A domain that is still pending 14 days after you added it is released, so an abandoned name does not sit at Cloudflare forever. Add it again once its DNS points at edge.camplax.app.
Certificates renew on their own — while DNS still points here
Certificates last 90 days and Cloudflare renews them automatically, but only while the name still points at edge.camplax.app. Camplax reads every live domain's certificate once a day: the domain's sheet shows when the current one expires, and the project's uptime alert recipients get an email 14, 7 and 1 days before expiry if it has not renewed, and as soon as Cloudflare reports the name has moved away.
www and apex are a pair
Add example.com and Camplax creates both names — one canonical host that serves, and its twin that answers with a permanent redirect (301) to it, keeping the path and query: https://www.example.com/pricing?x=1 goes to https://example.com/pricing?x=1. Point both at edge.camplax.app. Adding www.example.com does the same in reverse. You choose which side is canonical when you add the domain; the default is www if you typed a www. hostname, otherwise the apex. Removing the canonical name removes its twin too.
A subdomain like app.example.com is not paired with anything — it just serves.
Rules worth knowing:
- A hostname belongs to one project. Adding a name another project already serves fails with
hostname_taken. A name you move from one project to another stops serving the old one before it serves the new one. - Someone added your domain first? If another project added the name but it never went live there, adding it answers
hostname_pending_elsewherewith one TXT record to create —_camplax.example.com, valuecamplax-verify=…. Add it at your DNS host and add the domain again: Camplax checks the record, releases the other project's claim and adds the name to yours. You can delete the record afterwards. A name whose certificate was already issued to another project is never taken this way. - Up to 15 custom domains per project, each with its
wwwtwin. - Platform names are refused. You cannot add a
camplax.apporcamplax.devaddress, or a hostname that impersonates Camplax.
Adding, verifying and deleting domains is owner-only — a teammate role can look, but cannot move your traffic.
Sign-in follows your domain
When a custom domain turns active, or you delete one, Camplax updates production's settings on its own and rebuilds production from its last good commit, so sign-in works on the new address:
CAMPLAX_URLbecomes the canonical custom domain, and so do the public copies your frontend reads (NEXT_PUBLIC_CAMPLAX_URL,VITE_CAMPLAX_URL,PUBLIC_CAMPLAX_URL). The starters useCAMPLAX_URLfor sign-in unless you set your ownBETTER_AUTH_URL, which Camplax never changes. Production is rebuilt only when this address actually moves.CAMPLAX_TRUSTED_ORIGINSlists every address the app answers on — thecamplax.appone and each custom domain — and the starters pass it to Better Auth as trusted origins.- Passkeys stay tied to the
camplax.appaddress they were made on (CAMPLAX_PASSKEY_RP_ID), so adding a domain never strands them. Camplax serves/.well-known/webauthnon the app listing the other addresses, which lets Chrome, Edge and Safari use those passkeys on your custom domain. Firefox does not support this yet: there, a passkey made on thecamplax.appaddress works only on that address.
Previews keep their own branch address. If you wrote your own sign-in code, read CAMPLAX_URL and CAMPLAX_TRUSTED_ORIGINS rather than hard-coding the camplax.app address.
The rest of the Domains page
The same page carries the edge controls that sit in front of every hostname on the project — *.camplax.app and custom domains alike.
Firewall
Rules checked on your app's edge, in front of your app, within about a minute of saving. Start with the switches:
| Switch | What it does |
|---|---|
| SQL injection and XSS patterns | Blocks obvious attack patterns in the query string |
| Requests with no user agent | Blocks requests that present no User-Agent at all — on for new projects |
| Countries you do not serve | Blocks a list of ISO country codes you pick |
| Under Attack Mode | Challenges every visitor — the emergency switch. Camplax's own calls (cron jobs, uptime checks, deliveries) still get through, so your jobs keep running while it is on |
There is no "bad bots" or "risky IP" switch on purpose. Cloudflare's threat score has been 0 for every request since September 2024, so a rule on it would look like protection and block nothing; custom rules that use cf.threat_score or cf.bot_management.score are refused for the same reason.
Below the switches you can write custom rules — an expression like http.request.uri.path matches "^/internal" with an action: block (the visitor gets 403), skip (matching requests go straight through), or a challenge (challenge, managed_challenge, js_challenge). Rules run oldest first and the first match wins. Challenges run in Cloudflare, before the request reaches your app's edge, so a skip lets requests past the blocks after it but not past a challenge. Health checks are exempt, so the firewall cannot take down the monitor that would tell you it did.
A rule can use the path, query, method, host, user agent, referer, cookie, the visitor's IP address (ip.src — whole addresses, or IPv4 ranges ending at /8, /16 or /24) and country (ip.geoip.country), with eq, ne, contains, matches, in { … }, starts_with, ends_with and len. Anything else — lower(), any(), http.request.headers[…], ip.geoip.asnum — is refused when you save, with the reason, rather than saved and ignored. Each rule says whether it is running yet.
Blocks and skips run on your app's edge, so they have no limit beyond 50 per project. Challenges run in Cloudflare, in one zone shared by every app that holds only a handful of rules in total; if a challenge comes back "not live yet" with a rule-limit message, use Block, remove a challenge you no longer need, or ask support.
Rate limits
A limit is one sentence: N requests a second, minute, hour or day from each IP address. You pick the path (exact, or ending in * for everything under it) and the count, up to 10,000. A caller over the limit gets 429 until the window refills. Every project starts with 10 a minute on /auth/*, /api/login and /api/auth/sign-* (the starters' sign-in, sign-up and sign-out). Because limits run on your app's edge, a limit on a sign-in route stops most credential stuffing before it reaches your code.
Maintenance
One switch puts up a holding page on your app's production addresses within about a minute — your own title and message, served as 503 (or 200 if you prefer). Previews keep serving. Three options matter: let your own addresses through so you can check the fix on the real site, keep /api/* serving so mobile apps and webhooks keep working, and set an auto-off timer (up to 24 hours) so the page cannot outlive the outage by being forgotten. Deploys, migrations and this console keep working while it is on, and every window is recorded in a history list.
Preview gate
The switch that password-locks previews lives under Settings → Password-protect previews, because it is about the app, not a hostname. It is documented in Preview deployments.
The API
Everything above is on the API under /v1/projects/:slug/domains:
| Method | Path | What it does |
|---|---|---|
GET | /domains | List the project's domains plus the CNAME target |
POST | /domains | Add a hostname (owner only) |
POST | /domains/:id/verify | Re-check hostname and certificate status |
DELETE | /domains/:id | Remove a custom hostname (not the primary) |
GET/POST | /domains/firewall | List and create firewall rules |
PUT | /domains/firewall/presets | Toggle the preset switches |
GET/POST | /domains/rate-limits | List and create rate limits |
GET/PUT | /domains/maintenance | Read and set maintenance mode |
GET/PUT | /domains/preview-gate | The preview password gate — see Preview deployments |
Under the hood: custom domains are Cloudflare custom hostnames — Cloudflare issues the certificate (validated by the TXT record), and edge.camplax.app is the fallback origin your CNAME points at. Requests reach your app's cell through its tunnel, and the cell's gateway routes them by hostname; the firewall, rate limits and holding page are policies on that gateway, per environment.