Bedrud التوثيق

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 | CLI Reference

Ports: development vs production

ContextAPI / HTTPSLiveKit signaling (embedded)Frontend (dev only)
Local multi-process dev (config.local.yaml.example)70717072 (ws://localhost:7072)7070 (Vite)
Installer / production defaults8090 (HTTP) or 443 (TLS)7880 (often proxied at /livekit)Embedded in binary

Examples below use production-style ports (8090 / 7880). For local dev, copy server/config.local.yaml.exampleconfig.yaml and use the 707x ports.

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:

# Separate LiveKit unit (installer default for local LiveKit service)
sudo systemctl restart bedrud livekit
 
# Or single service when LiveKit is fully embedded / external
sudo systemctl restart bedrud

Full reference below.


Server Configuration

Location: config.yaml next to the binary (development) or /etc/bedrud/config.yaml (production).

Override path: --config, BEDRUD_CONFIG, or CONFIG_PATH.

Dev template: server/config.local.yaml.example.

Full Reference

server:
  port: "8090"                  # Main listener (HTTPS if TLS enabled, else HTTP)
  httpPort: ""                  # HTTP listener when TLS enabled. Default: "80". Unprivileged: "8080". Empty = default behavior.
  host: "0.0.0.0"               # Bind address
  readTimeout: 30               # Seconds
  writeTimeout: 30              # Seconds
  enableTLS: false              # Enable HTTPS. Env: SERVER_ENABLE_TLS
  disableTLS: false             # Force plain HTTP even if enableTLS was set
  certFile: ""                  # TLS certificate path. Env: SERVER_CERT_FILE
  keyFile: ""                   # TLS private key path. Env: SERVER_KEY_FILE
  domain: ""                    # Public domain (ACME, passkey RP ID). Env: SERVER_DOMAIN
  email: ""                     # ACME registration email. Env: SERVER_EMAIL
  useACME: false                # Let's Encrypt. Env: SERVER_USE_ACME
  behindProxy: false            # Trusted-proxy mode (Cloudflare, nginx). If true, trustedProxies MUST be non-empty or the process refuses to start.
  trustedProxies: []            # Required when behindProxy=true (proxy IPs/CIDRs). Never defaults to 0.0.0.0/0. Env: SERVER_TRUSTED_PROXIES
  proxyHeader: ""               # Client IP header (e.g. X-Forwarded-For). Env: SERVER_PROXY_HEADER
  certAlgorithm: "ed25519"      # Self-signed key algo: ed25519, ecdsa256, rsa2048, rsa4096. Env: SERVER_CERT_ALGORITHM
  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"                # sqlite or postgres
  path: "./bedrud-local.db"     # SQLite file path only (ignored for postgres)
  # PostgreSQL (use discrete fields — not a URL in path):
  # host: "localhost"
  # port: "5432"
  # user: "bedrud"
  # password: "secret"
  # dbname: "bedrud"
  # sslmode: "disable"
  # maxIdleConns: 10
  # maxOpenConns: 100
  # maxLifetime: 60              # minutes
 
logger:
  level: "debug"                # debug, info, warn, error (app + GORM SQL)
  outputPath: ""                # Empty = stdout
 
livekit:
  host: "ws://localhost:7880"   # Browser signaling URL (ws:// or wss://). Env: LIVEKIT_HOST
  # hostLocal: "ws://localhost:7070/livekit"  # Optional localhost override (remote debug). Env: LIVEKIT_HOST_LOCAL
  internalHost: "http://127.0.0.1:7880"  # Server-to-server API. Env: LIVEKIT_INTERNAL_HOST
  apiKey: "devkey"              # Auto-generated as gen-<32hex> if empty. Env: LIVEKIT_API_KEY
  apiSecret: "devsecret"        # Auto-generated if empty. Env: LIVEKIT_API_SECRET
  external: false               # true = skip embedded LiveKit + /livekit proxy. Env: LIVEKIT_EXTERNAL
  skipTLSVerify: false          # Skip TLS verify for LiveKit client. Env: LIVEKIT_SKIP_TLS_VERIFY
  # configPath: "/etc/bedrud/livekit.yaml"  # External LiveKit YAML. Env: LIVEKIT_CONFIG_PATH
  # nodeIP: "203.0.113.1"       # Explicit RTC node IP (disables STUN). Env: LIVEKIT_NODE_IP
  #
  # Webhook (disconnect detection) — LiveKit → Bedrud:
  #   Embedded: auto-configured to local API.
  #   External: https://<domain>/api/livekit/webhook in LiveKit dashboard / YAML.
 
auth:
  jwtSecret: "your-jwt-secret"           # JWT signing secret (prefer 32+ chars). Env: JWT_SECRET
  tokenDuration: 24                       # Access token hours
  sessionSecret: "your-session-secret"    # OAuth session cookies
  frontendURL: "http://localhost:8090"    # Frontend base for redirects. Env: AUTH_FRONTEND_URL
  passkeyChallengeTTL: 5                  # Minutes. Env: AUTH_PASSKEY_CHALLENGE_TTL
  resetTokenTTLHours: 1                   # Password reset link hours (0 = default 1). Env: AUTH_RESET_TOKEN_TTL_HOURS
 
  # Email verification (requires SMTP — see email section)
  requireEmailVerification: false         # Env: AUTH_REQUIRE_EMAIL_VERIFICATION
  verificationEmailCooldownMins: 2        # Env: AUTH_VERIFICATION_COOLDOWN_MINS
  verificationTokenTTLHours: 24           # Env: AUTH_VERIFICATION_TOKEN_TTL_HOURS
  unverifiedAccountTTLHours: 48           # Auto-delete unverified accounts; 0 = disabled. Env: AUTH_UNVERIFIED_ACCOUNT_TTL_HOURS
 
  google:
    clientId: ""
    clientSecret: ""
    redirectUrl: ""
  github:
    clientId: ""
    clientSecret: ""
    redirectUrl: ""
  twitter:
    clientId: ""                # Not clientKey
    clientSecret: ""
    redirectUrl: ""
 
cors:
  allowedOrigins: "http://localhost:8090,http://localhost:3000"
  allowedHeaders: "Origin, Content-Type, Accept, Authorization"
  allowedMethods: "GET, POST, PUT, DELETE, OPTIONS"
  allowCredentials: true
  exposeHeaders: ""
  maxAge: 0
 
rateLimit:
  authMaxRequests: 10           # 0 = disable. Env: RATELIMIT_AUTH_MAX
  authWindowSecs: 60            # Env: RATELIMIT_AUTH_WINDOW
  guestMaxRequests: 5           # Env: RATELIMIT_GUEST_MAX
  guestWindowSecs: 60           # Env: RATELIMIT_GUEST_WINDOW
  authResendMaxRequests: 3      # Verification resend. Env: RATELIMIT_AUTH_RESEND_MAX
  authResendWindowSecs: 60      # Env: RATELIMIT_AUTH_RESEND_WINDOW
  apiMaxRequests: 30            # General API. Env: RATELIMIT_API_MAX
  apiWindowSecs: 60             # Env: RATELIMIT_API_WINDOW
 
chat:
  maxUploadBytesPerUser: 524288000       # 500 MB. 0 = unlimited. Env: CHAT_MAX_UPLOAD_BYTES_PER_USER
  globalDiskThresholdBytes: 0            # 0 = unlimited. Env: CHAT_GLOBAL_DISK_THRESHOLD_BYTES
  maxMessageCount: 10000                 # Client-side retention. Env: CHAT_MAX_MESSAGE_COUNT
  messageTTLHours: 2160                  # 90 days. Env: CHAT_MESSAGE_TTL_HOURS
  uploads:
    backend: "disk"                      # disk | s3 | inline
    maxBytes: 10485760                   # Hard per-file limit (default 10 MB). Server HTTP body limit is aligned to this value.
    maxDimension: 8192                   # Max image width/height in pixels (default 8192). 0 = use default.
    inlineMaxBytes: 512000               # Below this → data URI (0 = disable)
    diskDir: "./data/uploads/chat"
    # s3:
    #   endpoint: ""
    #   bucket: ""
    #   region: ""
    #   accessKey: ""
    #   secretKey: ""
    #   publicBaseUrl: ""
 
# Reserved for planned recording feature (routes not registered in production yet)
recording:
  maxFileSizeMB: 2048                    # 0 = unlimited
  storageDir: "./data/recordings"
  maxRecordingsPerRoom: 0                # 0 = unlimited
  retentionHours: 720                    # 30 days. Env: RECORDING_RETENTION_HOURS
  cleanupIntervalHours: 24               # Env: RECORDING_CLEANUP_INTERVAL_HOURS
 
queue:
  pollInterval: 500                      # ms. Env: QUEUE_POLL_INTERVAL
  maxAttempts: 3                         # Env: QUEUE_MAX_ATTEMPTS
  concurrency: 1                         # Env: QUEUE_CONCURRENCY
 
email:
  smtpHost: ""                           # Env: EMAIL_SMTP_HOST
  smtpPort: 587                          # Env: EMAIL_SMTP_PORT
  username: ""                           # Env: EMAIL_USERNAME
  password: ""                           # Env: EMAIL_PASSWORD
  fromAddress: ""                        # Env: EMAIL_FROM_ADDRESS
  fromName: "Bedrud"                     # Env: EMAIL_FROM_NAME
  tlsSkipVerify: false                   # Env: EMAIL_TLS_SKIP_VERIFY
  smtpsMode: false                       # SMTPS port 465. Env: EMAIL_SMTPS_MODE
  # templates:
  #   instanceName: "Bedrud"
  #   supportEmail: ""
  #   instanceUrl: ""
  #   headerBgColor: "#1a1a2e"
  #   buttonBgColor: "#e11d48"
  #   subjectLines: { welcome: "", password_reset: "", verify_email: "" }
  #   preheaderText: { welcome: "", password_reset: "", verify_email: "" }

Key Settings

Database

SQLite (default): set type: "sqlite" and path to a file path.

PostgreSQL: set discrete connection fields — not a URL in path:

database:
  type: "postgres"
  host: "localhost"
  port: "5432"
  user: "bedrud"
  password: "secret"
  dbname: "bedrud"
  sslmode: "disable"

Env: DB_TYPE, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME, DB_PATH (SQLite path only).

Authentication

jwtSecret signs access tokens. Change defaults in production.

OAuth providers are optional. Each uses clientId, clientSecret, and optional redirectUrl (Google, GitHub, Twitter/X).

CORS

allowedOrigins (comma-separated) must include the URL where the frontend is served.

Rate Limiting

Four buckets (omit rateLimit for defaults; set any *MaxRequests: 0 to disable):

  • Auth — login, register, refresh, passkey
  • Guest join
  • General API
  • Verification resend (separate from auth)

Chat History

Advisory client-side retention (LiveKit does not persist data-channel messages server-side):

  • maxMessageCount — default 10000 (0 = unlimited)
  • messageTTLHours — default 2160 / 90 days (0 = forever)

Chat Uploads

chat.uploads controls image storage:

الحقلالمعنى
backenddisk (افتراضي) أو s3 أو inline
maxBytesحد صارم لكل ملف (افتراضي 10 MB). يمكن تجاوزه وقت التشغيل عبر إعداد المسؤول chatUploadMaxBytes.
maxDimensionأقصى عرض/ارتفاع بالبكسل (افتراضي 8192). يمكن تجاوزه عبر chatUploadMaxDimension.
inlineMaxBytesالصور الأصغر تُعاد كـ data URI
diskDirمسار خلفية القرص
s3.*بيانات اعتماد متوافقة مع S3 وURL عام أساسي

تعرض واجهة الاجتماع وGET /api/auth/settings القيم الفعلية لـ chatUploadMaxBytes وchatUploadMaxDimension للتحقق المسبق من العميل. تفرض واجهة الرفع نفس الحدود (413 كبير جدًا، 400 أبعاد زائدة).

Recordings

🚧 Planned. HTTP routes and queue handlers are not registered in the production server. YAML keys under recording: are reserved. Defaults: maxFileSizeMB: 2048, maxRecordingsPerRoom: 0 (unlimited), retentionHours: 720, cleanupIntervalHours: 24.

Env overrides that are loaded: RECORDING_RETENTION_HOURS, RECORDING_CLEANUP_INTERVAL_HOURS.

Queue (Job System)

Async jobs: room/user delete, suspension, chat upload (S3), outbound webhooks, email.

  • pollInterval — ms between polls (default 500)
  • maxAttempts — retries before failed (default 3); backoff 2^attempts * 5s, cap 1h
  • concurrency — worker goroutines (default 1; keep low on SQLite)

Email Verification

When requireEmailVerification: true (needs SMTP):

  • Local register/login require verified email
  • Resend via POST /api/auth/verify/resend (cooldown + rate limit)
  • Unverified accounts may be auto-deleted after unverifiedAccountTTLHours
  • Guests are exempt

Password Reset

Requires SMTP. Token TTL: resetTokenTTLHours (default 1 hour). Endpoints: POST /api/auth/forgot-password, POST /api/auth/reset-password.

Email Notifications

Transactional mail via SMTP + job queue. Templates: welcome, password_changed, password_reset, room_invite, verify_email (HTML + text). Branding via email.templates.*.


LiveKit Configuration

Typical paths: installer writes /etc/bedrud/livekit.yaml; embed mode can auto-generate temp config.

port: 7880
 
rtc:
  port_range_start: 50000
  port_range_end: 60000
  use_external_ip: true
  # node_ip: 203.0.113.1
 
turn:
  enabled: true
  udp_port: 3478
  # domain / tls_port / cert_file / key_file for TURN/TLS
 
keys:
  devkey: "devsecret"           # Must match livekit.apiKey / apiSecret
 
logging:
  level: info
 
room:
  auto_create: true
  empty_timeout: 60
  departure_timeout: 60
  max_participants: 20
  enable_remote_unmute: true

The keys in LiveKit YAML must match livekit.apiKey and livekit.apiSecret in Bedrud’s config.yaml.

Webhook (Disconnect Detection)

Bedrud receives LiveKit webhooks at POST /api/livekit/webhook (JWT signed with the same API key/secret).

EventAction
participant_disconnectedMarks participant inactive
room_finishedMarks all participants + room inactive

Embedded LiveKit: webhook URL auto-configured.
External LiveKit: set webhook URL to https://<your-domain>/api/livekit/webhook.

See WebRTC Connectivity and TURN Server.


Environment Variables

Only variables actually applied in config.Load() are listed.

export SERVER_PORT=8090
export SERVER_HTTP_PORT=8080
export DB_PATH=/var/lib/bedrud/bedrud.db
export JWT_SECRET=production-secret
export LIVEKIT_HOST=wss://meet.example.com/livekit
export LIVEKIT_API_KEY=prodkey
export LIVEKIT_API_SECRET=prodsecret

Full Environment Variable Reference

Env VarYAML PathDescription
SERVER_PORTserver.portMain listener port
SERVER_HTTP_PORTserver.httpPortHTTP port when TLS enabled
SERVER_ENABLE_TLSserver.enableTLSEnable HTTPS
SERVER_CERT_FILEserver.certFileTLS certificate path
SERVER_KEY_FILEserver.keyFileTLS private key path
SERVER_DOMAINserver.domainDomain name
SERVER_EMAILserver.emailACME email
SERVER_USE_ACMEserver.useACMELet’s Encrypt
SERVER_TRUSTED_PROXIESserver.trustedProxiesComma-separated proxy IPs
SERVER_PROXY_HEADERserver.proxyHeaderClient IP header
SERVER_CERT_ALGORITHMserver.certAlgorithmSelf-signed key algorithm
SERVER_MAX_ROOMS_PER_USERserver.maxRoomsPerUserActive rooms per user
SERVER_MAX_PARTICIPANTS_LIMITserver.maxParticipantsLimitHard ceiling for room capacity (default 1000)
DB_HOSTdatabase.hostPostgreSQL host
DB_PORTdatabase.portPostgreSQL port
DB_USERdatabase.userPostgreSQL user
DB_PASSWORDdatabase.passwordPostgreSQL password
DB_NAMEdatabase.dbnamePostgreSQL database name
DB_TYPEdatabase.typesqlite or postgres
DB_PATHdatabase.pathSQLite file path
LIVEKIT_HOSTlivekit.hostBrowser signaling URL
LIVEKIT_HOST_LOCALlivekit.hostLocalLocalhost signaling override
LIVEKIT_INTERNAL_HOSTlivekit.internalHostInternal API URL
LIVEKIT_API_KEYlivekit.apiKeyLiveKit API key
LIVEKIT_API_SECRETlivekit.apiSecretLiveKit API secret
LIVEKIT_CONFIG_PATHlivekit.configPathExternal LiveKit YAML path
LIVEKIT_NODE_IPlivekit.nodeIPExplicit RTC node IP
LIVEKIT_EXTERNALlivekit.externalUse external LiveKit instead of embedded (true/false)
LIVEKIT_SKIP_TLS_VERIFYlivekit.skipTLSVerifySkip TLS verification for LiveKit client (true/false)
JWT_SECRETauth.jwtSecretJWT signing secret
AUTH_FRONTEND_URLauth.frontendURLFrontend URL
AUTH_PASSKEY_CHALLENGE_TTLauth.passkeyChallengeTTLPasskey challenge minutes
AUTH_REQUIRE_EMAIL_VERIFICATIONauth.requireEmailVerificationGate on email verify
AUTH_VERIFICATION_COOLDOWN_MINSauth.verificationEmailCooldownMinsResend cooldown
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 hours
CHAT_MAX_UPLOAD_BYTES_PER_USERchat.maxUploadBytesPerUserPer-user upload quota
CHAT_GLOBAL_DISK_THRESHOLD_BYTESchat.globalDiskThresholdBytesGlobal upload ceiling
CHAT_MAX_MESSAGE_COUNTchat.maxMessageCountClient chat message cap
CHAT_MESSAGE_TTL_HOURSchat.messageTTLHoursClient chat TTL hours
QUEUE_POLL_INTERVALqueue.pollIntervalJob poll interval (ms)
QUEUE_MAX_ATTEMPTSqueue.maxAttemptsMax job retries
QUEUE_CONCURRENCYqueue.concurrencyWorker count
EMAIL_SMTP_HOSTemail.smtpHostSMTP host
EMAIL_SMTP_PORTemail.smtpPortSMTP port
EMAIL_USERNAMEemail.usernameSMTP user
EMAIL_PASSWORDemail.passwordSMTP password
EMAIL_FROM_ADDRESSemail.fromAddressFrom address
EMAIL_FROM_NAMEemail.fromNameFrom display name
EMAIL_TLS_SKIP_VERIFYemail.tlsSkipVerifySkip SMTP TLS verify
EMAIL_SMTPS_MODEemail.smtpsModeSMTPS mode
CORS_ALLOWED_ORIGINScors.allowedOriginsAllowed origins
CORS_ALLOWED_HEADERScors.allowedHeadersAllowed headers
CORS_ALLOWED_METHODScors.allowedMethodsAllowed methods
CORS_ALLOW_CREDENTIALScors.allowCredentialsAllow credentials
CORS_EXPOSE_HEADERScors.exposeHeadersExposed headers
CORS_MAX_AGEcors.maxAgePreflight max age
RATELIMIT_AUTH_MAXrateLimit.authMaxRequestsAuth rate limit max
RATELIMIT_AUTH_WINDOWrateLimit.authWindowSecsAuth window seconds
RATELIMIT_GUEST_MAXrateLimit.guestMaxRequestsGuest rate limit max
RATELIMIT_GUEST_WINDOWrateLimit.guestWindowSecsGuest window seconds
RATELIMIT_AUTH_RESEND_MAXrateLimit.authResendMaxRequestsResend rate limit max
RATELIMIT_AUTH_RESEND_WINDOWrateLimit.authResendWindowSecsResend window seconds
RATELIMIT_API_MAXrateLimit.apiMaxRequestsAPI rate limit max
RATELIMIT_API_WINDOWrateLimit.apiWindowSecsAPI window seconds
RECORDING_RETENTION_HOURSrecording.retentionHoursRecording retention hours
RECORDING_CLEANUP_INTERVAL_HOURSrecording.cleanupIntervalHoursRecording cleanup interval

Config path resolution also honors BEDRUD_CONFIG / CONFIG_PATH (CLI).


Production Checklist

  • Change jwtSecret and sessionSecret to strong random values
  • Set logger.level to info or warn
  • Configure TLS (installer, ACME, or reverse proxy + behindProxy)
  • Set cors.allowedOrigins to your production domain
  • Configure PostgreSQL for multi-user production if needed
  • Configure SMTP if using email verification / password reset
  • Open LiveKit RTC UDP range and TURN ports in the firewall
  • Set up log rotation for /var/log/bedrud/

Privileged Ports (< 1024)

When TLS is enabled, Bedrud may also listen on HTTP (default port 80). Non-root options:

server:
  httpPort: "8080"
export SERVER_HTTP_PORT=8080
# or
sudo setcap 'cap_net_bind_service=+ep' $(which bedrud)

setcap must be re-run after each binary update.

For complete production setup, see the Deployment Guide.