rCTF Docs
Overview

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#

VariableTypeDescription
RCTF_NAMEstringCTF display name
RCTF_ORIGINstringCTF origin URL (e.g., https://ctf.example.com)
RCTF_TOKEN_KEYstringBase64-encoded 32-byte key for token encryption
RCTF_INSTANCE_TYPEstringall, frontend, or leaderboard
RCTF_CONF_PATHstringPath to config directory (overrides search)

Database#

VariableTypeDescription
RCTF_DATABASE_URLstringPostgreSQL connection string
RCTF_DATABASE_HOSTstringPostgreSQL host (if not using URL)
RCTF_DATABASE_PORTintegerPostgreSQL port
RCTF_DATABASE_USERNAMEstringPostgreSQL user
RCTF_DATABASE_PASSWORDstringPostgreSQL password
RCTF_DATABASE_DATABASEstringPostgreSQL database name
RCTF_DATABASE_MIGRATEstringbefore, only, or never
RCTF_REDIS_URLstringRedis connection string
RCTF_REDIS_HOSTstringRedis host (if not using URL)
RCTF_REDIS_PORTintegerRedis port
RCTF_REDIS_PASSWORDstringRedis password
RCTF_REDIS_DATABASEintegerRedis database number

Timing and auth#

VariableTypeDescription
RCTF_START_TIMEintegerCompetition start time (Unix milliseconds)
RCTF_END_TIMEintegerCompetition end time (Unix milliseconds)
RCTF_LOGIN_TIMEOUTintegerVerification token expiry in milliseconds
RCTF_USER_MEMBERSbooleanEnable team members feature
RCTF_CTFTIME_CLIENT_IDstringCTFtime OAuth client ID
RCTF_CTFTIME_CLIENT_SECRETstringCTFtime OAuth client secret

UI and meta#

VariableTypeDescription
RCTF_HOME_CONTENTstringHome page markdown content
RCTF_FAVICON_URLstringFavicon URL
RCTF_META_DESCRIPTIONstringMeta description
RCTF_IMAGE_URLstringMeta image URL
RCTF_GLOBAL_SITE_TAGstringGoogle Analytics tag. Deprecated and auto-converted to analytics.provider at startup. See Upgrading from v1.

Email#

VariableTypeDescription
RCTF_EMAIL_FROMstringEmail sender address
RCTF_EMAIL_LOGO_URLstringLogo URL in email templates

Leaderboard#

VariableTypeDescription
RCTF_LEADERBOARD_MAX_LIMITintegerMax teams per leaderboard page
RCTF_LEADERBOARD_MAX_OFFSETintegerMax leaderboard offset
RCTF_LEADERBOARD_UPDATE_INTERVALintegerLeaderboard recalc interval (ms)
RCTF_LEADERBOARD_GRAPH_MAX_TEAMSintegerMax teams on score graph
RCTF_LEADERBOARD_GRAPH_SAMPLE_TIMEintegerGraph sample interval (ms)
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 | leaderboard
FieldTypeDefaultDescription
ctfNamestring-Display name of your CTF
originstring-Public URL of your CTF (no trailing slash)
tokenKeystring-Base64-encoded 32-byte key for AES-GCM token encryption
instanceTypestringallControls which components run: all (API + leaderboard worker), frontend (API only), leaderboard (worker only). See Scaling before splitting roles.
Warning

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: never

The 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 0
FieldTypeDefaultDescription
database.sqlstring | object-PostgreSQL connection (required)
database.redisstring | object-Redis connection (required)
database.migratestringneverbefore runs migrations on startup, only runs migrations and exits, never skips

Timing#

startTime: 1735689600000 # January 1, 2025 00:00 UTC
endTime: 1735776000000 # January 2, 2025 00:00 UTC
FieldTypeDefaultDescription
startTimenumber-Competition start time in Unix milliseconds (required)
endTimenumber-Competition end time in Unix milliseconds (required)
Tip

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: Student
defaultDivision: open
divisionACLs:
- match: domain
value: edu
divisions: [student, open]
- match: any
value: ''
divisions: [open]
FieldTypeDefaultDescription
divisionsobject{ open: "Open" }Map of division ID to display name
defaultDivisionstring-Default division for new users (optional)
divisionACLsarray-Access control rules for divisions (optional)

Division ACLs#

ACLs control which divisions a user can register for based on their email. Each ACL entry has:

FieldDescription
matchMatch type: domain, email, regex, or any
valueValue to match against (domain suffix, exact email, regex pattern, or empty for any)
divisionsArray of division IDs this rule grants access to

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: true
userMembers: true
maxMembers: 50
loginTimeout: 3600000
ctftime:
clientId: '12345'
clientSecret: your-secret
FieldTypeDefaultDescription
registrationsEnabledbooleantrueWhether new registrations are allowed
userMembersbooleantrueEnable team members feature
maxMembersnumber50Maximum members per team
loginTimeoutnumber3600000Verification/CTFtime token expiry in milliseconds (1 hour)
ctftime.clientIdstring-CTFtime OAuth client ID (numeric string)
ctftime.clientSecretstring-CTFtime OAuth client secret
Note

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
- recover
FieldTypeDefaultDescription
captcha.providerobject-Captcha provider config (name + options)
captcha.protectedEndpointsarray-List of actions requiring captcha

Available 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-1
scoreProvider:
name: scores/classic
instancerProvider:
name: instancer/docker-instancer
options:
apiUrl: http://localhost:8000
authToken: secret
FieldTypeDefaultDescription
uploadProviderobject{ name: "uploads/local" }File upload provider
scoreProviderobject{ name: "scores/classic" }Scoring algorithm provider
instancerProviderobject-Challenge instancer provider (optional)

See the Providers section for detailed configuration of each provider.

Admin bot#

adminBot:
provider:
name: admin-bot/rctf-js
options: {}
maxLogsPerUserChallenge: 5
FieldTypeDefaultDescription
adminBot.providerobject-Admin bot provider config
adminBot.maxLogsPerUserChallengeinteger5Max stored log entries per user per challenge

Email#

email:
provider:
name: emails/smtp
options:
smtpUrl: smtp://user:pass@mail.example.com:587
from: noreply@example.com
logoUrl: https://example.com/logo.png
FieldTypeDefaultDescription
email.providerobject-Email provider config
email.fromstring-Sender email address (required if email enabled)
email.logoUrlstring-Logo URL for email templates (optional)

See 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.com
meta:
description: A cool CTF competition
imageUrl: https://example.com/banner.png
faviconUrl: https://example.com/favicon.ico
logoLightUrl: https://example.com/logo-light.svg
logoDarkUrl: https://example.com/logo-dark.svg
flagFormatPlaceholder: 'flag{[\x20-\x7e]+}'
FieldTypeDefaultDescription
homeContentstring"Home content. Markdown supported."Home page content (Markdown)
sponsorsarray[]List of sponsors (name, icon, description, url)
meta.descriptionstring"rCTF event description"HTML meta description
meta.imageUrlstring""HTML meta image URL
faviconUrlstringrCTF defaultFavicon URL
logoLightUrlstring""Logo for light mode
logoDarkUrlstring""Logo for dark mode
flagFormatPlaceholderstring"flag{[\\x20-\\x7e]+}"Flag format hint shown to participants
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-XXXXXXXXX
FieldTypeDefaultDescription
analytics.providerobject-Analytics provider (analytics/google, analytics/cloudflare)

Limits#

maxAvatarSize: 1048576 # 1 MB
leaderboard:
maxLimit: 100
maxOffset: 4294967296
updateInterval: 30000
graphMaxTeams: 10
graphSampleTime: 1800000
graphWithListLimit: 100
FieldTypeDefaultDescription
maxAvatarSizenumber1048576 (1 MB)Maximum avatar upload size in bytes
leaderboard.maxLimitnumber100Max teams returned per leaderboard request
leaderboard.maxOffsetnumber4294967296Max pagination offset
leaderboard.updateIntervalnumber30000 (30s)Leaderboard recalculation interval in ms
leaderboard.graphMaxTeamsnumber10Max teams displayed on score graph
leaderboard.graphSampleTimenumber1800000 (30min)Time between graph data points in ms
leaderboard.graphWithListLimitnumber100Max teams for combined leaderboard + graph endpoint

Moderation#

avatarsModeration:
provider:
name: moderation/openai
options:
apiKey: sk-...
allowOnInternalError: true
FieldTypeDefaultDescription
avatarsModeration.providerobject-Moderation provider for avatar uploads
avatarsModeration.allowOnInternalErrorbooleantrueAllow avatar upload if moderation API fails

See Moderation Providers for details.

Proxy#

proxy:
cloudflare: true
trust: false
FieldTypeDefaultDescription
proxy.cloudflarebooleanfalseTrust Cloudflare CF-Connecting-IP header for client IP
proxy.trustboolean | string | string[] | numberfalseProxy trust setting for X-Forwarded-For. true trusts all, a number trusts the first N hops, a string or array specifies trusted CIDR ranges
Warning

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'
FieldTypeDefaultDescription
bloodBot.bloodsCountnumber1Number of blood tiers to announce (1-3)
bloodBot.destinationsarray-At least one destination (required)
bloodBot.destinations[].providerobject-Messages provider config
bloodBot.destinations[].messageTemplatestring-Custom message template (optional)

Available template variables: {{teamName}}, {{teamUrl}}, {{bloodNumSentence}}, {{challengeCategory}}, {{challengeName}}.

See Blood Bot for more details.

Complete example#

rctf.d/01-base.yaml
ctfName: Example CTF 2025
origin: https://ctf.example.com
tokenKey: dGhpcyBpcyBhIGJhc2U2NCBrZXkgZXhhbXBsZSE=
database:
sql: postgres://rctf:password@localhost:5432/rctf
redis: redis://localhost:6379
migrate: before
startTime: 1735689600000
endTime: 1735776000000
divisions:
open: Open
student: Student
defaultDivision: open
divisionACLs:
- 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
Esc

Start typing to search the docs.