title: Configuration description: Environment variables for secrets and infrastructure, admin dashboard for everything else. navigation: icon: i-lucide-sliders-horizontal #
Configuration is split in two, deliberately.
- Environment variables hold secrets and infrastructure. Changing them means a restart.
- The admin dashboard holds every operational limit, stored in the database and editable live.
No credential is ever stored in the database, so the dashboard cannot leak one.
Environment variables #
Required #
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
BETTER_AUTH_SECRET |
Signs session cookies. openssl rand -base64 32. Changing it logs everybody out |
BETTER_AUTH_URL |
Public URL of the instance, no trailing slash. Also used to build emailed links |
Deployment #
| Variable | Default | Description |
|---|---|---|
TRUSTED_PROXY_DEPTH |
0 |
Proxies you control in front of the app. 0 ignores X-Forwarded-For and uses the connection address |
AUTO_BAN_DURATION_HOURS |
72 |
How long an automatic ban lasts. 0 bans permanently |
HEALTH_TOKEN |
(empty) | Unlocks the mail provider and storage fields on /api/health |
Automatic bans are the ones the middleware places on probe paths and untrusted bots. Bans an admin places by hand from the dashboard are always permanent and are never shortened by this setting.
Anti-bot #
| Variable | Description |
|---|---|
NUXT_PUBLIC_TURNSTILE_SITE_KEY |
Cloudflare Turnstile site key |
NUXT_TURNSTILE_SECRET_KEY |
Cloudflare Turnstile secret key |
Paste creation requires a valid token from every tier, signed-in users included — a stolen account is still a bot vector. Create a widget in the Cloudflare dashboard and add your domain to its allowed hostnames.
::warning
Add localhost to the widget's hostnames if you develop against a real key, otherwise the widget
errors out in the browser with "cannot connect". A hard refresh is needed after changing it, since
the Turnstile script caches.
::
Mail #
| Variable | Description |
|---|---|
MAIL_PROVIDER |
resend, smtp or none |
MAIL_FROM |
Sender address, required unless none |
RESEND_API_KEY |
Resend only |
MAIL_SMTP_HOST MAIL_SMTP_PORT |
SMTP only |
MAIL_SMTP_SECURE |
true for implicit TLS (usually port 465), false for STARTTLS on 587 |
MAIL_SMTP_USER MAIL_SMTP_PASS |
Leave both empty for a relay that takes no credentials |
none is a supported configuration, not an error state. The instance runs fine without mail, but:
- email verification is not required and not sent,
- password reset links are unavailable — resetting becomes a manual admin action,
- invitations and server-side email sharing are disabled,
- changing your own email address is disabled.
The app refuses to start if MAIL_PROVIDER is set to something it doesn't recognise, or if a
required variable for the chosen provider is missing.
Other #
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Host port the app is published on |
SKIP_MIGRATIONS |
false |
Set to true if migrations are handled by a separate deployment step |
MIGRATIONS_DIR |
/app/migrations |
Where the .sql files live |
Admin settings #
Everything below lives in the database, at /admin/settings. Any numeric limit can be set to
unlimited.
Retention and reads #
| Setting | Default | |
|---|---|---|
max_retention_days_anonymous |
7 | Longest lifetime an anonymous paste may request |
max_retention_days_authenticated |
30 | Same, for signed-in users |
max_reads_anonymous |
unlimited | Cap on decryptions per paste |
max_reads_authenticated |
unlimited | Same, for signed-in users |
Sizes and quotas #
| Setting | Default | |
|---|---|---|
max_text_size_bytes |
100 000 | Per paste, measured on the ciphertext |
max_upload_size_bytes |
2 000 000 | Per file |
max_total_pastes |
unlimited | Instance-wide count |
max_total_storage_bytes |
40 GB | Instance-wide stored bytes |
Instance quotas are checked live against the database on every creation, so they cannot drift.
Creation returns 503 when the paste limit is reached and 507 when the storage quota is.
Rate limits #
| Setting | Default | |
|---|---|---|
rate_limit_anonymous_creates_per_period |
unlimited | Per IP |
rate_limit_authenticated_creates_per_period |
unlimited | Per account |
rate_limit_uploads_per_period |
unlimited | Stricter cap, uploads only |
rate_limit_period_minutes |
10 | Window shared by all three |
Exceeding a limit returns 429 with a Retry-After header.
Access #
| Setting | Default | |
|---|---|---|
registration_enabled |
on | Public sign-up. Invitations bypass this |
public_paste_enabled |
on | Anonymous creation. Turning it off makes the instance accounts-only; reading stays open |
require_2fa |
off | Forces every account to enrol in TOTP |
::warning
Enabling require_2fa while your own account has no TOTP enrolled locks you out of every admin
route, including the one that would turn it back off. Enrolling from /account stays available and
is the way back — but enrol first, then enable.
::
Invitations and email #
| Setting | Default | |
|---|---|---|
invitation_expiry_days |
7 | Lifetime of an invitation link |
max_email_recipients_per_paste |
3 | Recipients on a single share |