···11-# Tranquil PDS Configuration Reference
22-33-Every option in `example.toml` is overridable by an environment variable. This document focuses on what each setting *does* and why some of them are critical to get right before you start. The install guides (`install-containers.md`, `install-kubernetes.md`, `install-nix.md`) cover the mechanics of standing up the service; this covers what to put in it.
44-55-## Configuration methods
66-77-The server accepts config in two forms, applied in this order (later wins):
88-99-1. A TOML file, defaulting to `/etc/tranquil-pds/config.toml`. Override the path with `TRANQUIL_PDS_CONFIG`.
1010-2. Environment variables. Every TOML key has a corresponding env var documented in `example.toml`.
1111-1212-In practice: TOML for static, non-sensitive settings; env vars (injected from secrets) for anything that would be unfortunate in a file you may want to commit.
1313-1414----
1515-1616-## Required settings
1717-1818-These have no defaults and the Tranqil will not start without them.
1919-2020-### `PDS_HOSTNAME`
2121-2222-The public hostname of your PDS. Example: `pds.example.com`.
2323-2424-This is used in DID documents, OAuth metadata, and the `/.well-known/atproto-did` endpoint. Getting it wrong after you have accounts is painful: DIDs are registered against this hostname in the PLC directory, and changing it requires a PLC rotation operation. Set it correctly before creating any accounts.
2525-2626-If your PDS hostname is also the apex domain (e.g. `example.com` rather than `pds.example.com`), users can have their handle *be* the apex domain (e.g. `@example.com`) since the PDS serves `/.well-known/atproto-did` directly without needing a DNS TXT record.
2727-2828-### `DATABASE_URL`
2929-3030-PostgreSQL connection string. Example: `postgres://tranquil_pds:password@host:5432/pds`
3131-3232-Tranquil PDS uses Postgres for all persistent state: accounts, repos, sessions, the comms queue, and (when using the default `postgres` repo backend) the full block store for every account's atproto repository.
3333-3434----
3535-3636-## Required secret/sensitive env vars
3737-3838-These three are the most sensitive values in the entire deployment. They should never appear in a committed file, in any circumstance.
3939-4040-### `JWT_SECRET`
4141-4242-Used to sign and verify all session JWTs issued by the PDS.
4343-4444-Generate: `openssl rand -base64 48`
4545-4646-### `DPOP_SECRET`
4747-4848-Used to validate DPoP (Demonstrating Proof of Possession) proofs in OAuth flows. DPoP is the binding mechanism that ties an OAuth access token to the client's key pair, preventing token theft from replaying the token with a different client.
4949-5050-Generate: `openssl rand -base64 48`
5151-5252-### `MASTER_KEY`
5353-5454-Used as input to HKDF key derivation and for encrypting per-account signing keys at rest. Every account's atproto repo signing key is derived from or wrapped with this value.
5555-5656-Generate: `openssl rand -base64 48`
5757-5858----
5959-6060-## Handle domains
6161-6262-### `PDS_USER_HANDLE_DOMAINS`
6363-6464-Comma-separated list of domains under which handles are issued. Defaults to `PDS_HOSTNAME` if unset.
6565-6666-If your PDS hostname is `pds.example.com` but you want handles like `alice.example.com`, set this to `example.com`. You need a wildcard TLS cert and DNS wildcard record for the domain(s) you list here.
6767-6868-### `PDS_BANNED_WORDS`
6969-7070-Comma-separated list of substring patterns blocked at registration time. Useful to prevent handles that would collide with your infrastructure (e.g. `grafana`, `vault`, `relay`). The server has a built-in exact-match reserved list (`admin`, `api`, `git`, `mail`, `ssh`, etc.); this adds substring matches for anything missing from that list.
7171-7272-### `INVITE_CODE_REQUIRED`
7373-7474-Default `true`. Keep this enabled on any public-facing PDS unless you specifically want open registration. Invite codes are generated via the admin API/UI.
7575-7676----
7777-7878-## Blob storage
7979-8080-### `BLOB_STORAGE_BACKEND`
8181-8282-`filesystem` (default) or `s3`. Pretty straight forward, and dependent on your personal infrastructure requirements and availability.
8383-8484-### `BLOB_STORAGE_PATH`
8585-8686-Path on disk for the filesystem backend. Default `/var/lib/tranquil-pds/blobs`.
8787-8888-For S3: set `S3_BUCKET` and optionally `S3_ENDPOINT` for S3-compatible stores (Tigris, Cloudflare R2, MinIO, etc.).
8989-9090----
9191-9292-## Federation
9393-9494-### `CRAWLERS`
9595-9696-Comma-separated list of relay URLs to notify when new events are committed to an account's repo. Often `https://bsky.network` for production, this is what allows your PDS to be federated to on the wider network.
9797-9898----
9999-100100-## Email
101101-102102-Email is sorta optional but required for account verification, password resets, and 2FA backup codes. If `MAIL_FROM_ADDRESS` is not set, email sending is disabled entirely.
103103-104104-Tranquil supports two delivery modes:
105105-106106-### Smarthost (SMTP relay)
107107-108108-Set `MAIL_SMARTHOST_HOST`, `MAIL_SMARTHOST_PORT`, `MAIL_SMARTHOST_USERNAME`, and `MAIL_SMARTHOST_PASSWORD`. Use this if you're routing through a sending service (Postmark, SES, etc.) or a local MTA.
109109-110110-TLS mode defaults to `starttls`. Setting `tls = "none"` alongside a password is rejected at startup.
111111-112112-### DKIM signing (`email.dkim`)
113113-114114-Set `MAIL_DKIM_SELECTOR`, `MAIL_DKIM_DOMAIN`, and `MAIL_DKIM_KEY_PATH` to sign outgoing mail. The key file should be PEM-format RSA or Ed25519. The corresponding DNS TXT record at `<selector>._domainkey.<domain>` must be published before mail is sent.
115115-116116----
117117-118118-## Notifications
119119-120120-Beyond email, Tranquil supports Discord, Telegram, and Signal for user notifications. Each is opt-in:
121121-122122-- **Discord**: Set `DISCORD_BOT_TOKEN`.
123123-- **Telegram**: Set `TELEGRAM_BOT_TOKEN` and `TELEGRAM_WEBHOOK_SECRET`.
124124-- **Signal**: Set `SIGNAL_ENABLED=true` after linking a device via the admin API. State is persisted in `signal_*` tables in Postgres.
125125-126126----
127127-128128-## SSO
129129-130130-OAuth SSO is supported for GitHub, Discord, Google, GitLab, generic OIDC, and Apple. Each provider requires `enabled = true`, a `client_id`, and a `client_secret`. GitLab and OIDC also need an `issuer` URL.
131131-132132-`client_secret` values should be injected as env vars (`SSO_GITHUB_CLIENT_SECRET`, etc.) and not placed in config files.
133133-134134----
135135-136136-## Cache backend
137137-138138-### `CACHE_BACKEND`
139139-140140-`ripple` (default) or `valkey`. Ripple is the a built-in in-process gossip cache. Valkey is an external Redis fork with it's own complications.
141141-142142----
143143-144144-## Optional keys worth knowing
145145-146146-| Env var | Default | Notes |
147147-|---|---|---|
148148-| `PLC_ROTATION_KEY` | — | Operator-level PLC rotation key (DID key). If unset, per-user keys are used. |
149149-| `ENABLE_PDS_HOSTED_DID_WEB` | `false` | Opt-in did:web hosting. Long-term commitment — only enable if you intend to serve DID documents indefinitely. |
150150-| `MAX_BLOB_SIZE` | 10 GiB | Per-blob upload limit in bytes. |
151151-| `DISABLE_RATE_LIMITING` | `false` | Only for testing. Avoid in production. |
152152-| `DATABASE_MAX_CONNECTIONS` | 100 | Connection pool ceiling. Tune against your Postgres `max_connections`. |
153153-| `FIREHOSE_BACKFILL_HOURS` | 72 | How many hours of history relays can replay from cursor. |
154154-| `REPO_BACKEND` | `postgres` | `tranquil-store` is the experimental embedded backend. Careful if attempting to run in production. |