Skip to content

HTTPS & Domain Lock

Out of the box Pinchy accepts requests on any host over plain HTTP. That’s fine for local development but unsafe in production. This guide walks you through two steps that secure your instance: HTTPS via a reverse proxy (so all traffic is encrypted) and Domain Lock (so Pinchy only responds to your chosen domain).

HTTPS encrypts all traffic between browsers and your server. Pinchy doesn’t handle TLS itself — a reverse proxy sits in front of it and takes care of certificates. We recommend Caddy because it automatically gets and renews Let’s Encrypt certificates with zero configuration.

Terminal window
apt-get install -y caddy

Make sure your domain’s DNS A record points to your server’s IP address. Then edit the Caddy configuration:

Terminal window
nano /etc/caddy/Caddyfile

Replace the contents with (use your actual domain):

pinchy.example.com {
reverse_proxy localhost:7777
}

Save the file (Ctrl+O, Enter, Ctrl+X in nano) and restart Caddy:

Terminal window
systemctl restart caddy

Caddy automatically provisions Let’s Encrypt certificates for your domain. After a few seconds, visit https://pinchy.example.com — you should see Pinchy with a valid HTTPS certificate.

For production domains, set BETTER_AUTH_URL to the same HTTPS origin users visit. Better Auth uses it when it needs to build absolute links, including password resets, OAuth callbacks, and Telegram pairing redirects.

Edit your Pinchy .env file:

Terminal window
nano /opt/pinchy/.env

Add your public URL, including https:// and no trailing slash:

Terminal window
BETTER_AUTH_URL=https://pinchy.example.com

Restart the Pinchy container so Docker Compose passes the updated value through:

Terminal window
cd /opt/pinchy
docker compose up -d pinchy
What’s a reverse proxy and why do I need one?

A reverse proxy sits between the internet and Pinchy. When someone visits your domain:

  1. Their browser connects to Caddy on port 443 (HTTPS)
  2. Caddy handles the SSL certificate and encryption
  3. Caddy forwards the request to Pinchy on port 7777 (internal only)
  4. Pinchy sends the response back through Caddy

This way, Pinchy never needs to deal with SSL certificates directly.

See the Hardening Guide for nginx examples and advanced configuration.

Once HTTPS is working, the next step is locking Pinchy to that domain. Domain Lock pins your instance to exactly one HTTPS domain and turns on the rest of the production security profile as a package:

  • Only requests whose Host header matches the locked domain reach the app. Everything else gets a 403 Access Denied page.
  • Cookies are issued with Secure and SameSite=Lax.
  • HSTS is advertised so modern browsers refuse to connect over plain HTTP.
  • Origin checks are enforced on state-changing requests.

You don’t have to configure any of these individually — locking the domain flips them all on.

  1. Sign in as an admin on the HTTPS domain you want to lock to
  2. Go to Settings → Security
  3. The page shows your current host and whether the connection is HTTPS
  4. Click Lock to this domain
  5. Pinchy restarts briefly to apply the new security profile — the page will reload automatically

From this point on, only the locked domain can reach the app. Use the same hostname for the lock and for BETTER_AUTH_URL; the lock controls which host Pinchy accepts, while BETTER_AUTH_URL controls the absolute URLs Better Auth generates.

If the business needs change (different domain, temporary maintenance URL, etc.):

  1. Go to Settings → Security
  2. Click Remove domain lock
  3. Confirm — the restart happens the same way as on lock

If HTTPS goes down (expired certificate, proxy misconfiguration, DNS change) you can find yourself unable to reach the admin UI at all. Pinchy includes a last-resort CLI to clear the lock from inside the container:

Terminal window
docker exec pinchy pnpm domain:reset

The command needs shell access to the Pinchy container, so protect it like any other production admin credential. After it runs, Pinchy accepts any host again and you can reach the UI on the non-HTTPS or fallback URL to diagnose the underlying problem.

Don’t use domain lock in development. It is designed for a single deployed instance on a known domain, not for localhost or Docker dev stacks. The Security tab is still visible but it refuses to lock when the current connection is not HTTPS.