Going to production
The defaults are tuned for trying Clerq on localhost. Before anyone else
can reach your instance, there are a few things to get right: real secrets,
HTTPS, and the correct public URL. This page walks through all of it.
1. Set real secrets
Never expose an instance with the default password and dev secret. In your
.env next to the compose file:
POSTGRES_PASSWORD=a-strong-random-password
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
BETTER_AUTH_URL=https://clerq.example.com
POSTGRES_PASSWORD- the database password. The database port is bound to127.0.0.1in the compose file, so it is not directly reachable from the network, but a strong password is still the right baseline.BETTER_AUTH_SECRET- signs sessions and invoice-PDF links. Keep it secret and stable; rotating it logs everyone out.BETTER_AUTH_URL- the exact external origin users will type, includinghttps://. Auth callbacks and redirects depend on this being correct.
2. Put it behind a reverse proxy with HTTPS
The app speaks plain HTTP on port 3000. In production you terminate TLS at a reverse proxy in front of it (Caddy, nginx, Traefik, or a cloud load balancer) and proxy to the app.
The simplest option is Caddy, which obtains and renews certificates automatically:
# Caddyfile
clerq.example.com {
reverse_proxy 127.0.0.1:3000
}
An equivalent nginx server block:
server {
listen 443 ssl;
server_name clerq.example.com;
# ssl_certificate / ssl_certificate_key managed by certbot or similar
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Whichever proxy you use, make sure:
- it forwards the original
Hostheader, - it sets
X-Forwarded-Proto: https, and BETTER_AUTH_URLmatches the publichttps://address exactly.
If you change APP_PORT in your .env, point the proxy at that host port
instead of 3000.
3. Optional: enable Google sign-in
Email and password sign-in always works, so this step is optional. To add a "Continue with Google" button:
- In the Google Cloud console, create an OAuth 2.0 Client ID (type: Web application).
- Add the authorized redirect URI:
https://clerq.example.com/api/auth/callback/google(use your realBETTER_AUTH_URL). - Put the credentials in your
.env:GOOGLE_CLIENT_ID=your-client-id GOOGLE_CLIENT_SECRET=your-client-secret - Restart:
docker compose up -d.
The button appears automatically once both values are present, and disappears if you remove them.
4. Think about open sign-ups
Clerq has no built-in switch to disable registration today. Any visitor who can reach the sign-in page can create an account (they will not see your business data - everything is scoped per business and new accounts start empty), but on a public instance you may still not want open registration.
Options:
- Keep the instance on a private network or VPN.
- Put an authentication layer (your proxy's basic auth, an identity-aware proxy, or an allowlist) in front of it.
- Run it public and accept that anyone can register their own empty business.
5. Back it up
Set up the nightly dump and off-host storage described in Backups & upgrades before you start entering real data, not after.
Pre-flight checklist
-
POSTGRES_PASSWORDchanged from the default -
BETTER_AUTH_SECRETis a fresh 32+ character random value -
BETTER_AUTH_URLis your realhttps://origin - TLS terminates at a reverse proxy in front of the app
- The proxy forwards
HostandX-Forwarded-Proto - No low-entropy-secret warning in
docker compose logs app - A health check hits
/api/trpc/health.pingand getsok: true - Nightly backups run and are stored off the host
- You have decided how to handle open sign-ups
Once this list is green, you have a production instance: your data, your infrastructure, fully under your control.