Skip to content

Reverse proxy and client IPs

Day Planner listens on plain HTTP on port 8303. Put a reverse proxy in front of it for HTTPS, and tell Day Planner how the proxy passes on the visitor’s IP address, so sign-in rate limits apply per visitor.

This Caddyfile gets a certificate automatically and forwards to the app.

tasks.example.com {
reverse_proxy localhost:8303
}

Set BETTER_AUTH_URL=https://tasks.example.com to match. Caddy adds X-Forwarded-For and X-Forwarded-Host by default.

Rate limits count requests per visitor IP. Behind a proxy, every request comes from the proxy, so Day Planner reads the visitor’s IP from a header instead. Pick the case that matches your setup:

Setup Settings
One proxy (Caddy, nginx, Traefik) on the same server Defaults are fine: CLIENT_IP_HEADER=x-forwarded-for. Add the proxy’s address to TRUSTED_PROXIES if it isn’t 127.0.0.1, for example the Docker network 172.16.0.0/12.
nginx setting X-Real-IP CLIENT_IP_HEADER=x-real-ip
Cloudflare in front, and the server only reachable through Cloudflare CLIENT_IP_HEADER=cf-connecting-ip
Cloudflare in front, but the server also reachable directly Keep x-forwarded-for and list your proxy in TRUSTED_PROXIES. Anyone who bypasses Cloudflare could otherwise set CF-Connecting-IP to anything.

With x-forwarded-for, Day Planner reads the list of addresses from right to left and skips the ones in TRUSTED_PROXIES. The first address that isn’t a trusted proxy is the visitor. A visitor can’t fake it, because they can only add entries to the left.

Forms such as setup and Settings use Next.js Server Actions, which check that the Origin header matches the host. Most proxies pass X-Forwarded-Host correctly, so this works without extra configuration. If a form fails with “Invalid Server Actions request” in the logs, make sure your proxy forwards the original Host.