Configuration
Complete reference for all rCTF configuration options including file-based config and environment variables.
rCTF is configured through YAML or JSON files in a rctf.d/ directory and optional environment variable overrides.
Configuration loading#
Configuration is loaded from a directory named rctf.d/. The loader searches upward from the packages/config/ directory, or you can specify a path via the RCTF_CONF_PATH environment variable.
All files in the directory (.yaml, .yml, or .json) are loaded alphabetically and deep-merged in order, so you can split configuration across multiple files for organization:
rctf.d/
- 01-base.yaml Core settings
- 02-providers.yaml Provider configuration
- 03-overrides.yaml Environment-specific overrides
Environment variables are applied last, overriding any file-based values.
Environment variables#
The following environment variables are supported. They override values from config files.
Core#
Database#
Timing and auth#
UI and meta#
Email#
Leaderboard#
Note
Boolean environment variables accept true, yes, y, or 1 as truthy values. Anything else is treated as false.
Configuration reference#
Below is the complete reference of all configuration options available in rctf.d/ files.
Core settings#
ctfName: My CTF # CTF display name (required)origin: https://ctf.example.com # Public origin URL (required)tokenKey: <base64-32-byte-key> # Token encryption key (required)instanceType: all # all | frontend | leaderboardWarning
The tokenKey is critical for security. All authentication tokens are encrypted with this key. If it is lost or changed, all existing tokens become invalid and users will need to re-authenticate.
Database#
database: sql: postgres://user:password@localhost:5432/rctf redis: redis://localhost:6379 migrate: neverThe sql field accepts either a connection string or an object:
database: sql: host: localhost port: 5432 user: rctf password: secret database: rctf maxPoolSize: 100 # Default: 100 idleTimeout: 30000 # Default: 30000 (ms) connectTimeout: 3000 # Default: 3000 (ms)The redis field accepts either a connection string or an object:
database: redis: host: localhost port: 6379 password: secret # Optional database: 0 # Optional, default 0Timing#
startTime: 1735689600000 # January 1, 2025 00:00 UTCendTime: 1735776000000 # January 2, 2025 00:00 UTCTip
To convert a date to Unix milliseconds: date -d "2025-01-01T00:00:00Z" +%s000 or use new Date('2025-01-01T00:00:00Z').getTime() in JavaScript.
Divisions#
divisions: open: Open student: StudentdefaultDivision: opendivisionACLs: - match: domain value: edu divisions: [student, open] - match: any value: '' divisions: [open]Division ACLs#
ACLs control which divisions a user can register for based on their email. Each ACL entry has:
ACL rules are evaluated in order. The first matching rule determines the user’s allowed divisions.
Warning (Email provider required, disable CTFtime auth)
Division ACLs match on email, so an email provider must be configured for them to apply. CTFtime authentication should also be disabled on instances that use ACLs, since CTFtime sign-ins bypass ACL evaluation entirely and grant access to every division.
Authentication#
registrationsEnabled: trueuserMembers: truemaxMembers: 50loginTimeout: 3600000ctftime: clientId: '12345' clientSecret: your-secretNote
Auth tokens (used for logging in) never expire. Only verification and CTFtime tokens expire according to loginTimeout.
Captcha#
captcha: provider: name: captcha/turnstile options: siteKey: your-site-key secretKey: your-secret-key protectedEndpoints: - register - recoverAvailable captcha actions: register, recover, setEmail, instancerStart, instancerExtend, avatarUpload, adminBotSubmit.
See Captcha Providers for provider-specific configuration.
Providers#
uploadProvider: name: uploads/s3 options: bucketName: my-bucket awsKeyId: AKIAIOSFODNN7EXAMPLE awsKeySecret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY awsRegion: us-east-1scoreProvider: name: scores/classicinstancerProvider: name: instancer/docker-instancer options: apiUrl: http://localhost:8000 authToken: secretSee the Providers section for detailed configuration of each provider.
Admin bot#
adminBot: provider: name: admin-bot/rctf-js options: {} maxLogsPerUserChallenge: 5Email#
email: provider: name: emails/smtp options: smtpUrl: smtp://user:pass@mail.example.com:587 from: noreply@example.com logoUrl: https://example.com/logo.pngSee Email Providers for provider-specific options.
UI#
homeContent: | Welcome to My CTF!
**Markdown** is supported.sponsors: - name: Sponsor Name icon: https://example.com/sponsor.png description: Sponsor description url: https://sponsor.commeta: description: A cool CTF competition imageUrl: https://example.com/banner.pngfaviconUrl: https://example.com/favicon.icologoLightUrl: https://example.com/logo-light.svglogoDarkUrl: https://example.com/logo-dark.svgflagFormatPlaceholder: 'flag{[\x20-\x7e]+}'Tip
Most UI settings and client config timing values (ctfName, startTime, endTime, homeContent, sponsors, meta, faviconUrl, logoLightUrl, logoDarkUrl) can also be changed at runtime through the admin settings API without restarting the server.
Analytics#
analytics: provider: name: analytics/google options: siteTag: G-XXXXXXXXXLimits#
maxAvatarSize: 1048576 # 1 MBleaderboard: maxLimit: 100 maxOffset: 4294967296 updateInterval: 30000 graphMaxTeams: 10 graphSampleTime: 1800000 graphWithListLimit: 100Moderation#
avatarsModeration: provider: name: moderation/openai options: apiKey: sk-... allowOnInternalError: trueSee Moderation Providers for details.
Proxy#
proxy: cloudflare: true trust: falseWarning
Set proxy.cloudflare to true if your rCTF instance is behind Cloudflare. This ensures correct client IP extraction for rate limiting and logging. When using a different reverse proxy, configure proxy.trust instead.
Blood bot#
bloodBot: bloodsCount: 3 destinations: - provider: name: messages/discord options: url: https://discord.com/api/webhooks/... - provider: name: messages/telegram options: botToken: '123456:ABC-DEF...' chatId: '-1001234567890'Available template variables: {{teamName}}, {{teamUrl}}, {{bloodNumSentence}}, {{challengeCategory}}, {{challengeName}}.
See Blood Bot for more details.
Complete example#
ctfName: Example CTF 2025origin: https://ctf.example.comtokenKey: dGhpcyBpcyBhIGJhc2U2NCBrZXkgZXhhbXBsZSE=
database: sql: postgres://rctf:password@localhost:5432/rctf redis: redis://localhost:6379 migrate: before
startTime: 1735689600000endTime: 1735776000000
divisions: open: Open student: StudentdefaultDivision: opendivisionACLs: - match: domain value: edu divisions: [student, open] - match: any value: '' divisions: [open]
captcha: provider: name: captcha/turnstile options: siteKey: 0x4AAAAAAA... secretKey: 0x4AAAAAAA... protectedEndpoints: - register
email: provider: name: emails/smtp options: smtpUrl: smtp://user:pass@mail.example.com:587 from: noreply@example.com
uploadProvider: name: uploads/s3 options: bucketName: ctf-uploads awsKeyId: AKIAIOSFODNN7EXAMPLE awsKeySecret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY awsRegion: us-east-1
proxy: cloudflare: true
bloodBot: bloodsCount: 3 destinations: - provider: name: messages/discord options: url: https://discord.com/api/webhooks/123/abc