[READ-ONLY] Mirror of https://github.com/thoda-dev/shhh. Self-hostable zero-knowledge pastebin for secrets that expire on their own
docker end-to-end-encryption nuxt nuxtjs pastebin secrets selft-hosted zero-knowledge
shhh apps docs content 2.self-hosting 2.configuration.md
5.5 kB
Markdown


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