Configuration reference
This page lists all 175 variables of the node’s .env.example, grouped as
in the file: 68 are set there, the others are commented out. Copy the file to .env next to
docker-compose.yml and change what you need; the node checks the whole configuration at start and names
every problem.
The middle column shows the value in the example file. A line that is commented out there shows the built-in default or an example: leave it commented out to keep the default.
How the configuration is loaded, and what the settings of each area mean, is explained on the Configuration page.
Required
Section titled “Required”| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_DOMAIN | chat.example.org | 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://chat.example.org (commented out) | Identity domain vs. node host (Identity domain and node host (delegation)): TAVRN_DOMAIN is the identity (addresses alice@<domain>, keys, tokens). TAVRN_PUBLIC_URL is where the node is served, default https://<TAVRN_DOMAIN>. Example: addresses alice@tavrn.org, app at app.tavrn.org (the website on tavrn.org then serves or forwards /.well-known/tavrn):Example: TAVRN_DOMAIN=tavrn.orgExample: TAVRN_PUBLIC_URL=https://app.tavrn.orghttps://<host>[:port], no path; any host (http only with —dev) |
POSTGRES_PASSWORD | please-use-a-long-random-password | Password of the compose PostgreSQL (required by the compose file). |
Container and database
Section titled “Container and database”| Variable | Value in .env.example | Description |
|---|---|---|
IMAGE_TAG | 0.1 (commented out) | Container image tag (docker-compose.yml): pin a release line such as 0.1 so docker compose pull only brings compatible updates; unset means the newest release (README.md “Upgrading”). |
TAVRN_DATABASE_URL | postgres://tavrn:password@db.example.org:5432/tavrn (commented out) | External PostgreSQL instead of the compose service “db” (then remove that service from docker-compose.yml):postgres://user:password@host:port/db (or postgresql://). sqlite: only with --dev. |
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_NAME | My node | Display name of the node (directory, discovery document). |
TAVRN_DESCRIPTION | empty | Short description of the node (directory). |
TAVRN_REGISTRATION | invite | open | invite | closed (closed only blocks new accounts, never federation) |
TAVRN_EMAIL | optional | none | optional | required (required needs TAVRN_SMTP_URL) |
TAVRN_ADMIN_USERS | empty | e.g. alice,bob |
Second factor and passkeys
Section titled “Second factor and passkeys”Second factor (TOTP or passkeys) is mandatory for every account and cannot be switched off. TOTP secrets are encrypted with ./data/keys/secrets.key, stored files with ./data/keys/storage.key: back up ./data (including ./data/keys) together with the database. Passkeys: the WebAuthn origin defaults to TAVRN_PUBLIC_URL (override only for development/tests), the relying party id to TAVRN_DOMAIN. The relying party id must be the origin’s host or a parent domain of it (tavrn.org covers app.tavrn.org); keep it when the app host moves, or existing passkeys stop working. With a public URL on an unrelated host (chat.example.net for tavrn.org) set it to that host or its parent domain.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_WEBAUTHN_ORIGIN | http://localhost:5173 (commented out) | Origin of the web client for passkeys. Override only for development and tests (e.g. http://localhost:5173). |
TAVRN_WEBAUTHN_RP_ID | tavrn.org (commented out) | 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 | android:apk-key-hash:<43 base64url characters> (commented out) | Passkeys inside your Android app: its origin, the base64url SHA-256 of the APK’s signing certificate (comma-separated for several keys; Passkeys in the apps converts the hex fingerprint). The relying party’s assetlinks.json must list the same certificate. |
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_SMTP_URL | smtp://192.0.2.10:8025 (commented out) | Email for the node’s mails (address confirmation, password reset, account deletion, takedown notices, app token expiry), in each recipient’s language: smtp:// (plain, internal relay), smtps:// (TLS), smtp+starttls:// |
TAVRN_SMTP_FROM | noreply@example.org (commented out) | default: noreply@<TAVRN_DOMAIN> |
TAVRN_MAIL_TEMPLATES_DIR | /data/templates/mail (commented out) | Your own mail texts per language (English and German are built in): <dir>/<language>/<name>.txt, see Mail languages and templates and templates/mail/ of the repository. |
Captcha
Section titled “Captcha”Test the relay: docker exec tavrn tavrn-node send-test-mail you@example.org Captcha (optional): Cloudflare Turnstile, keys from dash.cloudflare.com → Turnstile
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_CAPTCHA_PROVIDER | none | none | turnstile |
TAVRN_TURNSTILE_SITE_KEY | 0x4AAAAAAA... (commented out) | See the introduction of this section. |
TAVRN_TURNSTILE_SECRET_KEY | 0x4AAAAAAA... (commented out) | See the introduction of this section. |
TAVRN_CAPTCHA_REGISTER | true | require the captcha on registration |
TAVRN_CAPTCHA_LOGIN | false | require the captcha on login |
Single sign-on (OpenID Connect)
Section titled “Single sign-on (OpenID Connect)”Single sign-on via OpenID Connect (optional; APPLICATIONS.md §8, Single sign-on (OpenID Connect)). Register <TAVRN_PUBLIC_URL>/api/v1/auth/oidc/callback (default https://<TAVRN_DOMAIN>/…) as redirect URI at the provider (code flow, PKCE S256).
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_OIDC_ISSUER | https://keycloak.example/realms/company (commented out) | enables SSO; https only, must match exactly |
TAVRN_OIDC_CLIENT_ID | tavrn (commented out) | Client id at the provider (required with an issuer). |
TAVRN_OIDC_CLIENT_SECRET_FILE | /data/keys/oidc-client-secret (commented out) | File holding the client secret (preferred) |
TAVRN_OIDC_CLIENT_SECRET | empty (commented out) | Or the secret itself (never printed); not both |
TAVRN_OIDC_SCOPES | openid profile email groups (commented out) | Requested scopes (space or comma separated); must contain openid. |
TAVRN_OIDC_USERNAME_CLAIM | preferred_username (commented out) | username of new accounts (sanitized, -2 on collision) |
TAVRN_OIDC_DISPLAY_NAME_CLAIM | name (commented out) | updated on every login |
TAVRN_OIDC_GROUPS_CLAIM | groups (commented out) | Claim holding the groups (array of strings), from the ID token or UserInfo. |
TAVRN_OIDC_BUTTON_LABEL | empty (commented out) | empty: the client’s “Sign in with SSO” |
TAVRN_OIDC_ONLY | false (commented out) | true: password/passkey logins and registration only for TAVRN_ADMIN_USERS |
TAVRN_OIDC_AUTO_PROVISION | true (commented out) | create accounts on the first SSO login |
TAVRN_OIDC_GROUP_MAP | empty (commented out) | JSON, e.g. {“it-admins”:{“node_admin”:true},“staff”:{“room_creation”:5}} |
TAVRN_OIDC_GROUP_SYNC | add (commented out) | add | sync (sync also revokes what an earlier sync granted) |
TAVRN_OIDC_SESSION_DAYS | 7 (commented out) | hard end of an SSO session (1–30) |
Sessions and devices
Section titled “Sessions and devices”| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_SESSION_ACCESS_MINUTES | 60 (commented out) | access token lifetime (5–1440) |
TAVRN_SESSION_REFRESH_DAYS | 90 (commented out) | sliding refresh token lifetime (1–3650); SSO keeps TAVRN_OIDC_SESSION_DAYS as hard end |
TAVRN_RATE_USER_PER_MINUTE | 1200 (commented out) | API requests per user and minute (0 = off; route limits apply on top) |
TAVRN_RATE_LIMIT_IPV6_PREFIX | 64 (commented out) | per-address rate limits count IPv6 clients by this prefix (32-128) |
TAVRN_PASSWORD_HASH_CONCURRENCY | 0 (commented out) | password hashes running at once (0 = twice the CPU cores, at least 2) |
TAVRN_WS_MAX_SOCKETS_PER_USER | 20 (commented out) | WebSocket connections per user and instance (0 = unlimited) |
TAVRN_DEVICE_STALE_DAYS | 180 (commented out) | delete devices unused for this long (never the only one or one with a live session); 0 = never |
TAVRN_E2EE_CLAIMS_PER_TARGET_PER_HOUR | 60 (commented out) | claims for one user per hour per source (this node / one other node) by requesters sharing no room, DM or friendship with them (1–10000; 10x per user in total) |
TAVRN_MINIMUM_AGE | 16 (commented out) | minimum age at registration (13–99); the date of birth is asked on every path |
Rooms and uploads
Section titled “Rooms and uploads”| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_ROOM_CREATION | invite | none | invite | local | federated |
TAVRN_MAX_ROOMS_PER_USER | 5 | Rooms a user may own (entitlement rooms.max_owned; 0–1000000, 0 = unlimited). |
TAVRN_DM_POLICY | everyone | Upper bound of every user’s “Who can message me” (users choose within it, their default is everyone_shared): friends | everyone_shared (friends and shared rooms) | everyone. Non-friends land in the recipient’s message requests; blocks always apply. open is the old name of everyone. |
TAVRN_UPLOADS_ENABLED | true | false switches file uploads off. |
TAVRN_UPLOAD_MAX_BYTES | 26214400 | 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 (wildcards like image/*); 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: images, audio, video, documents, archives, other (rooms set them per role and channel instead) |
TAVRN_STORAGE_QUOTA_PER_ROOM_BYTES | 10737418240 | Storage per room (entitlement storage.max_bytes_per_room). |
TAVRN_RETENTION_DAYS | 0 | Message retention: messages of channels, DMs and group DMs are deleted this many days after they were sent (hourly housekeeping, with their attachments). Pinned messages and the welcome channel are kept; rooms may only shorten it. 0 = keep forever (0-36500). |
TAVRN_EMOJI_MAX_PER_ROOM | 100 | Custom emojis per room (entitlement emoji.max_per_room). |
TAVRN_EMOJI_MAX_BYTES | 262144 | Largest custom emoji file. |
TAVRN_PREVIEW_AUTO_MAX_BYTES | 5242880 | 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_STORAGE_ENCRYPTION | on | Encryption at rest of every stored file (attachments, avatars, icons, emojis, sounds): each file has its own AES-256-GCM key, wrapped with ./data/keys/storage.key (created at the first start). BACK UP ./data/keys/storage.key with the database: without it every stored file is unreadable. Rotate it with the node stopped: tavrn-node rotate-storage-key (re-wraps the file keys only). off = new files are stored unencrypted (debugging only; encrypted files stay readable). |
Entitlement defaults
Section titled “Entitlement defaults”Entitlements (ENTITLEMENTS.md): the values above and below are the node defaults of the entitlement keys; 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. TAVRN_MAX_ROOMS_PER_USER (rooms.max_owned, 0 = unlimited), TAVRN_UPLOAD_MAX_BYTES (upload.max_bytes), TAVRN_STORAGE_QUOTA_PER_ROOM_BYTES (storage.max_bytes_per_room), TAVRN_EMOJI_MAX_PER_ROOM (emoji.max_per_room) are read from above.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_UPLOAD_MAX_BYTES_CEILING | 0 | largest upload a grant can allow; 0 = TAVRN_UPLOAD_MAX_BYTES (grants cannot raise it) |
TAVRN_VOICE_CHANNEL_MAX_PARTICIPANTS | 0 | voice.max_participants per voice channel (0 = only TAVRN_VOICE_MAX_PARTICIPANTS) |
TAVRN_STREAM_MAX_HEIGHT | 0 | stream.max_height in pixels (0 = only TAVRN_SCREENSHARE_MAX_RESOLUTION); client-enforced |
TAVRN_STREAM_MAX_FPS | 0 | stream.max_fps (0 = only TAVRN_SCREENSHARE_MAX_FPS); client-enforced |
TAVRN_CAMERA_MAX_HEIGHT | 0 | camera.max_height in pixels (0 = only TAVRN_CAMERA_MAX_RESOLUTION); client-enforced |
TAVRN_EMOJI_MAX_PERSONAL | 20 | emoji.max_personal |
TAVRN_SOUNDS_MAX_PER_ROOM | 50 | sounds.max_per_room (soundboard clips) |
TAVRN_SOUNDS_MAX_PERSONAL | 10 | sounds.max_personal |
TAVRN_ATTACHMENT_RETENTION_DAYS | 0 | Attachment retention (attachments.retention_days): attachments of channels and DMs are deleted this many days after the upload (hourly housekeeping); messages keep listing them as “file expired”. Pinned messages keep their files. Rooms may only shorten it. 0 = keep forever. |
TAVRN_FILES_CHANNELS | on | files_channels.enabled (on | off) |
TAVRN_FILES_CHANNELS_MAX_BYTES | 10737418240 | files_channels.max_bytes per files channel on the node’s own storage (default 10 GiB); never limits external stores (each has its own optional limit in the channel’s storage settings). Every member who may attach files can fill it: 0 (unlimited) leaves the node’s disk unbounded. Grants and plans raise it per room. |
TAVRN_FILES_EXTERNAL | off | Files channels on external WebDAV/S3 stores (FILES.md §5): off by default. The private switch also allows private/loopback addresses and plain http (stores in the LAN; disables the SSRF protection for them). on | off |
TAVRN_FILES_EXTERNAL_PRIVATE | off | on | off |
TAVRN_FILES_INTERNAL | on | off = files channels on external stores only: the node keeps no files of files channels, a new channel takes uploads once its managers set a WebDAV/S3 store (needs TAVRN_FILES_EXTERNAL=on). Attachments in chats stay on the node. Node default of the entitlement files_channels.internal: a plan or override of a room owner can allow the node’s storage in their rooms anyway (1) or take it away on a node that is on (0). on | off |
Media pipeline
Section titled “Media pipeline”Media pipeline for avatars, profile headers, room icons and video backgrounds. Uploads up to 20 MB and 30 s are converted to WebP (animated input to animated WebP with a still first frame) by ffmpeg without network access and with a 30-second limit per job. The image ships a minimal static ffmpeg.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_MEDIA_FFMPEG | auto | auto (ffmpeg from PATH) | off (only static PNG/JPEG/WebP, no animations) | /path/to/ffmpeg |
TAVRN_MEDIA_WORKERS | 1 | concurrent conversions (1–8); a few more uploads wait, the rest get 503 media.busy |
TAVRN_MEDIA_CACHE_UNUSED_DAYS | 365 | cached emojis and soundboard clips of other nodes are dropped after this many days without use (0 = never) |
TAVRN_MEDIA_CACHE_MAX_BYTES | 2147483648 (commented out) | total size of those cached copies (least recently used go first) |
TAVRN_MEDIA_CACHE_MAX_BYTES_PER_HOST | 67108864 (commented out) | size of the cached copies from one origin node |
TAVRN_WHITEBOARD_MAX_SCENES | 50 | whiteboard scenes per user in the encrypted library (1–1000, at most 5 MB each) |
GIF search
Section titled “GIF search”GIF search (optional): the API key stays on the node, clients only ask the node
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_GIF_PROVIDER | none | none | giphy | tenor |
TAVRN_GIF_API_KEY | empty (commented out) | Required with a GIF provider. |
Link previews
Section titled “Link previews”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.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_UNFURL | on | on | off |
Auto-moderation
Section titled “Auto-moderation”| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_AUTOMOD_BURST_LIMIT | 10 | more than N messages per user and channel in 5 seconds = spam (0 = off) |
TAVRN_AUTOMOD_TIMEOUT_MINUTES | 10 | Timeout in minutes after a spam burst. |
TAVRN_AUTOMOD_WORDS_FILE | /data/automod/words.txt (commented out) | Content rules of the operator for every unencrypted conversation (MODERATION.md §2.3), action “block”. Both files are read at start and again every hour and on SIGHUP (docker compose kill -s HUP tavrn); an invalid file keeps the previous lists. One entry per line, # comments, at most 1 MiB / 10000 entries.terms, matched as whole words; * matches within a word |
TAVRN_AUTOMOD_LINKS | off (commented out) | off | path of a domain blocklist (subdomains included) |
Reports
Section titled “Reports”| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_REPORTS_RATE_PER_HOUR | 10 (commented out) | reports per user and hour (1-1000) |
TAVRN_REPORTS_RETENTION_DAYS | 90 (commented out) | resolved and dismissed reports are deleted after this many days (1-36500) |
TAVRN_REPORTS_INBOUND_PER_HOUR | 20 (commented out) | reports forwarded by other nodes per sending domain and hour, 5x in total (1-1000) |
Applications and bots
Section titled “Applications and bots”Applications / bots (APPLICATIONS.md §1.2, §9.6). Bot tokens are a single factor: rooms, categories and channels can be declared “no bots” in their settings.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_APP_CREATION | all (commented out) | all | invite (admin grants per user) | admins |
TAVRN_APPS_PER_USER | 5 (commented out) | applications per owner (0-100) |
TAVRN_APPS_PER_ROOM | 25 (commented out) | installed applications per room (0-1000) |
TAVRN_APP_TOKEN_MAX_DAYS | 365 (commented out) | maximum app token lifetime (1-3650 days) |
TAVRN_APP_RATE_REQUESTS | 1200 (commented out) | API requests per application and minute |
TAVRN_APP_ALLOW_PRIVATE_URLS | false (commented out) | application URLs on private/loopback addresses (intranets; SSRF protection off) |
TAVRN_APP_COMMANDS_REQUIRE_APPROVAL | false (commented out) | admins approve every application command definition (Admin API, §5.2) |
TAVRN_COMPONENT_MAX_AGE_DAYS | 30 (commented out) | buttons and menus of application messages older than this answer 410 (0 = no limit) |
Incoming webhooks
Section titled “Incoming webhooks”Incoming webhooks (APPLICATIONS.md §6.1): write-only URLs that post into unencrypted channels.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_WEBHOOKS_ENABLED | true (commented out) | Incoming webhooks; false refuses creation and execution (webhook.disabled). |
TAVRN_WEBHOOKS_PER_CHANNEL | 10 (commented out) | 1-50 per channel, at most 50 per room |
Search
Section titled “Search”Message search over unencrypted channels (PROTOCOL.md §7d, SEARCH.md); encrypted conversations are searched in the clients’ local encrypted index only.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_SEARCH_ENABLED | true (commented out) | 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 (commented out) | search requests per user and minute (1-10000) |
Legal (LEGAL.md, Legal pages and GDPR): takedown notices, terms acceptance, account deletion.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_LEGAL_NOTICES | off (commented out) | on: public notice form /legal/notice (captcha when configured) |
TAVRN_LEGAL_NOTICE_RATE_PER_HOUR | 5 (commented out) | notices per client address and hour (1-1000) |
TAVRN_LEGAL_NOTICE_RETENTION_DAYS | 1095 (commented out) | cases are kept this long after their last change (30-36500) |
TAVRN_LEGAL_NOTICE_INBOUND_PER_HOUR | 10 (commented out) | notices forwarded by other nodes per sending domain and hour, 5x in total (1-1000) |
TAVRN_LEGAL_REPEAT_THRESHOLD | 3 (commented out) | actioned notices after which the admin page suggests disabling (1-100) |
TAVRN_LEGAL_COUNTER_NOTICE_DAYS | 14 (commented out) | days the uploader may send a counter-notice (1-365) |
TAVRN_LEGAL_TERMS_MODE | notice (commented out) | notice: ask once after a new version; require: block until accepted |
TAVRN_ACCOUNT_DELETE_GRACE_DAYS | 30 (commented out) | deleted accounts can be restored this long; then erased (1-365) |
Voice, video and screen sharing
Section titled “Voice, video and screen sharing”Voice, video and screen sharing (VOICE.md). The node starts and supervises livekit-server (part of the image) with a config generated from these values; its API key lives in ./data/keys/livekit.key.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_VOICE_ENABLED | true | false: no voice channels, no LiveKit process |
TAVRN_VOICE_MAX_BITRATE_KBPS | 192 | audio bitrate cap for every voice channel and call (8–512); 192 allows the music preset |
TAVRN_VOICE_MAX_PARTICIPANTS | 50 | participants per voice channel at most (1–1000) |
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 | 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 | 1080p |
TAVRN_CAMERA_MAX_FPS | 30 | 1–60 |
TAVRN_RTC_UDP_PORT_RANGE | 61000-61100 | Media ports: open this UDP range in the firewall and publish it 1:1 to the container (docker-compose.yml). Keep the range outside the Linux ephemeral port range (32768-60999): otherwise any process’s outgoing UDP socket can occupy a port in it and the container fails to publish the range. |
TAVRN_RTC_EXTERNAL_IP | 203.0.113.7 (commented out) or home.dyndns.example (commented out) | Addresses LiveKit announces to clients (VOICE.md §10.2). Leave both empty to detect the public IPv4 and IPv6 via STUN at start (recommended when the IPv6 prefix changes). Explicit: the first IPv4 and the first IPv6 replace the container’s private addresses; every further address (e.g. the host’s LAN address, for clients in the same network when the router has no NAT loopback) is handed to clients as an extra candidate. The startup log line “LiveKit starting media=…” names what is announced. Both also take hostnames (dynamic DNS, mixed with IP literals): resolved at start and every minute (or after the DNS TTL if longer), A and AAAA; LiveKit restarts when the addresses change (calls reconnect within the grace period). Use an unproxied record with a low TTL; media cannot go through Cloudflare & co (Voice and video). |
TAVRN_RTC_ADVERTISE_IPS | 2001:db8::7,192.168.0.23 (commented out) or home.dyndns.example,192.168.0.23 (commented out) | 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 (commented out) | true: private addresses (LAN, ULA) of hostnames are announced too |
TAVRN_RTC_STUN_SERVERS | stun.l.google.com:19302 (commented out) | host:port list for the detection (default: LiveKit’s) |
TAVRN_RTC_TCP_PORT | 7881 | ICE over TCP when UDP is blocked: publish and forward this TCP port like the UDP range (0 = off). |
TAVRN_RTC_TURN_ENABLED | false | Embedded TURN server for clients behind strict NATs/firewalls (TURN over TLS through the proxy). |
TAVRN_RTC_TURN_UDP_PORT | 3478 (commented out) | TURN over UDP, published and forwarded (0 = off) |
TAVRN_RTC_TURN_TLS_PORT | 5349 (commented out) | plain TCP behind the proxy’s TLS termination on 443 (0 = off) |
TAVRN_RTC_TURN_DOMAIN | turn.chat.example.org (commented out) | default turn.<host of TAVRN_PUBLIC_URL>; the proxy serves it on 443 |
TAVRN_RTC_TURN_RELAY_PORT_RANGE | 30000-30999 (commented out) | relay sockets inside the container, not published |
TAVRN_LIVEKIT_PORT | 7880 (commented out) | LiveKit’s signaling/API port on 127.0.0.1 inside the container (never published; clients use /rtc). |
TAVRN_LIVEKIT_BINARY | livekit-server (commented out) | path or name in PATH (outside Docker: just livekit-install) |
Federation
Section titled “Federation”| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_FEDERATION_ENABLED | true | false: no tokens, federated logins, inbox or outbox |
TAVRN_REFERENCE_NODES | tavrn.org | Peer directory and Discover (FEDERATION.md §7): reference nodes this node announces itself to (at start, every 6 hours and when a room is listed or unlisted) and pulls the node list from; comma-separated. The template lists the node at the official node tavrn.org and pulls its node list; empty = no reference nodes (the node is in no directory and its Discover page shows only nodes that announce themselves to it). |
TAVRN_DIRECTORY_LISTING | true | false: never announce; other nodes refuse to list this node |
TAVRN_DIRECTORY_MAX_ROOMS | 5000 (commented out) | cached rooms of other nodes for Discover (at most 200 per node) |
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 (commented out) | true allows peers on private/loopback addresses (dev, tests, intranets) |
TAVRN_FEDERATION_SRV_FALLBACK | true (commented out) | Resolution order of other nodes: well-known -> SRV -> https://<domain>. When a peer’s identity domain serves no https://<domain>/.well-known/tavrn (404, or no HTTPS server at all), look up the SRV record _tavrn._tcp.<domain> and fetch the document from its target before trying https://<domain>/api/v1 (browsers get the same result through this node, GET /api/v1/federation/resolve/<domain>). The target must present a certificate valid for <domain> itself (TLS and Host are <domain>); a certificate only for the target’s own name is refused. |
TAVRN_FEDERATION_REMOTE_BOTS | true (commented out) | accept bots of other nodes |
TAVRN_APPS_FEDERATE | true (commented out) | this node’s bots may obtain federation tokens for other nodes |
TAVRN_FEDERATION_RATE_LOGINS | 60 (commented out) | Per remote domain and minute: 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 (commented out) | Per remote domain and minute: Inbox messages per remote domain and minute (> 0). |
TAVRN_FEDERATION_RATE_API | 3000 (commented out) | Per remote domain and minute: API requests of all users of one remote domain per minute (> 0). |
TAVRN_FEDERATION_RATE_API_PER_USER | 600 (commented out) | Per remote domain and minute: per remote user, below the domain’s budget |
TAVRN_FEDERATION_RATE_CLAIMS | 120 (commented out) | Per remote domain and minute: KeyPackage claims per requesting node and minute (> 0). |
TAVRN_FEDERATION_MAX_REMOTE_USERS | 10000 (commented out) | Remote user quotas: 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 (commented out) | Remote user quotas: Remote users per home domain. |
TAVRN_FEDERATION_PERMIT_TIMEOUT_SECS | 10 (commented out) | Remote user quotas: wait for a connection slot of a peer, then node-wide (1-120) |
TAVRN_FEDERATION_MAX_UNVERIFIED_PEERS | 1000 (commented out) | Remote user quotas: peers known only from unverified requests (10-1000000) |
TAVRN_FEDERATION_DISCOVERY_SLOTS | 12 (commented out) | Remote user quotas: parallel discovery fetches for unknown, unverified issuers (1-64) |
TAVRN_FEDERATION_REMOTE_USER_RETENTION_DAYS | 90 (commented out) | 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 (commented out) | 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 (commented out) | Conformance checks of other nodes (FEDERATION.md §9) always run (first contact, then daily; hard failures and observed spoofing block a node automatically). true: also refuse nodes that were not checked yet or failed. Check a node by hand: tavrn-node doctor --peer <domain> or the Federation tab of the node administration. |
TAVRN_FEDERATION_DEV_PEERS | a.test=http://127.0.0.1:18081,b.test=http://127.0.0.1:18082 (commented out) | Development only (refused without —dev): local multi-node setups |
TAVRN_DEV_MISBEHAVE | empty (commented out) | 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; comma-separated). Never in production. |
Operations
Section titled “Operations”Operations (TLS is done by the reverse proxy; tavrn speaks HTTP only)
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_LISTEN | 0.0.0.0:8080 | Address and port of the plain-HTTP server. |
TAVRN_HOST_PORT | 8080 | local host port the reverse proxy points at |
TAVRN_TRUSTED_PROXIES | 127.0.0.0/8,::1/128,172.16.0.0/12,192.168.0.0/16 (commented out) | Proxies whose X-Forwarded-For is trusted (IP or CIDR). Default: loopback only. docker-compose.yml sets Docker’s private ranges (the proxy reaches the node through Docker’s gateway); list only your proxies, never client networks. |
TAVRN_LOG_LEVEL | 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 | json |
TAVRN_LOG_SLOW_REQUEST_MS | 0 (commented out) | Performance (Performance): log every HTTP request that took at least this many milliseconds (method, route template, status, duration_ms, queries = database statements); 0 = off (default). |
TAVRN_DB_SLOW_STATEMENT_MS | 1000 (commented out) | Log a warning for every database statement that took at least this many milliseconds; 0 = off. |
TAVRN_DATA_DIR | /data (commented out) | Directory for keys/, blobs/ (stored files) and the generated LiveKit configuration; the image sets /data. |
TAVRN_CONFIG | /data/tavrn.toml (commented out) | Optional TOML file with further settings (keys are the variable names without TAVRN_ in lowercase); the environment wins over the file. |
TAVRN_DEV | false (commented out) | Development mode, the same as the —dev flag (SQLite, development defaults, How the configuration is loaded). Never in production. |
Web push
Section titled “Web push”Web push notifications to browsers and installed web apps (Web push): on by default. The node creates its VAPID key in /data/keys/vapid.key (back it up with the other keys) and needs outbound HTTPS to the browsers’ push services. A push goes out only while the user has no open connection.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_PUSH | on (commented out) | on | off |
TAVRN_PUSH_CONTACT | mailto:admin@example.org (commented out) | VAPID contact (mailto: or https:); default: the public URL |
TAVRN_PUSH_MAX_PER_MINUTE | 10 (commented out) | per user (1-1000); plus at most 1 per conversation per 30 s |
Metrics
Section titled “Metrics”Prometheus metrics at GET /metrics (Metrics and health): off by default. With a token (16-256 printable characters) scrapers send “Authorization: Bearer <token>”; without one only loopback and TAVRN_TRUSTED_PROXIES clients are answered. Block /metrics at the reverse proxy for the public.
| Variable | Value in .env.example | Description |
|---|---|---|
TAVRN_METRICS | off (commented out) | on | off |
TAVRN_METRICS_TOKEN | empty (commented out) | 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”Several instances of this node behind a load balancer (Scaling with Valkey). Unset: one instance, everything in memory (the default; Valkey is not needed). Set: every instance shares PostgreSQL, the /data volume and this Valkey/Redis (>= 7); the node refuses to start (exit code 3) while it is unreachable. rediss:// for TLS; the password never appears in logs or check-config. Also uncomment the “valkey” service in docker-compose.yml.
| Variable | Value in .env.example | Description |
|---|---|---|
VALKEY_PASSWORD | change-me (commented out) | the “valkey” service’s password |
TAVRN_CACHE_URL | redis://:change-me@valkey:6379/0 (commented out) | same password (URL-encode special characters) |
TAVRN_INSTANCE_ID | tavrn-1 (commented out) | Name of this instance in logs and the cache (A-Z a-z 0-9 . _ -, at most 64); default: random per start. |
TAVRN_INSTANCE_URL | http://tavrn-1:8080 (commented out) | Where the other instances reach this one over HTTP (voice, call and whiteboard requests are relayed to the leader instance); default: this host’s address towards the cache with the TAVRN_LISTEN port. |
This page is generated from tavrn-node/deploy/.env.example (AGPL-3.0-only) when the website is built.