Bedrud uses YAML configuration files for both the main server and the embedded LiveKit media server.

See also: Quick Start | Installation | Deployment Guide | Docker Guide

Minimum Production Config

Default config works for development. For production, change these values in /etc/bedrud/config.yaml:

auth:
  jwtSecret: "change-to-random-string-32-chars"
  sessionSecret: "change-to-another-random-string"

Restart after changes:

sudo systemctl restart bedrud livekit

Full reference below.


Server Configuration

Location: server/config.yaml (development) or /etc/bedrud/config.yaml (production)

Full Reference

server:
  port: 8090                    # HTTPS port (or HTTP if TLS disabled)
  httpPort: ""                  # HTTP listener when TLS enabled. Default: "80". Unprivileged: "8080". Empty = disabled.
  host: "localhost"             # Bind address
  maxParticipantsLimit: 1000    # Hard ceiling for room capacity (0 = unlimited). Env: SERVER_MAX_PARTICIPANTS_LIMIT
  maxRoomsPerUser: 100          # Max active rooms per user (0 = unlimited). Env: SERVER_MAX_ROOMS_PER_USER
 
database:
  type: "sqlite"                # Database type: sqlite or postgres
  path: "data.db"               # SQLite database file path
 
logger:
  level: "debug"                # Log level: debug, info, warn, error. Controls both app and SQL query logs.
  outputPath: ""                # Log file path (empty = stdout)
 
livekit:
  host: "ws://localhost:7880"   # Client WebSocket URL (ws:// for plain, wss:// for TLS). Env: LIVEKIT_HOST
  internalHost: "http://127.0.0.1:7880"  # Server-to-server API URL (http:// or https://). Env: LIVEKIT_INTERNAL_HOST
  apiKey: "devkey"              # LiveKit API key (auto-generated as gen-<32hex> if empty). Env: LIVEKIT_API_KEY
  apiSecret: "devsecret"        # LiveKit API secret (64-char hex auto-generated if empty). Env: LIVEKIT_API_SECRET
  external: false               # true = use external LiveKit, false = embedded. Env: LIVEKIT_EXTERNAL
  skipTLSVerify: false          # Skip TLS verification for self-signed LiveKit certs. Env: LIVEKIT_SKIP_TLS_VERIFY
  # configPath: "/etc/bedrud/livekit.yaml"  # Path to LiveKit YAML. Env: LIVEKIT_CONFIG_PATH
  # nodeIP: "203.0.113.1"       # Explicit node IP for embedded LiveKit RTC (disables STUN). Env: LIVEKIT_NODE_IP
  #
  # Webhook (disconnect detection):
  #   Embedded LiveKit: auto-configures webhook URL -> local API.
  #   External LiveKit (Cloud): configure URL in LiveKit dashboard -> https://<domain>/api/livekit/webhook
  #   External LiveKit (self-hosted): add to LiveKit YAML:
  #     webhook:
  #       urls: ["https://<domain>/api/livekit/webhook"]
  #       api_key: <your-api-key>
 
auth:
  jwtSecret: "your-jwt-secret"           # Secret for signing JWT tokens
  tokenDuration: 24                       # Token expiration in hours
  sessionSecret: "your-session-secret"    # Secret for session cookies
  frontendURL: "http://localhost:8090"    # Frontend URL (for OAuth redirects)
  passkeyChallengeTTL: 5                # Passkey challenge expiry in minutes
  # resetTokenTTLHours controls how long password reset links remain valid.
  # 0 means use default (1 hour). Env: AUTH_RESET_TOKEN_TTL_HOURS
  resetTokenTTLHours: 1                # Password reset token expiry in hours
 
  # Email verification (when enabled, users must verify before using the app)
  # Requires SMTP to be configured — see email section below
  requireEmailVerification: false        # When true, gate access behind email verification. Env: AUTH_REQUIRE_EMAIL_VERIFICATION
  verificationEmailCooldownMins: 2       # Min time between verification resends. Env: AUTH_VERIFICATION_COOLDOWN_MINS
  verificationTokenTTLHours: 24          # How long verification links stay valid. Env: AUTH_VERIFICATION_TOKEN_TTL_HOURS
  unverifiedAccountTTLHours: 48          # Auto-delete unverified accounts (0 = disabled). Env: AUTH_UNVERIFIED_ACCOUNT_TTL_HOURS
 
  # OAuth providers (optional)
  google:
    clientId: ""
    clientSecret: ""
  github:
    clientId: ""
    clientSecret: ""
  twitter:
    clientKey: ""
    clientSecret: ""
 
cors:
  allowedOrigins: "http://localhost:8090,http://localhost:3000"  # Comma-separated
  allowedHeaders: "Origin, Content-Type, Accept, Authorization"
  allowedMethods: "GET, POST, PUT, DELETE, OPTIONS"
  allowCredentials: true
 
rateLimit:
  authMaxRequests: 10       # Max auth requests per window (login, register, refresh, passkey). 0 = disable
  authWindowSecs: 60         # Auth rate limit window in seconds
  guestMaxRequests: 5        # Max guest join requests per window. 0 = disable
  guestWindowSecs: 60        # Guest rate limit window in seconds
  authResendMaxRequests: 3   # Max verification resend requests per window. 0 = disable. Env: RATELIMIT_AUTH_RESEND_MAX
  authResendWindowSecs: 60   # Resend rate limit window in seconds. Env: RATELIMIT_AUTH_RESEND_WINDOW
 
chat:
  maxUploadBytesPerUser: 524288000       # Per-user upload quota (500 MB). 0 = unlimited. Env: CHAT_MAX_UPLOAD_BYTES_PER_USER
  globalDiskThresholdBytes: 0            # Global storage ceiling across all users. 0 = unlimited. Env: CHAT_GLOBAL_DISK_THRESHOLD_BYTES
  maxMessageCount: 10000                 # Max chat messages kept per room (0 = unlimited). Env: CHAT_MAX_MESSAGE_COUNT
  messageTTLHours: 2160                  # Purge messages older than this in hours (0 = forever). 2160 = 90 days. Env: CHAT_MESSAGE_TTL_HOURS
 
recording:
  maxFileSizeMB: 2048           # Max recording file size in MB (0 = unlimited). Env: RECORDING_MAX_FILE_SIZE_MB
  storageDir: "./data/recordings"  # Local storage path for disk-backed recordings. Env: RECORDING_STORAGE_DIR
  maxRecordingsPerRoom: 100     # Max total recordings per room (0 = unlimited). Env: RECORDING_MAX_PER_ROOM
 
queue:
  pollInterval: 500          # ms between job polls. Env: QUEUE_POLL_INTERVAL
  maxAttempts: 3             # max retries before failed. Env: QUEUE_MAX_ATTEMPTS
  concurrency: 1             # worker goroutines. Env: QUEUE_CONCURRENCY
 
email:
  smtpHost: ""               # SMTP server hostname. Env: EMAIL_SMTP_HOST
  smtpPort: 587              # SMTP port (STARTTLS). Env: EMAIL_SMTP_PORT
  username: ""               # SMTP username. Env: EMAIL_USERNAME
  password: ""               # SMTP password. Env: EMAIL_PASSWORD
  fromAddress: ""            # Sender email address. Env: EMAIL_FROM_ADDRESS
  fromName: "Bedrud"         # Sender display name. Env: EMAIL_FROM_NAME
  tlsSkipVerify: false       # Skip TLS certificate validation. Env: EMAIL_TLS_SKIP_VERIFY
  smtpsMode: false           # Direct TLS (SMTPS, port 465). Env: EMAIL_SMTPS_MODE
  # templates:                 # Email branding and per-template overrides (optional)
  #   instanceName: "Bedrud"   # Instance name in email headers/footers
  #   supportEmail: ""         # Contact email for support links
  #   instanceUrl: ""          # Link to instance homepage
  #   headerBgColor: "#1a1a2e" # Header background color (hex)
  #   buttonBgColor: "#e11d48" # Call-to-action button color (hex)
  #   subjectLines:            # Per-template subject overrides
  #     welcome: ""
  #     password_reset: ""
  #     verify_email: ""
  #   preheaderText:           # Per-template preheader overrides
  #     welcome: ""
  #     password_reset: ""
  #     verify_email: ""

Key Settings

Database

By default, Bedrud uses SQLite with a file at the configured path. For production with higher concurrency, switch to PostgreSQL by providing a connection string instead. When using PostgreSQL, the path field holds a connection string, not a file path:

database:
  type: "postgres"
  path: "postgres://user:password@localhost:5432/bedrud?sslmode=disable"

Authentication

The jwtSecret is used to sign access and refresh tokens. Change this from the default in production.

OAuth providers are optional. If you don’t configure them, social login buttons won’t appear in the UI. Each provider requires registering an OAuth app with the respective service and providing the client ID and secret.

CORS

The allowedOrigins string (comma-separated) must include the URL where your frontend is served. In development, this is http://localhost:3000. In production, set it to your domain (e.g., https://meet.example.com).

Rate Limiting

Rate limiting protects auth endpoints from brute-force and DoS attacks. Four buckets:

  • Auth endpoints (login, register, refresh, passkey login/signup) — default: 10 requests per 60 seconds per IP
  • Guest join — default: 5 requests per 60 seconds per IP
  • General API — default: 30 requests per 60 seconds per IP
  • Verification resend — default: 3 requests per 60 seconds per IP (separate from auth bucket)

Omit the rateLimit section to keep defaults. Set any *MaxRequests: 0 to disable that bucket.

Chat History

Bedrud applies advisory retention limits to in-room chat messages. Since LiveKit — the real-time media layer — does not persist data channel messages server-side, these limits are enforced on the client:

  • maxMessageCount — caps the number of chat messages held in memory and sessionStorage per room. When exceeded, the oldest messages are trimmed. Default: 10000. Set to 0 for unlimited.
  • messageTTLHours — messages older than this threshold are purged from the local cache. Default: 2160 (90 days). Set to 0 to keep messages indefinitely.

Chat messages in Bedrud are transmitted exclusively through LiveKit data channels and are not stored on the server. The retention limits above control how many messages the frontend keeps in memory and sessionStorage. A dedicated chat history service (with server-side persistence) would be needed for true history across sessions — this is tracked as a future enhancement.

Recordings

🚧 Recording settings are planned for a future release.

Room recording is controlled by the RecordingsEnabled system setting (toggle in admin Settings → General). When enabled:

  • Moderators can start/stop composite MP4 recordings from the meeting controls bar
  • Recordings are stored alongside chat uploads using the configured storage backend (disk/S3)
  • Each room has a per-room recordingsAllowed toggle that must also be enabled
  • Failed/pending recordings over 7 days old are automatically cleaned up daily
  • Non-persistent rooms have a configurable per-room recording cap (maxRecordingsPerRoom) to prevent abuse
  • After a room is deleted, the recording creator can still access their recordings from the same room URL

YAML Reference:

  • maxFileSizeMB — maximum file size for a single recording in MB. 0 = unlimited. Default: 2048. Env: RECORDING_MAX_FILE_SIZE_MB
  • storageDir — directory for disk-backed recordings. Only used when S3 is not configured. Default: ./data/recordings. Env: RECORDING_STORAGE_DIR
  • maxRecordingsPerRoom — caps total recordings per room (all statuses: completed, failed, etc.). 0 = unlimited. Only enforced for non-persistent rooms. Default: 100. Env: RECORDING_MAX_PER_ROOM

See the Recordings Guide for full details.

Queue (Job System)

The internal job queue processes asynchronous background tasks like user/room deletion, suspension, and chat uploads.

  • pollInterval — how often the worker checks for new jobs (in ms). Lower values reduce latency at the cost of more database queries.
  • maxAttempts — maximum retry attempts before a job is marked as failed. Retries use exponential backoff (2^attempt * 5s, capped at 1h).
  • concurrency — number of worker goroutines. Increase for higher throughput on PostgreSQL. SQLite is limited to single-worker (one connection).

Email Verification

Email verification gates local (non-OAuth) registration and login behind email confirmation. When requireEmailVerification: true:

  • Registration: user must verify their email before first login
  • Login: unverified users get a 403 with "requiresVerification": true — they must click the link from their inbox
  • Resend: unverified users can request a new verification email via POST /api/auth/verify/resend (rate-limited by verificationEmailCooldownMins)
  • Auto-cleanup: accounts that never verify are automatically deleted after unverifiedAccountTTLHours
  • Email change: changing email triggers a new verification flow (user stays logged in, new token issued with EmailVerifiedAt=nil)

Guest users are exempt from email verification — they have no email and are not affected.

Email verification requires SMTP to be configured. If requireEmailVerification: true but email.smtpHost is empty, the server logs a warning at startup and verification emails cannot be sent.

Password Reset

Users can reset their password via email. The flow requires SMTP to be configured:

  1. User requests reset via POST /api/auth/forgot-password with their email
  2. Server sends email with a reset link containing a time-limited token
  3. User opens link, sets new password via POST /api/auth/reset-password

The reset token expires after resetTokenTTLHours (default 1 hour). Rate limiting applies to the forgot-password endpoint (shared with auth rate limit bucket).

Email Notifications

Bedrud sends transactional emails (welcome, password change confirmation, room invites) via SMTP. Emails are processed asynchronously through the internal job queue — the HTTP request completes immediately, and the email is sent by a background worker.

  • smtpHost / smtpPort — SMTP server address. Port 587 (STARTTLS) is the default. Set smtpsMode: true for direct TLS on port 465.
  • username / password — SMTP authentication credentials. Uses PLAIN auth; requires STARTTLS or SMTPS mode (credentials are never sent over unencrypted connections).
  • fromAddress / fromName — sender identity for outgoing emails. Leave fromAddress empty to use noreply@bedrud (may be rejected by strict mail servers).
  • tlsSkipVerify — set to true if your SMTP server uses a self-signed certificate. Default: false.
  • smtpsMode — enable direct TLS (SMTPS) instead of STARTTLS. Use for port 465. Default: false.

Emails are only sent when SMTP is configured (smtpHost is non-empty). When SMTP is not set up, email jobs are silently marked done and the rendered message body is logged at WARN level for debugging.

Available email templates:

TemplateTriggerData Keys
welcomeRegistrationName, LoginURL
password_changedPassword changeIPAddress
password_resetPassword reset flowResetURL, IPAddress
room_inviteRoom inviteInviterName, RoomName, JoinURL
verify_emailEmail verificationVerifyURL

Templates are embedded at build time but branding can be customized at runtime via email.templates.* config (instance name, colors, subject lines, preheader text). See Email Configuration. Unknown template names fall back to a generic notification template with key-value data.

Each template has two embedded variants: .html (rich HTML) and .txt (plain text fallback for non-HTML email clients). The appropriate variant is selected automatically based on the recipient’s email client capabilities.


LiveKit Configuration

Location: server/config/livekit.yaml (development) or /etc/bedrud/livekit.yaml (production)

Bedrud can run an embedded LiveKit server (single binary) or connect to an external LiveKit instance. The embedded mode auto-generates a temporary config file with room settings, TURN, and webhook URL.

If livekit.apiKey is not set in config, Bedrud generates a random keypair (gen-<32hex> / 64-char hex) at startup. The embedded LiveKit server and the webhook handler both use the same generated key — everything works out of the box with no manual configuration.

port: 7880                      # LiveKit HTTP/WebSocket port
 
rtc:
  port_range_start: 50000       # UDP port range start
  port_range_end: 60000         # UDP port range end
  use_external_ip: true         # Use STUN to detect public IP; requires internet
  # node_ip: 203.0.113.1        # Explicit node IP (disables STUN). Required in containers when use_external_ip fails.
 
turn:
  enabled: true
  # domain: "turn.example.com"  # Required when tls_port is set (for TLS cert verification)
  # tls_port: 5349              # TLS port; requires cert_file, key_file, and domain
  udp_port: 3478                # UDP port (no TLS cert needed)
 
keys:
  devkey: "devsecret"           # Must match server config
 
logging:
  level: info
 
room:
  auto_create: true              # Auto-create rooms when participants join
  empty_timeout: 60              # Seconds before deleting empty room
  departure_timeout: 60          # Seconds to keep room after all participants leave
  max_participants: 20           # Max participants per room (0 = unlimited)
  enable_remote_unmute: true     # Allow server-side unmute of participants

The keys in livekit.yaml must match the livekit.apiKey and livekit.apiSecret in the server’s config.yaml.

RTC Port Range

LiveKit uses UDP ports for media streams. The default range 50000-60000 works for most setups. If running behind a firewall, ensure these ports are open.

See WebRTC Connectivity for architecture and troubleshooting.

TURN Server

The embedded TURN server relays media for clients behind restrictive NATs or corporate firewalls. It’s enabled by default on ports 3478 (UDP) and 5349 (TLS).

TURN is a last-resort relay - most clients (~80%) connect directly via UDP and never use it. When TURN activates, the server carries all relayed media bandwidth.

TLS requirement: TURN/TLS (port 5349) needs a valid TLS certificate. For production, set turn.tls_port: 443 and point cert_file/key_file to your certificate, or place a Layer 4 load balancer in front with external_tls: true.

See the TURN Server Guide for architecture, config details, bandwidth calculations, and troubleshooting.

Webhook (Disconnect Detection)

Bedrud receives webhook events from LiveKit to detect when participants disconnect unexpectedly (e.g., browser crash, network drop) and mark them as inactive in the database.

Endpoint: POST /api/livekit/webhook

Authentication: Uses LiveKit’s JWT signing with the same apiKey/apiSecret. No separate secret needed.

Embedded LiveKit: When Bedrud manages its own LiveKit server (default), the webhook URL is auto-configured in the generated LiveKit YAML. No user action needed.

External LiveKit (Cloud or self-hosted with external: true): Manually configure the webhook URL:

  • LiveKit Cloud: Settings → Webhooks → Create new webhook with URL https://<your-domain>/api/livekit/webhook
  • Self-hosted LiveKit: Add to your LiveKit YAML config:
    webhook:
      urls: ["https://<your-domain>/api/livekit/webhook"]
      api_key: <your-api-key>

LiveKit version note: LiveKit v1.12+ removed the top-level tls config field. If self-hosting external LiveKit ≥v1.12, omit tls: from your LiveKit YAML and use http:// for internalHost / ws:// for host in Bedrud’s config. TURN TLS is still supported via turn.cert_file/turn.key_file.

Events handled:

EventAction
participant_disconnectedMarks participant as inactive in room_participants
room_finishedMarks all participants + room as inactive

If apiKey is left empty in config, a random keypair is generated at startup. The embedded LiveKit server and webhook handler both use the same generated key — no manual configuration needed.

Room Settings

The room: section controls meeting room behavior:

  • auto_create - Automatically create rooms when participants join (default: true)
  • empty_timeout - Seconds before deleting a room that was never joined (default: 60)
  • departure_timeout - Seconds to keep room active after all participants leave (default: 60)
  • max_participants - Maximum participants per room. Set to 0 for no limit (default: 20)
  • enable_remote_unmute - Allow server-side muting/unmuting of participants (default: true)

Tuning for capacity:

  • Small team meetings: max_participants: 10-20
  • Large webinars: max_participants: 100 (or 0 for unlimited)
  • Resource-constrained servers: Lower max_participants to reduce CPU/memory usage

Environment Variables

Configuration values can be overridden with environment variables. The naming follows a per-section prefix convention:

For Docker deployments, see the Docker Guide.

export SERVER_PORT=8090
export SERVER_HTTP_PORT=8080  # Optional: HTTP port when TLS enabled (default: 80)
export DB_PATH=/var/lib/bedrud/bedrud.db
export JWT_SECRET=production-secret
export LIVEKIT_HOST=http://localhost:8090/livekit
export LIVEKIT_API_KEY=prodkey
export LIVEKIT_API_SECRET=prodsecret

Full Environment Variable Reference

Env VarYAML PathDescription
SERVER_PORTserver.portHTTPS port (or HTTP if TLS disabled)
SERVER_HTTP_PORTserver.httpPortHTTP listener port when TLS enabled (default 80)
SERVER_ENABLE_TLSserver.enableTLSEnable HTTPS (true/false)
SERVER_CERT_FILEserver.certFilePath to TLS certificate
SERVER_KEY_FILEserver.keyFilePath to TLS private key
SERVER_DOMAINserver.domainDomain name
SERVER_EMAILserver.emailEmail for Let’s Encrypt
SERVER_USE_ACMEserver.useACMEEnable automatic Let’s Encrypt (true/false)
SERVER_TRUSTED_PROXIESserver.trustedProxiesComma-separated trusted proxy IPs
SERVER_PROXY_HEADERserver.proxyHeaderHeader to read client IP from (e.g., X-Forwarded-For)
DB_HOSTdatabase.hostDatabase host (PostgreSQL)
DB_PORTdatabase.portDatabase port
DB_USERdatabase.userDatabase user
DB_PASSWORDdatabase.passwordDatabase password
DB_NAMEdatabase.dbnameDatabase name
DB_TYPEdatabase.typesqlite or postgres
DB_PATHdatabase.pathSQLite file path or PostgreSQL connection string
LIVEKIT_HOSTlivekit.hostExternal LiveKit URL
LIVEKIT_INTERNAL_HOSTlivekit.internalHostInternal LiveKit URL
LIVEKIT_API_KEYlivekit.apiKeyLiveKit API key (auto-generated as gen-<32hex> if empty)
LIVEKIT_API_SECRETlivekit.apiSecretLiveKit API secret (64-char hex auto-generated if empty)
LIVEKIT_CONFIG_PATHlivekit.configPathPath to external LiveKit YAML config
LIVEKIT_EXTERNALlivekit.externalUse external LiveKit instead of embedded (true/false)
LIVEKIT_SKIP_TLS_VERIFYlivekit.skipTLSVerifySkip TLS verification for self-signed LiveKit certs
LIVEKIT_NODE_IPlivekit.nodeIPExplicit node IP for embedded LiveKit RTC (disables STUN)
SERVER_CERT_ALGORITHMserver.certAlgorithmKey algorithm for cert generation: ed25519, ecdsa256, rsa2048, rsa4096
SERVER_MAX_PARTICIPANTS_LIMITserver.maxParticipantsLimitHard ceiling for room capacity (default 1000)
SERVER_MAX_ROOMS_PER_USERserver.maxRoomsPerUserMax active rooms per user (default 100, 0 = unlimited)
CHAT_MAX_UPLOAD_BYTES_PER_USERchat.maxUploadBytesPerUserPer-user upload quota in bytes (default 524288000, 0 = unlimited)
CHAT_GLOBAL_DISK_THRESHOLD_BYTESchat.globalDiskThresholdBytesGlobal upload storage ceiling in bytes (default 0 = unlimited)
CHAT_MAX_MESSAGE_COUNTchat.maxMessageCountMax chat messages kept per room (default 10000, 0 = unlimited)
CHAT_MESSAGE_TTL_HOURSchat.messageTTLHoursMax age of chat messages in hours (default 2160 = 90 days, 0 = forever)
QUEUE_POLL_INTERVALqueue.pollIntervalJob poll interval in ms (default 500)
QUEUE_MAX_ATTEMPTSqueue.maxAttemptsMax job retries before marking failed (default 3)
QUEUE_CONCURRENCYqueue.concurrencyWorker goroutine count (default 1)
EMAIL_SMTP_HOSTemail.smtpHostSMTP server hostname
EMAIL_SMTP_PORTemail.smtpPortSMTP server port (default 587)
EMAIL_USERNAMEemail.usernameSMTP username
EMAIL_PASSWORDemail.passwordSMTP password
EMAIL_FROM_ADDRESSemail.fromAddressFrom email address
EMAIL_FROM_NAMEemail.fromNameFrom display name (default “Bedrud”)
EMAIL_TLS_SKIP_VERIFYemail.tlsSkipVerifySkip TLS certificate validation for self-signed SMTP certs
EMAIL_SMTPS_MODEemail.smtpsModeEnable direct TLS (SMTPS) mode for port 465
JWT_SECRETauth.jwtSecretSecret for signing JWT tokens
AUTH_REQUIRE_EMAIL_VERIFICATIONauth.requireEmailVerificationGate access behind email verification (true/false)
AUTH_VERIFICATION_COOLDOWN_MINSauth.verificationEmailCooldownMinsMin time between verification email resends (minutes)
AUTH_VERIFICATION_TOKEN_TTL_HOURSauth.verificationTokenTTLHoursHow long verification links remain valid (hours)
AUTH_UNVERIFIED_ACCOUNT_TTL_HOURSauth.unverifiedAccountTTLHoursAuto-delete unverified accounts after N hours (0 = disabled)
AUTH_RESET_TOKEN_TTL_HOURSauth.resetTokenTTLHoursPassword reset token expiry in hours (default 1)
AUTH_FRONTEND_URLauth.frontendURLFrontend URL for OAuth redirects
AUTH_PASSKEY_CHALLENGE_TTLauth.passkeyChallengeTTLPasskey challenge expiry in minutes
CORS_ALLOWED_ORIGINScors.allowedOriginsComma-separated allowed origins
CORS_ALLOWED_HEADERScors.allowedHeadersAllowed request headers
CORS_ALLOWED_METHODScors.allowedMethodsAllowed HTTP methods
CORS_ALLOW_CREDENTIALScors.allowCredentialsAllow credentials (true/false)
CORS_EXPOSE_HEADERScors.exposeHeadersHeaders exposed to the browser
CORS_MAX_AGEcors.maxAgePreflight cache duration in seconds
RATELIMIT_AUTH_MAXrateLimit.authMaxRequestsMax auth requests per rate limit window
RATELIMIT_AUTH_WINDOWrateLimit.authWindowSecsAuth rate limit window in seconds
RATELIMIT_GUEST_MAXrateLimit.guestMaxRequestsMax guest join requests per window
RATELIMIT_GUEST_WINDOWrateLimit.guestWindowSecsGuest rate limit window in seconds
RATELIMIT_AUTH_RESEND_MAXrateLimit.authResendMaxRequestsMax verification resend requests per window
RATELIMIT_AUTH_RESEND_WINDOWrateLimit.authResendWindowSecsResend rate limit window in seconds

Production Checklist

  • Change jwtSecret and sessionSecret to strong random values
  • Set logger.level to info or warn
  • Configure TLS (via installer or reverse proxy)
  • Set cors.allowedOrigins to your production domain
  • Configure OAuth providers if needed
  • Open LiveKit RTC port range in your firewall
  • Set up log rotation for /var/log/bedrud/

Privileged Ports (< 1024)

When TLS is enabled, Bedrud also starts an HTTP listener (default port 80). Ports below 1024 require root on Linux. If running as a non-root user:

Option 1: Use an unprivileged port (recommended for dev/reverse-proxy setups):

server:
  httpPort: "8080"

Or via environment variable:

export SERVER_HTTP_PORT=8080

Option 2: Allow the binary to bind low ports without root:

sudo setcap 'cap_net_bind_service=+ep' $(which bedrud)

setcap must be re-run after each binary update (reinstall, upgrade, new build).

For complete production setup, see the Deployment Guide.