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
| Context | API / HTTPS | LiveKit signaling (embedded) | Frontend (dev only) |
|---|---|---|---|
Local multi-process dev (config.local.yaml.example) | 7071 | 7072 (ws://localhost:7072) | 7070 (Vite) |
| Installer / production defaults | 8090 (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.example → config.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 bedrudFull 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— default10000(0= unlimited)messageTTLHours— default2160/ 90 days (0= forever)
Chat Uploads
chat.uploads controls image storage:
| Alan | Anlam |
|---|---|
backend | disk (varsayılan), s3 veya inline |
maxBytes | Dosya başına sert limit (varsayılan 10 MB). Çalışma anında admin chatUploadMaxBytes ile geçersiz kılınabilir. |
maxDimension | Maks. genişlik/yükseklik piksel (varsayılan 8192). Admin chatUploadMaxDimension ile geçersiz kılınabilir. |
inlineMaxBytes | Daha küçük görseller data URI olarak döner |
diskDir | Disk arka ucu yolu |
s3.* | S3 uyumlu kimlik bilgileri ve genel taban URL |
Toplantı arayüzü ve GET /api/auth/settings, istemcinin yüklemeden önce doğrulaması için etkin chatUploadMaxBytes ve chatUploadMaxDimension değerlerini sunar. Yükleme API’si aynı limitleri uygular (413 aşırı büyük, 400 boyut aşımı).
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 (default500)maxAttempts— retries beforefailed(default3); backoff2^attempts * 5s, cap 1hconcurrency— worker goroutines (default1; 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: trueThe 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).
| Event | Action |
|---|---|
participant_disconnected | Marks participant inactive |
room_finished | Marks 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=prodsecretFull Environment Variable Reference
| Env Var | YAML Path | Description |
|---|---|---|
SERVER_PORT | server.port | Main listener port |
SERVER_HTTP_PORT | server.httpPort | HTTP port when TLS enabled |
SERVER_ENABLE_TLS | server.enableTLS | Enable HTTPS |
SERVER_CERT_FILE | server.certFile | TLS certificate path |
SERVER_KEY_FILE | server.keyFile | TLS private key path |
SERVER_DOMAIN | server.domain | Domain name |
SERVER_EMAIL | server.email | ACME email |
SERVER_USE_ACME | server.useACME | Let’s Encrypt |
SERVER_TRUSTED_PROXIES | server.trustedProxies | Comma-separated proxy IPs |
SERVER_PROXY_HEADER | server.proxyHeader | Client IP header |
SERVER_CERT_ALGORITHM | server.certAlgorithm | Self-signed key algorithm |
SERVER_MAX_ROOMS_PER_USER | server.maxRoomsPerUser | Active rooms per user |
SERVER_MAX_PARTICIPANTS_LIMIT | server.maxParticipantsLimit | Hard ceiling for room capacity (default 1000) |
DB_HOST | database.host | PostgreSQL host |
DB_PORT | database.port | PostgreSQL port |
DB_USER | database.user | PostgreSQL user |
DB_PASSWORD | database.password | PostgreSQL password |
DB_NAME | database.dbname | PostgreSQL database name |
DB_TYPE | database.type | sqlite or postgres |
DB_PATH | database.path | SQLite file path |
LIVEKIT_HOST | livekit.host | Browser signaling URL |
LIVEKIT_HOST_LOCAL | livekit.hostLocal | Localhost signaling override |
LIVEKIT_INTERNAL_HOST | livekit.internalHost | Internal API URL |
LIVEKIT_API_KEY | livekit.apiKey | LiveKit API key |
LIVEKIT_API_SECRET | livekit.apiSecret | LiveKit API secret |
LIVEKIT_CONFIG_PATH | livekit.configPath | External LiveKit YAML path |
LIVEKIT_NODE_IP | livekit.nodeIP | Explicit RTC node IP |
LIVEKIT_EXTERNAL | livekit.external | Use external LiveKit instead of embedded (true/false) |
LIVEKIT_SKIP_TLS_VERIFY | livekit.skipTLSVerify | Skip TLS verification for LiveKit client (true/false) |
JWT_SECRET | auth.jwtSecret | JWT signing secret |
AUTH_FRONTEND_URL | auth.frontendURL | Frontend URL |
AUTH_PASSKEY_CHALLENGE_TTL | auth.passkeyChallengeTTL | Passkey challenge minutes |
AUTH_REQUIRE_EMAIL_VERIFICATION | auth.requireEmailVerification | Gate on email verify |
AUTH_VERIFICATION_COOLDOWN_MINS | auth.verificationEmailCooldownMins | Resend cooldown |
AUTH_VERIFICATION_TOKEN_TTL_HOURS | auth.verificationTokenTTLHours | How long verification links remain valid (hours) |
AUTH_UNVERIFIED_ACCOUNT_TTL_HOURS | auth.unverifiedAccountTTLHours | Auto-delete unverified accounts after N hours (0 = disabled) |
AUTH_RESET_TOKEN_TTL_HOURS | auth.resetTokenTTLHours | Password reset token hours |
CHAT_MAX_UPLOAD_BYTES_PER_USER | chat.maxUploadBytesPerUser | Per-user upload quota |
CHAT_GLOBAL_DISK_THRESHOLD_BYTES | chat.globalDiskThresholdBytes | Global upload ceiling |
CHAT_MAX_MESSAGE_COUNT | chat.maxMessageCount | Client chat message cap |
CHAT_MESSAGE_TTL_HOURS | chat.messageTTLHours | Client chat TTL hours |
QUEUE_POLL_INTERVAL | queue.pollInterval | Job poll interval (ms) |
QUEUE_MAX_ATTEMPTS | queue.maxAttempts | Max job retries |
QUEUE_CONCURRENCY | queue.concurrency | Worker count |
EMAIL_SMTP_HOST | email.smtpHost | SMTP host |
EMAIL_SMTP_PORT | email.smtpPort | SMTP port |
EMAIL_USERNAME | email.username | SMTP user |
EMAIL_PASSWORD | email.password | SMTP password |
EMAIL_FROM_ADDRESS | email.fromAddress | From address |
EMAIL_FROM_NAME | email.fromName | From display name |
EMAIL_TLS_SKIP_VERIFY | email.tlsSkipVerify | Skip SMTP TLS verify |
EMAIL_SMTPS_MODE | email.smtpsMode | SMTPS mode |
CORS_ALLOWED_ORIGINS | cors.allowedOrigins | Allowed origins |
CORS_ALLOWED_HEADERS | cors.allowedHeaders | Allowed headers |
CORS_ALLOWED_METHODS | cors.allowedMethods | Allowed methods |
CORS_ALLOW_CREDENTIALS | cors.allowCredentials | Allow credentials |
CORS_EXPOSE_HEADERS | cors.exposeHeaders | Exposed headers |
CORS_MAX_AGE | cors.maxAge | Preflight max age |
RATELIMIT_AUTH_MAX | rateLimit.authMaxRequests | Auth rate limit max |
RATELIMIT_AUTH_WINDOW | rateLimit.authWindowSecs | Auth window seconds |
RATELIMIT_GUEST_MAX | rateLimit.guestMaxRequests | Guest rate limit max |
RATELIMIT_GUEST_WINDOW | rateLimit.guestWindowSecs | Guest window seconds |
RATELIMIT_AUTH_RESEND_MAX | rateLimit.authResendMaxRequests | Resend rate limit max |
RATELIMIT_AUTH_RESEND_WINDOW | rateLimit.authResendWindowSecs | Resend window seconds |
RATELIMIT_API_MAX | rateLimit.apiMaxRequests | API rate limit max |
RATELIMIT_API_WINDOW | rateLimit.apiWindowSecs | API window seconds |
RECORDING_RETENTION_HOURS | recording.retentionHours | Recording retention hours |
RECORDING_CLEANUP_INTERVAL_HOURS | recording.cleanupIntervalHours | Recording cleanup interval |
Config path resolution also honors BEDRUD_CONFIG / CONFIG_PATH (CLI).
Production Checklist
- Change
jwtSecretandsessionSecretto strong random values - Set
logger.leveltoinfoorwarn - Configure TLS (installer, ACME, or reverse proxy +
behindProxy) - Set
cors.allowedOriginsto 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.