Skip to main content
HARLY_URL is the canonical public origin. Use a bare origin without a path: https://hiring.example.com. Every secret must be independent — do not reuse values between variables.

HARLY_URL validation

Harly derives every public URL from HARLY_URL rather than the incoming request origin. That includes upload presign URLs, authenticated file URLs, OAuth callbacks, redirects, page metadata, outbound webhook payloads, candidate notifications, and integration callbacks. A misconfigured value therefore leaks into links your candidates and integrations receive, so Harly validates it at startup and refuses to boot when it is unsafe. In production (NODE_ENV=production), HARLY_URL must:
  • Be an absolute URL with an https:// scheme. Plain http:// is rejected.
  • Resolve to a routable public hostname. Harly rejects localhost, any *.localhost subdomain, the IPv4 loopback range 127.0.0.0/8, the IPv6 loopback ::1 (and [::1]), and the unspecified bind addresses 0.0.0.0, ::, and [::].
  • Contain no userinfo, query string, or fragment. Only scheme, host, and optional port are accepted.
Outside production, http:// is allowed so local development against http://localhost:3000 continues to work. If validation fails, Harly throws at startup with a message such as HARLY_URL must use HTTPS in production. or HARLY_URL must use a reachable public hostname, not a local or bind address.. Fix the value in .env (or your platform’s secret manager) and restart the app.
Behind a reverse proxy, set HARLY_URL to the public hostname candidates and integrations see — never the container’s internal bind address. Values like http://0.0.0.0:3000 or http://127.0.0.1:3000 are rejected in production, and even when accepted they would produce broken links in emails, webhooks, and OAuth callbacks. See proxy modes for reverse-proxy setup.

Required runtime values

npx @harly/cli generates independent values and writes .env with mode 0600. On managed platforms, store all values as secret variables rather than plain configuration. Secrets for BETTER_AUTH_SECRET, AI_ENCRYPTION_KEY, STORAGE_UPLOAD_SECRET, CRON_SECRET, and HARLY_SETUP_SECRET must contain at least 32 bytes (or 32+ bytes when base64-decoded).

Storage

Local volume

Back up the uploads volume together with PostgreSQL. Do not use local storage on platforms with ephemeral filesystems (Render, Railway, Fly.io).

S3-compatible storage

S3_ENDPOINT and S3_PUBLIC_URL are optional for AWS but required for Cloudflare R2, MinIO, and other S3-compatible providers. All four core S3 variables (S3_BUCKET, S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY) are required when STORAGE_PROVIDER=s3. Candidate files must live in a private bucket. Disable public access, object ACLs, anonymous GetObject, and public CDN origins. Harly serves resumes through an authenticated application route with Cache-Control: private, no-store; do not configure a public CDN path for workspaces/*/resumes/*.

Scheduler and metrics

The scheduler authenticates private cron routes using CRON_SECRET. Harly exposes a Prometheus-compatible metrics endpoint:
Never put either secret in a URL or query string. The scheduler handles the email outbox, domain-event replay, webhooks dispatch, interview calendar sync, mailbox sync, document expiry, retention enforcement, e-sign reconciliation, scheduled reports, and governed evaluation jobs.

Resource profiles

The CLI selects a resource profile based on the available free memory at install time. You can override any value in .env. Override individual limits in .env:

Optional integration credentials

Installation-level OAuth credentials enable each integration’s connect flow. All are optional. Workspace credentials are encrypted in PostgreSQL after an administrator connects an account. Slack delivery history is retained for 90 days by default. Operators may set SLACK_DELIVERY_RETENTION_DAYS between 30 and 730 days; the protected webhooks-dispatch cron purges completed and dead-lettered records outside that window. ID and secret must always be set together — configuring only one causes a startup validation error. See integrations overview for provider-specific redirect URIs and setup steps.

Security-sensitive settings

HARLY_ALLOW_PRIVATE_WEBHOOKS should be set to true only for a controlled internal deployment where outbound webhooks must reach loopback or private addresses. In all other deployments, leave at the default. Harly blocks loopback, private, link-local, and cloud-metadata addresses on all outbound HTTP actions. TRUSTED_PROXY_IPS lists the peer addresses of reverse proxies whose X-Forwarded-For header Harly should trust. When the immediate peer matches one of these values, Harly uses the leftmost forwarded address as the client IP. Otherwise it falls back to the rightmost hop so the header cannot be spoofed. This value drives what audit logs record for ipAddress, which address workspace IP allowlists evaluate, and how per-IP rate limits partition traffic. Leave TRUSTED_PROXY_IPS empty when Harly is exposed directly or served through the bundled Caddy proxy. Set it to the address (or comma-separated addresses) that Harly sees as the TCP peer when running behind an external reverse proxy, load balancer, or CDN. See proxy modes for concrete examples.