Configuration
The node is configured through TAVRN_* environment variables; this section explains how they are loaded and lists
every variable by topic.
How the configuration is loaded
Section titled “How the configuration is loaded”Every setting is a TAVRN_* environment variable; in the compose setup they come from .env
(deploy/.env.example is the annotated template).
The node reads, later sources overriding earlier ones:
- the built-in defaults (the tables below),
- an optional TOML file named by
TAVRN_CONFIG(keys are the variable names withoutTAVRN_in lowercase, e.g.registration = "open"), - the
TAVRN_*environment variables.
Every value is then validated, and all problems are printed together (Configuration error: with one line
per problem and the name of the variable); the process exits with code 2. Check a configuration without starting
the server: docker compose exec tavrn tavrn-node check-config (prints the effective configuration as JSON; the
passwords of the database, SMTP and Valkey URLs, the Turnstile secret, the GIF key, the OIDC client secret and the
metrics token are never printed).
On/off switches accept true/false; those documented as on | off accept on/off as well as
true/false. Lists are comma-separated. Sizes are in bytes.
--dev (or TAVRN_DEV=true) starts a development node with different defaults: domain localhost, listen
127.0.0.1:8080, SQLite in data-dev/ (database sqlite://data-dev/tavrn.db?mode=rwc), data directory
data-dev, registration open, room creation local, log level debug,sqlx=info, no reference nodes,
TAVRN_RTC_TCP_PORT=0, private application URLs allowed and 1000 legal notices per hour; http:// public URLs
and OIDC issuers, TAVRN_FEDERATION_DEV_PEERS and TAVRN_DEV_MISBEHAVE are only accepted there. Never run
--dev in production.
Node identity, addresses and database
Section titled “Node identity, addresses and database”These settings fix the node’s identity domain, where it is served and which database it uses.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_DOMAIN | required | Identity domain of the node: addresses alice@<domain>, node keys, tokens. Lowercase, without scheme, path or port. Cannot be changed later without losing the node’s identity. |
TAVRN_PUBLIC_URL | https://<TAVRN_DOMAIN> | Where the node (web client, API, /rtc) is served: https://<host>[:port], no path, query or fragment (http:// and IP addresses only with --dev). Everything that builds absolute URLs uses it (delegation). |
TAVRN_NAME | tavrn | Display name of the node (directory, discovery document). |
TAVRN_DESCRIPTION | empty | Short description of the node (directory). |
TAVRN_LISTEN | 0.0.0.0:8080 | Address and port of the plain-HTTP server. |
TAVRN_TRUSTED_PROXIES | 127.0.0.0/8,::1/128 | IPs or CIDRs whose X-Forwarded-For is trusted. For connections from elsewhere the connection’s own address counts (rate limits, captcha), not the header. Also decides who may read /metrics without a token. List your reverse proxy here when it does not connect over loopback (another host, a container network); the compose file sets Docker’s private ranges, because there the proxy reaches the node through Docker’s gateway address. Never list networks that clients reach the node from directly: every address listed may choose its rate-limit identity. |
TAVRN_DATABASE_URL | empty (compose: built from POSTGRES_PASSWORD) | postgres://user:password@host:port/db (or postgresql://). sqlite: only with --dev. |
TAVRN_DATA_DIR | /data (image) | Directory for keys/, blobs/ (stored files) and the generated LiveKit configuration. |
TAVRN_ADMIN_USERS | empty | Comma-separated usernames with node admin rights (besides the first registered account). |
TAVRN_CONFIG | unset | Path of an optional TOML configuration file (see above). |
Registration, accounts and sessions
Section titled “Registration, accounts and sessions”These settings decide who may create an account and how, which mail and captcha services take part, how passkeys and sessions work and the minimum age.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_REGISTRATION | invite | open, invite (account invites from the node administration) or closed. closed only blocks new accounts, never federation. |
TAVRN_EMAIL | optional | none, optional or required (required needs TAVRN_SMTP_URL). With required an account signs in with its password only after confirming its address through the mailed link (<public URL>/verify-email?token=…, valid 24 hours); the device that registered stays signed in, and the login page offers to send the mail again. |
TAVRN_SMTP_URL | unset | smtp://host:port (plain, internal relay), smtps://host:port (TLS) or smtp+starttls://host:port, optionally user:pass@. Test it with tavrn-node send-test-mail you@example.org. |
TAVRN_SMTP_FROM | noreply@<TAVRN_DOMAIN> | Sender address. |
TAVRN_MAIL_TEMPLATES_DIR | <TAVRN_DATA_DIR>/templates/mail | Your own texts for the node’s mails, per language: <dir>/<language>/<name>.txt. See Mail languages and templates. |
TAVRN_CAPTCHA_PROVIDER | none | none or turnstile (Cloudflare Turnstile). |
TAVRN_TURNSTILE_SITE_KEY, TAVRN_TURNSTILE_SECRET_KEY | unset | Required with TAVRN_CAPTCHA_PROVIDER=turnstile. |
TAVRN_CAPTCHA_REGISTER | true | Require the captcha at registration (only with a provider). |
TAVRN_CAPTCHA_LOGIN | false | Require the captcha at login (only with a provider). |
TAVRN_WEBAUTHN_ORIGIN | the public URL | Origin of the web client for passkeys. Override only for development and tests (e.g. http://localhost:5173). |
TAVRN_WEBAUTHN_RP_ID | TAVRN_DOMAIN | Relying party id of passkeys; must be the host of the origin or a parent domain of it, otherwise the node refuses to start. Changing it later invalidates every registered passkey (Passkeys). |
TAVRN_WEBAUTHN_APP_ORIGINS | empty | WebAuthn origins of your own apps besides the web origin, comma-separated: android:apk-key-hash:<base64url SHA-256 of the signing certificate> per Android signing key. Lets the Android app use passkeys in the app (Passkeys in the apps); published as webauthn.origins. Invalid entries stop the start. |
TAVRN_SESSION_ACCESS_MINUTES | 60 | Access token lifetime (5–1440). A revocation (logout, password change or reset, device removal, an admin disabling the account or resetting its second factor, a node ban, account deletion) ends the access tokens and WebSockets of the affected devices at once (401 auth.session_revoked, close code 4001), and their voice and call connections; the node checks the session behind a token at most every 5 seconds per device and instance. |
TAVRN_SESSION_REFRESH_DAYS | 90 | Sliding refresh token lifetime (1–3650); every refresh extends it. SSO sessions keep TAVRN_OIDC_SESSION_DAYS as their hard end, federated sessions their 7 days. |
TAVRN_RATE_USER_PER_MINUTE | 1200 (0 in development mode) | API requests per user and minute over every authenticated route and WebSocket connect (429 with Retry-After beyond); 0 switches the general limit off. Route limits (login, uploads, messages, …) apply on top. |
TAVRN_RATE_LIMIT_IPV6_PREFIX | 64 | Per-address rate limits (login, registration, password reset, connect, directory, the emoji and sound proxy, …) count IPv6 clients by this prefix (32–128): one host usually holds a whole /64. IPv4 addresses count one by one. |
TAVRN_PASSWORD_HASH_CONCURRENCY | 0 (twice the CPU cores, at least 2) | Password hashes and checks (Argon2id, 64 MiB each) running at once, off the request threads; further logins wait for a slot. |
TAVRN_WS_MAX_SOCKETS_PER_USER | 20 | WebSocket connections one user may hold at once per instance (0 = unlimited); beyond it the socket is closed with 4002 too_many_connections. App tokens keep their own limit of two. |
TAVRN_DEVICE_STALE_DAYS | 180 | Devices unused this long are deleted by the hourly housekeeping (never a user’s only device, never one with a live session); 0–36500, 0 = never. An account has at most 100 devices (clients refuse a keys document with more): a login that would create the 101st is refused with 403 auth.device_limit until the user removes a device they no longer use (device settings of a signed-in device; logging out with “forget this device” removes it too) or the housekeeping deletes stale ones. |
TAVRN_E2EE_CLAIMS_PER_TARGET_PER_HOUR | 60 | KeyPackage claims for one user per hour from one source (this node, or one other node) by requesters who share no room, DM, group DM or friendship with the user (1–10000); ten times that per user over all sources. Requesters who share one of these, and a user’s claims for their own devices, are not counted. Protects users from having their KeyPackages drained by claims in many names without locking out their contacts. |
TAVRN_MINIMUM_AGE | 16 | Minimum age for registration (13–99). Every registration path asks for the date of birth and refuses younger people; see Age gates. |
TAVRN_DM_POLICY | everyone | Upper bound of every user’s “Who can message me”: friends, everyone_shared (friends and shared rooms) or everyone (open is the old name). Users choose within it (their default is everyone_shared); non-friends land in the recipient’s message requests; blocks always apply. |
The second factor (TOTP or passkeys) is mandatory for every account and cannot be switched off.
The desktop app signs in on the node’s own web client (app sessions of the connect flow, FEDERATION.md §8 step 3,
capability connect.app): the user signs in or registers there with any method, including passkeys, single sign-on
and the captcha, and the app picks up the session. A passkey used there also unlocks the app’s encryption keys: the
page seals the passkey’s unlock secret to a key only the app holds, and the node keeps that sealed box (at most
1 KiB, never logged, handed out once) until the app picks it up. There is nothing to configure; /connect must be
served like every other page of the web client.
Mail languages and templates
Section titled “Mail languages and templates”The node’s mails (email confirmation, password reset, account deleted and restored, takedown notices and statements of reasons, app token expiry) are written in the recipient’s language: the language the user picked in the client (the client stores it on the node; a new account starts with the browser’s language), for reporters of takedown notices the browser language of the notice form. The node has English and German texts; any other language falls back to English.
The texts are the templates in templates/mail/<language>/<name>.txt of this repository:
Subject: Reset your password
Hello {name},
you can set a new password via this link:{link}The first line is the subject (the node puts its name in front), the second is empty, the rest is the plain-text
body. {name}-style variables are filled in; every template knows {node} (TAVRN_NAME), {domain} and
{public_url} plus its own (check-config names them when one is wrong):
| Template | Sent when | Own variables |
|---|---|---|
verify_email | an account registers with an e-mail address, or asks for the confirmation mail again (“Send the confirmation mail again” on the login page: a new link, at most three per account and hour) | {name}, {link} |
password_reset | a user asks for a password reset | {name}, {link} |
account_deleted, account_deleted_by_admin | the user or a node admin deleted the account | {address}, {purge_after} |
account_restored | a node admin restored a deleted account | {address} |
app_token_expiring_week, app_token_expiring_day | an application token expires in seven days or in one day | {name}, {application}, {expires_at}, {link} |
notice_received, notice_forwarded_in | a takedown notice arrived (from the form or forwarded by another node) | {case}, {link} or {origin} |
notice_content_removed, notice_account_disabled, notice_rejected, notice_forwarded | the decision on a notice, for its reporter | {case}, {reason} or {nodes} |
statement_content_removed, statement_account_disabled | the statement of reasons for the uploader | {case}, {basis}, {statement}, {deadline} |
counter_notice_received, counter_restored, counter_upheld | a counter-notice and the decision on it | {case}, {statement} and {signature} or {reason} |
The language is chosen per recipient: a user whose language is de-AT gets de-at if there is such a template,
otherwise de, otherwise English. To change a text, copy the file to
TAVRN_MAIL_TEMPLATES_DIR (default /data/templates/mail) under the same language and name, for example
/data/templates/mail/de/password_reset.txt, and edit it; you can also add a language the node has no texts for
(/data/templates/mail/fr/…, used for users whose language is fr or fr-CA). The files are read when a mail is
sent, so changes apply without a restart. tavrn-node check-config validates the directory (unknown names, a
missing subject line, unknown variables, files over 64 KiB); a file that is broken when a mail goes out is skipped
with a warning and the built-in text is sent instead.
Single sign-on (OpenID Connect)
Section titled “Single sign-on (OpenID Connect)”With an OpenID Connect provider configured, people sign in with the provider’s accounts, and its groups can be mapped to roles.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_OIDC_ISSUER | unset | Issuer URL of the OpenID provider; SSO is on when set. https:// only (http:// with --dev), must match the provider’s issuer exactly. |
TAVRN_OIDC_CLIENT_ID | unset | Client id at the provider (required with an issuer). |
TAVRN_OIDC_CLIENT_SECRET | unset | Client secret of a confidential client (never printed). Use this or the file variant. |
TAVRN_OIDC_CLIENT_SECRET_FILE | unset | File holding the client secret (keeps it out of the environment), e.g. /data/keys/oidc-client-secret. |
TAVRN_OIDC_SCOPES | openid profile email groups | Requested scopes (space or comma separated); must contain openid. |
TAVRN_OIDC_USERNAME_CLAIM | preferred_username | Claim the username of new accounts comes from (sanitized, -2 appended on a collision). |
TAVRN_OIDC_DISPLAY_NAME_CLAIM | name | Claim the display name comes from (updated on every login). |
TAVRN_OIDC_GROUPS_CLAIM | groups | Claim holding the groups (array of strings), from the ID token or UserInfo. |
TAVRN_OIDC_BUTTON_LABEL | empty | Text of the login button (at most 64 characters); empty: the client’s “Sign in with SSO”. |
TAVRN_OIDC_ONLY | false | true: password and passkey logins and registration only for TAVRN_ADMIN_USERS (break-glass accounts). |
TAVRN_OIDC_AUTO_PROVISION | true | Create accounts on the first SSO login. |
TAVRN_OIDC_GROUP_MAP | unset | JSON group → rights map, e.g. {"it-admins":{"node_admin":true},"staff":{"room_creation":5}}; when set, the admin API cannot change it. |
TAVRN_OIDC_GROUP_SYNC | add | add grants only; sync also revokes what an earlier sync granted (never manual grants). |
TAVRN_OIDC_SESSION_DAYS | 7 | Hard end of an SSO session in days (1–30). |
Setting it up with a provider (protocol: APPLICATIONS.md §8):
-
Create a confidential OAuth2/OpenID client at the provider with the authorization code flow and PKCE (S256) and register exactly this redirect URI:
<TAVRN_PUBLIC_URL>/api/v1/auth/oidc/callback(by defaulthttps://<TAVRN_DOMAIN>/api/v1/auth/oidc/callback). As post-logout redirect URI register<TAVRN_PUBLIC_URL>/login. Sign ID tokens with RS256, PS256, ES256 or EdDSA (never HS256). Add agroupsclaim if you want the group mapping.- Keycloak: client type OpenID Connect, client authentication on, standard flow only, “PKCE method”
S256. For groups add a “Group Membership” mapper (token claim name
groups, “Full group path” off). Issuer:https://keycloak.example/realms/<realm>. - Authentik: provider type “OAuth2/OpenID Provider”, client type confidential, signing key set (RS256).
The default scope mappings include
groups. Issuer:https://authentik.example/application/o/<slug>/(with the trailing slash, exactly as shown in the provider’s overview).
- Keycloak: client type OpenID Connect, client authentication on, standard flow only, “PKCE method”
S256. For groups add a “Group Membership” mapper (token claim name
-
Configure the node (secrets preferably through a file):
Terminal window TAVRN_OIDC_ISSUER=https://keycloak.example/realms/companyTAVRN_OIDC_CLIENT_ID=tavrnTAVRN_OIDC_CLIENT_SECRET_FILE=/data/keys/oidc-client-secret# optional:TAVRN_OIDC_BUTTON_LABEL=Sign in with Company SSOTAVRN_OIDC_GROUP_MAP={"it-admins":{"node_admin":true},"staff":{"room_creation":5},"devs":{"rooms":[{"room":"01J…","roles":["01J…"]}]}}TAVRN_OIDC_GROUP_SYNC=add # sync also revokes what an earlier sync granted (never manual grants)TAVRN_OIDC_ONLY=false # true: password logins only for TAVRN_ADMIN_USERS (break-glass) -
Check it:
docker compose exec tavrn tavrn-node check-configfetches the provider’s discovery document and keys and prints the redirect URI to register.
Notes: provider requests use verified TLS, follow no redirects and may target LAN addresses. An unreachable
provider never stops the node (it is contacted on demand). SSO logins need no tavrn second factor (the provider
is responsible), but end-to-end encryption still asks for the recovery key on the first login. Existing password
accounts link their provider identity themselves in Settings → Security (the node never links by e-mail or
username). Without TAVRN_OIDC_GROUP_MAP admins edit the mapping through PUT /api/v1/admin/sso/group-map;
group changes take effect at the next SSO login, at the latest after TAVRN_OIDC_SESSION_DAYS. With
TAVRN_OIDC_ONLY=true keep at least one account in TAVRN_ADMIN_USERS, so an outage of the provider cannot lock
you out; those names are never given to accounts created by SSO. TAVRN_OIDC_CLIENT_ID, TAVRN_OIDC_ONLY or
TAVRN_OIDC_GROUP_MAP without an issuer is a configuration error.
Rooms, uploads and storage
Section titled “Rooms, uploads and storage”These settings limit who creates rooms and how many, how large uploads may be and how stored files are kept.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_ROOM_CREATION | invite | Who may create rooms: none (node admins only), invite (node admins and users with a room-creation grant), local (every local user) or federated (also users of other nodes signed in through federation). |
TAVRN_MAX_ROOMS_PER_USER | 5 | Rooms a user may own (entitlement rooms.max_owned; 0–1000000, 0 = unlimited). |
TAVRN_UPLOADS_ENABLED | true | false switches file uploads off. |
TAVRN_UPLOAD_MAX_BYTES | 26214400 (25 MiB) | Largest upload (entitlement upload.max_bytes); must be > 0 while uploads are on. The proxy must allow bodies at least this large. |
TAVRN_UPLOAD_ALLOWED_TYPES | image/*,video/*,audio/*,application/pdf,text/plain,application/zip | MIME types allowed for attachments in unencrypted conversations (the declared type; wildcards like image/* allowed; others get 415 upload.type_not_allowed). Encrypted uploads and files channels are limited by the upload categories only (Who may upload what). |
TAVRN_UPLOAD_ALLOW_EXECUTABLES | false | Executables, scripts, installers and disk images (exe, msi, bat, sh, jar, apk, dmg, iso, …; PROTOCOL.md §7a) are refused unless this is true and the member’s upload categories (role setting) include other. |
TAVRN_UPLOAD_DM_CATEGORIES | images,audio,video,documents,archives,other | Upload categories in DMs and group DMs hosted here (rooms set them per role and channel). |
TAVRN_STORAGE_QUOTA_PER_ROOM_BYTES | 10737418240 (10 GiB) | Storage per room (entitlement storage.max_bytes_per_room). |
TAVRN_STORAGE_ENCRYPTION | on | Encryption at rest of every stored file; off stores new files unencrypted (debugging only; encrypted files stay readable). See Storage and media. |
TAVRN_RETENTION_DAYS | 0 | Message retention: messages of channels, DMs and group DMs hosted here are deleted this many days after they were sent by the hourly housekeeping, with their attachments, reactions and pins. Pinned messages and the welcome channel’s entries are kept. Rooms may only shorten it (room setting). 0–36500, 0 = keep forever. See Message retention. |
TAVRN_EMOJI_MAX_PER_ROOM | 100 | Custom emojis per room (entitlement emoji.max_per_room). |
TAVRN_EMOJI_MAX_BYTES | 262144 (256 KiB) | Largest custom emoji file. |
TAVRN_PREVIEW_AUTO_MAX_BYTES | 5242880 (5 MiB) | Images up to this size load as previews by themselves where the user’s setting allows it (always / friends and shared rooms / never); larger ones show a placeholder with a “Load” button. 0 = never automatically. |
TAVRN_MEDIA_CACHE_UNUSED_DAYS | 365 | Cached copies of other nodes’ custom emojis and soundboard clips are evicted after this many days without use; 0 = never. |
TAVRN_MEDIA_CACHE_MAX_BYTES | 2147483648 (2 GiB) | Total size of those cached copies; beyond it the least recently used go (checked at most every 10 seconds per instance and by the hourly housekeeping). |
TAVRN_MEDIA_CACHE_MAX_BYTES_PER_HOST | 67108864 (64 MiB) | Size of the cached copies from one origin node (at most TAVRN_MEDIA_CACHE_MAX_BYTES); beyond it that node’s least recently used copies go. Fetches for copies not cached yet are limited to 60 per client address and minute. |
Entitlement defaults
Section titled “Entitlement defaults”Entitlements (protocol: ENTITLEMENTS.md) are the per-user and per-room limits. The values here and the ones
marked “entitlement” above are the node defaults; node admins extend them per user or room with plans and grants
(admin page “Entitlements” or the admin API with a tavrn_adm_ token). 0 = unlimited unless noted.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_UPLOAD_MAX_BYTES_CEILING | 0 | Largest upload a grant can allow; 0 = TAVRN_UPLOAD_MAX_BYTES (grants cannot raise it). Otherwise at least TAVRN_UPLOAD_MAX_BYTES; also the body limit of the upload routes. |
TAVRN_VOICE_CHANNEL_MAX_PARTICIPANTS | 0 | voice.max_participants per voice channel (0–1000; 0 = only TAVRN_VOICE_MAX_PARTICIPANTS). |
TAVRN_STREAM_MAX_HEIGHT | 0 | stream.max_height, screen share height in pixels (0–4320; 0 = only TAVRN_SCREENSHARE_MAX_RESOLUTION). Enforced by the clients. |
TAVRN_STREAM_MAX_FPS | 0 | stream.max_fps (0–240; 0 = only TAVRN_SCREENSHARE_MAX_FPS). Enforced by the clients. |
TAVRN_CAMERA_MAX_HEIGHT | 0 | camera.max_height in pixels (0–4320; 0 = only TAVRN_CAMERA_MAX_RESOLUTION). Enforced by the clients. |
TAVRN_EMOJI_MAX_PERSONAL | 20 | emoji.max_personal, personal emojis per user (0–100000). |
TAVRN_SOUNDS_MAX_PER_ROOM | 50 | sounds.max_per_room, soundboard clips per room (0–100000). |
TAVRN_SOUNDS_MAX_PERSONAL | 10 | sounds.max_personal, personal soundboard clips (0–100000). |
TAVRN_ATTACHMENT_RETENTION_DAYS | 0 | attachments.retention_days: attachments of channels and DMs are deleted this many days after the upload by the hourly housekeeping; messages keep listing them as “file expired”. Pinned messages keep their files. Rooms may only shorten it. 0–36500, 0 = keep forever. |
TAVRN_FILES_CHANNELS | on | files_channels.enabled: files channels may be created (on/off). |
TAVRN_FILES_CHANNELS_MAX_BYTES | 10737418240 (10 GiB) | files_channels.max_bytes, storage per files channel on the node’s own storage (never external stores, which have their own limit in the channel’s storage settings). Every member with Attach files can fill it, and a room may have several files channels: size it to the disk. 0 = unlimited (only where the disk is guarded otherwise); grants and plans can raise it per room. |
TAVRN_FILES_EXTERNAL | off | Files channels on external WebDAV/S3 stores (protocol: FILES.md §5). |
TAVRN_FILES_INTERNAL | on | files_channels.internal: off = files channels on external stores only; the node keeps no files of files channels (needs TAVRN_FILES_EXTERNAL=on, see Files channels on external stores). A plan or override of the room owner (or a room grant) with files_channels.internal 1 allows the node’s own storage in that owner’s rooms anyway, 0 takes it away on a node that is on. |
TAVRN_FILES_EXTERNAL_PRIVATE | off | on also allows external stores on private, loopback or link-local addresses and plain http (stores in the LAN); this disables the SSRF protection for them. |
Media, previews and search
Section titled “Media, previews and search”These settings control the conversion of images and videos, GIF search, link previews and the node’s search in plaintext channels.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_MEDIA_FFMPEG | auto | Media pipeline for avatars, profile headers, room icons and video backgrounds: auto (ffmpeg from PATH; the image ships one), off (only static PNG/JPEG/WebP, no animations or video input) or an absolute path to ffmpeg. |
TAVRN_MEDIA_WORKERS | 1 | Concurrent conversions (1–8); a few more uploads wait, the rest get 503 media.busy. |
TAVRN_WHITEBOARD_MAX_SCENES | 50 | Whiteboard scenes per user in the encrypted library (1–1000, at most 5 MB each). |
TAVRN_GIF_PROVIDER | none | GIF search: none, giphy or tenor. The API key stays on the node; clients only ask the node. Answers are cached (trending 5 minutes, searches 10 minutes) and each user may search 30 times a minute, so a few users cannot use up the key’s quota; a provider error answers 503 gif.unavailable. |
TAVRN_GIF_API_KEY | unset | Required with a GIF provider. |
TAVRN_UNFURL | on | Link previews: the sender’s client asks this node to load a linked page (public addresses only, 5 s, 1 MiB of HTML, image downscaled to a 40 KiB WebP) and puts the preview into the message. The node sees the URLs its users preview; readers never fetch anything. off: no previews from this node (404 unfurl.disabled). |
TAVRN_SEARCH_ENABLED | true | Node-side message search over unencrypted channels (protocol: SEARCH.md); false removes the route (404 search.disabled) and stops indexing. Encrypted conversations are only searched in the clients’ local encrypted index. |
TAVRN_SEARCH_RATE_PER_MINUTE | 30 | Search requests per user and minute (1–10000). |
Voice and video settings
Section titled “Voice and video settings”The node starts and supervises livekit-server with a configuration generated from these values
(Voice and video explains ports and addresses).
| Variable | Default | Meaning |
|---|---|---|
TAVRN_VOICE_ENABLED | true | false: no voice channels, no LiveKit process. |
TAVRN_VOICE_MAX_BITRATE_KBPS | 192 | Audio bitrate cap for every voice channel (8–512). New channels start at 64 kbps; owners pick a quality preset up to the cap (Saver 8, Standard 32, High 64, Studio 128, Music 192 stereo). Calls in DMs use 64 kbps, or the cap when it is lower. Lower it to save bandwidth: the clients show the presets above it as limited by this node. |
TAVRN_VOICE_MAX_PARTICIPANTS | 50 | Participants per voice channel at most (1–1000); channel user limits apply below it. |
TAVRN_VOICE_RECONNECT_GRACE_SECS | 45 | A dropped participant stays “reconnecting” this long (0–600; 0 = leave at once). |
TAVRN_SCREENSHARE_ENABLED | true | Screen sharing. |
TAVRN_SCREENSHARE_MAX_RESOLUTION | 1080p | 720p, 1080p, 1440p or 4k. |
TAVRN_SCREENSHARE_MAX_FPS | 30 | 1–60. |
TAVRN_CAMERA_ENABLED | true | Webcam sharing (the use_camera permission still applies). |
TAVRN_CAMERA_MAX_RESOLUTION | 720p | 360p, 720p or 1080p. |
TAVRN_CAMERA_MAX_FPS | 30 | 1–60. |
TAVRN_RTC_UDP_PORT_RANGE | 61000-61100 | UDP media ports (from-to, from < to, both > 1024), published 1:1. Keep it outside the Linux ephemeral range 32768–60999. |
TAVRN_RTC_EXTERNAL_IP | unset | Public address LiveKit announces (IPv4, or a hostname resolved at start and every minute). Empty together with TAVRN_RTC_ADVERTISE_IPS: detected via STUN (IPv4 and IPv6). |
TAVRN_RTC_ADVERTISE_IPS | empty | More addresses, comma-separated (IPs or hostnames): the first IPv4 and the first IPv6 (with TAVRN_RTC_EXTERNAL_IP) replace the container’s private addresses, every further one is handed to clients as an extra candidate. No link-local, multicast or 0.0.0.0 addresses. |
TAVRN_RTC_ADVERTISE_PRIVATE | false | true: private addresses (LAN, ULA) that hostnames resolve to are announced too (IP literals are always taken as configured). |
TAVRN_RTC_STUN_SERVERS | empty (LiveKit’s defaults) | host:port list for the address detection. |
TAVRN_RTC_TCP_PORT | 7881 | ICE over TCP, the fallback when UDP is blocked; publish and forward it like the UDP range. 0 = off, otherwise 1025–65535 and different from the other TCP ports. |
TAVRN_RTC_TURN_ENABLED | false | LiveKit’s embedded TURN server for clients behind strict NATs or firewalls. Needs at least one of the two TURN ports. |
TAVRN_RTC_TURN_UDP_PORT | 3478 | TURN over UDP, published and forwarded (0 = off); must not lie in the media range. |
TAVRN_RTC_TURN_TLS_PORT | 5349 | Plain TCP port behind the proxy’s TLS termination for TURN over TLS on 443 (0 = off). |
TAVRN_RTC_TURN_DOMAIN | turn.<host of TAVRN_PUBLIC_URL> | Name clients use for turns:<domain>:443 (turn.<TAVRN_DOMAIN> when the public URL’s host is an IP address). |
TAVRN_RTC_TURN_RELAY_PORT_RANGE | 30000-30999 | Relay sockets of the TURN server inside the container (not published); must not overlap the media range. |
TAVRN_LIVEKIT_PORT | 7880 | LiveKit’s signaling/API port on 127.0.0.1 inside the container (1025–65535, not the TAVRN_LISTEN port). Never published; clients use /rtc. |
TAVRN_LIVEKIT_BINARY | livekit-server | Path or name in PATH of the LiveKit binary (outside Docker: just livekit-install). |
Federation and directory settings
Section titled “Federation and directory settings”These settings decide whether and with whom the node federates and how it takes part in the node directory.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_FEDERATION_ENABLED | true | false: no federation tokens, federated logins, inbox or outbox. |
TAVRN_REFERENCE_NODES | empty (.env.example: tavrn.org, the official node) | Comma-separated domains of the reference nodes this node announces itself to and pulls the node list from (directory). Lowercase, at least one dot, no port. |
TAVRN_DIRECTORY_LISTING | true | false: never announce; other nodes refuse to list this node. |
TAVRN_DIRECTORY_MAX_ROOMS | 5000 | Cached rooms of other nodes for Discover in total (at most 200 per node; > 0). |
TAVRN_FEDERATION_BLOCKLIST | empty | Domains never federated with (also blocks their subdomains). |
TAVRN_FEDERATION_ALLOWLIST | empty | If set, federate only with these domains (the blocklist still wins). |
TAVRN_FEDERATION_ALLOW_PRIVATE | false | true allows peers on private or loopback addresses (development, tests, closed intranets). |
TAVRN_FEDERATION_SRV_FALLBACK | true | When a peer’s identity domain serves no /.well-known/tavrn (404, or no HTTPS server at all), look up _tavrn._tcp.<domain> SRV and fetch the document from its target (SRV record). |
TAVRN_FEDERATION_REMOTE_BOTS | true | Accept bots of other nodes. |
TAVRN_FEDERATION_RATE_LOGINS | 60 | Federated logins per remote domain and minute (> 0). One account, and one client address on the network (not loopback), get a quarter of it (at least 1). |
TAVRN_FEDERATION_RATE_INBOX | 300 | Inbox messages per remote domain and minute (> 0). |
TAVRN_FEDERATION_RATE_API | 3000 | API requests of all users of one remote domain per minute (> 0). |
TAVRN_FEDERATION_RATE_API_PER_USER | 600 | API requests of one remote user per minute (> 0), counted before the domain’s budget and at most that budget: requests it refuses do not use up the domain’s budget, so one account cannot lock the other users of its domain out. Keep it well below TAVRN_FEDERATION_RATE_API. |
TAVRN_FEDERATION_RATE_CLAIMS | 120 | KeyPackage claims per requesting node and minute (> 0). |
TAVRN_FEDERATION_MAX_REMOTE_USERS | 10000 | Remote users in total. Users that only an incoming message names (the sender of a friend request) may fill it up to nine tenths; the rest stays for users who sign in. |
TAVRN_FEDERATION_MAX_REMOTE_USERS_PER_DOMAIN | 1000 | Remote users per home domain. |
TAVRN_FEDERATION_PERMIT_TIMEOUT_SECS | 10 | How long a request to a peer waits for one of the peer’s 4 connection slots and then one of the 64 node-wide slots before it fails (1–120). A slow peer only ever ties up its own slots. |
TAVRN_FEDERATION_MAX_UNVERIFIED_PEERS | 1000 | Cached discovery documents of peers no signed request of which ever verified here (10–1000000). Beyond it, unsigned requests naming a new domain are refused (503 federation.peer_limit) before any fetch. Peers this node looked up itself, listed in its directory, or with users or sign-ins never count. Failed fetches are remembered for 60 seconds per domain, and peers unused for 30 days that nothing ties to the node are pruned. |
TAVRN_FEDERATION_DISCOVERY_SLOTS | 12 | Discovery fetches at the same time for unknown issuers of requests not verified yet (1–64). When all are taken such requests are refused at once (503 federation.busy), so unsigned requests naming many slow domains cannot take the connection slots that known peers need. |
TAVRN_FEDERATION_REMOTE_USER_RETENTION_DAYS | 90 | Remote users without a federated login for this many days and with nothing left on this node (no memberships, friendships, blocks, messages, …) are deleted by the hourly housekeeping; 0 keeps them forever. |
TAVRN_FEDERATION_NOTIFY_BANS | false | true: tell the home node of a remote user that a room or node ban removed them from a room (ban.notify, information only, never the reason). |
TAVRN_FEDERATION_REQUIRE_CONFORMANCE | false | true: also refuse federated logins and inbox messages of nodes that were not checked yet or failed (conformance checks). |
TAVRN_FEDERATION_DEV_PEERS | empty | Development only (refused without --dev): domain=http://host:port pairs for local multi-node setups. |
TAVRN_DEV_MISBEHAVE | empty | Development only (refused without --dev): a node that breaks the protocol on purpose for conformance tests (accept_bad_signatures, replay_ok, plaintext_ok, no_rate_limit). Never in production. |
Applications, bots and webhooks settings
Section titled “Applications, bots and webhooks settings”Bot tokens are a single factor: rooms, categories and channels can be declared “no bots” in their settings (protocol: APPLICATIONS.md §1.2, §9.6). Details: Webhooks and applications.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_APP_CREATION | all | Who may create applications: all (every local user with a full session), invite (users with an application grant from an admin) or admins. |
TAVRN_APPS_PER_USER | 5 | Applications per owner (0–100); node admins are not exempt. |
TAVRN_APPS_PER_ROOM | 25 | Installed applications per room (0–1000). |
TAVRN_APP_TOKEN_MAX_DAYS | 365 | Maximum app token lifetime (1–3650 days). |
TAVRN_APP_RATE_REQUESTS | 1200 | API requests (REST and WebSocket operations) per application and minute (> 0). |
TAVRN_APPS_FEDERATE | true | This node’s bots may obtain federation tokens for other nodes. |
TAVRN_APP_ALLOW_PRIVATE_URLS | false (--dev: true) | Application URLs (interactions URL, event subscriptions) on private or loopback addresses (intranets; turns the SSRF protection off for them). |
TAVRN_APP_COMMANDS_REQUIRE_APPROVAL | false | Node admins approve every application command definition before it is offered in this node’s rooms (a changed definition is pending again). |
TAVRN_COMPONENT_MAX_AGE_DAYS | 30 | Buttons and select menus of application messages older than this many days answer 410 component.expired (0 = no limit, at most 3650). |
TAVRN_WEBHOOKS_ENABLED | true | Incoming webhooks; false refuses creation and execution (webhook.disabled). |
TAVRN_WEBHOOKS_PER_CHANNEL | 10 | Webhooks per channel (1–50; a room has at most 50). |
Moderation settings
Section titled “Moderation settings”These settings tune the flood detection that works in every channel, encrypted or not.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_AUTOMOD_BURST_LIMIT | 10 | More than this many messages of a user per channel within 5 seconds count as spam (0 = off). |
TAVRN_AUTOMOD_TIMEOUT_MINUTES | 10 | Timeout in minutes after a spam burst. |
Reports and content auto-moderation add TAVRN_REPORTS_RETENTION_DAYS, TAVRN_REPORTS_RATE_PER_HOUR,
TAVRN_REPORTS_INBOUND_PER_HOUR, TAVRN_AUTOMOD_WORDS_FILE and TAVRN_AUTOMOD_LINKS; see
Moderation and auto-moderation.
Legal settings
Section titled “Legal settings”Details: Legal pages and GDPR.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_LEGAL_NOTICES | off | on opens the public takedown notice form /legal/notice (captcha when configured). Notices forwarded by other nodes are accepted either way. |
TAVRN_LEGAL_NOTICE_RATE_PER_HOUR | 5 (--dev: 1000) | Notices per client address and hour (1–1000). |
TAVRN_LEGAL_NOTICE_RETENTION_DAYS | 1095 | Cases are kept this long after their last change (30–36500; 1095 = 3 years). |
TAVRN_LEGAL_NOTICE_INBOUND_PER_HOUR | 10 | Notices forwarded by other nodes accepted per sending registrable domain (its subdomains share the budget) and hour (1–1000); five times as many from all domains together. Only nodes this node has history with (its users signed in there, users of that node known here for at least a day, or listed in its directory) may forward; forwarded cases get no confirmation mail here. |
TAVRN_LEGAL_REPEAT_THRESHOLD | 3 | Actioned notices against one user after which the admin page suggests disabling the account (1–100; a suggestion only, never automatic). |
TAVRN_LEGAL_COUNTER_NOTICE_DAYS | 14 | Days the uploader may answer an actioned notice with a counter-notice (1–365). |
TAVRN_LEGAL_TERMS_MODE | notice | notice: ask once after a new version of the terms; require: refuse the API (403 legal.terms_required) until accepted (declining signs out). |
TAVRN_ACCOUNT_DELETE_GRACE_DAYS | 30 | Deleted accounts can be restored by an admin this long, with the username reserved; then they are erased (1–365). |
Web push and metrics settings
Section titled “Web push and metrics settings”These settings switch web push notifications and the Prometheus metrics endpoint on or off and configure them.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_PUSH | on | Web push notifications to browsers and installed web apps (PWAs). See Web push. |
TAVRN_PUSH_CONTACT | the node’s public URL (TAVRN_PUBLIC_URL) | VAPID contact, a mailto: address or an https:// URL (at most 256 characters) that push services may use to reach the operator. |
TAVRN_PUSH_MAX_PER_MINUTE | 10 | Pushes per user and minute (1–1000); in addition at most one push per conversation and user every 30 seconds. |
TAVRN_METRICS | off | on exposes GET /metrics (Prometheus text format) on the node’s HTTP port. See Metrics and health. |
TAVRN_METRICS_TOKEN | empty | When set (16–256 printable ASCII characters without spaces), scrapers must send Authorization: Bearer <token>; never printed by check-config. When empty, /metrics answers only to loopback and the addresses in TAVRN_TRUSTED_PROXIES. |
Several instances
Section titled “Several instances”Details: Scaling with Valkey.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_CACHE_URL | unset | redis://[user:password@]host[:port][/db] or rediss://… (TLS); enables several instances. The password never appears in logs or check-config; URL-encode special characters. |
TAVRN_INSTANCE_ID | random per start (logged) | [A-Za-z0-9._-]{1,64}; names the instance in logs, the cache and livekit-<id>.yaml. |
TAVRN_INSTANCE_URL | address towards the cache + listen port | http(s)://host[:port] where the other instances reach this one. |
Logging
Section titled “Logging”These settings control how much the node logs, in which format, and which slow requests and statements it reports.
| Variable | Default | Meaning |
|---|---|---|
TAVRN_LOG_LEVEL | info (--dev: debug,sqlx=info) | A log filter: a level (error, warn, info, debug, trace) or per-module directives such as info,sqlx=warn. An invalid filter falls back to info. |
TAVRN_LOG_FORMAT | text | text or json (one JSON object per line, for log collectors). |
TAVRN_LOG_SLOW_REQUEST_MS | 0 (off) | Logs one slow request line (level info, target tavrn_node::slow_request) for every HTTP request that took at least this many milliseconds (up to 3600000): method, route template (never ids), status, duration_ms and queries (the database statements it ran). See Performance. |
TAVRN_DB_SLOW_STATEMENT_MS | 1000 | A database statement that takes at least this many milliseconds is logged as a warning (slow statement, target sqlx::query, with the statement text but no values); 0 turns it off (up to 3600000). |
Variables outside the node
Section titled “Variables outside the node”A few variables configure the compose setup around the node: the database password, the image tag, the host port and the optional Valkey password.
These are read by docker-compose.yml, not by the node:
| Variable | Meaning |
|---|---|
POSTGRES_PASSWORD | Password of the compose PostgreSQL (required by the compose file). |
IMAGE_TAG | Image tag, e.g. 0.1 to pin a release line; default latest. |
TAVRN_HOST_PORT | Host port on 127.0.0.1 the reverse proxy points at (default 8080). |
VALKEY_PASSWORD | Password of the optional valkey service (the same one goes into TAVRN_CACHE_URL). |
This page is generated from tavrn-node/docs/HANDBOOK.md (AGPL-3.0-only) when the website is built; its links were adapted for this site.