Configuration

Complete reference for all rCTF configuration options including file-based config and environment variables.

Edit this page View Markdown

rCTF is configured through YAML or JSON files in a rctf.d/ directory and optional environment variable overrides.

Configuration loading

By default, rCTF searches upward from packages/config/ for a directory named rctf.d/. Set RCTF_CONF_PATH to use another directory.

rCTF loads .yaml, .yml, and .json files alphabetically, merging each file over the previous ones. This lets you separate base settings, providers, and deployment-specific overrides:

  • 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

Variable Type Description
RCTF_NAME string CTF display name
RCTF_ORIGIN string CTF origin URL (e.g., https://ctf.example.com)
RCTF_TOKEN_KEY string Base64-encoded 32-byte key for token encryption
RCTF_INSTANCE_TYPE string all, frontend, or leaderboard
RCTF_SHUTDOWN_TIMEOUT integer Graceful-shutdown cap in milliseconds before force-exit. 0 disables the cap.
RCTF_IDLE_TIMEOUT integer Idle connection timeout in seconds (0-255)
RCTF_MAX_REQUEST_BODY_SIZE integer Maximum accepted request body size in bytes
RCTF_UPLOAD_PROVIDER string uploads/local, uploads/s3, or uploads/gcs. See Uploads for each provider’s variables.
RCTF_CONF_PATH string Path to config directory (overrides search)

Database

Variable Type Description
RCTF_DATABASE_URL string PostgreSQL connection string
RCTF_DATABASE_HOST string PostgreSQL host (if not using URL)
RCTF_DATABASE_PORT integer PostgreSQL port
RCTF_DATABASE_USERNAME string PostgreSQL user
RCTF_DATABASE_PASSWORD string PostgreSQL password
RCTF_DATABASE_DATABASE string PostgreSQL database name
RCTF_DATABASE_MIGRATE string before, only, or never
RCTF_REDIS_URL string Redis connection string
RCTF_REDIS_HOST string Redis host (if not using URL)
RCTF_REDIS_PORT integer Redis port
RCTF_REDIS_PASSWORD string Redis password
RCTF_REDIS_DATABASE integer Redis database number

Timing and auth

Variable Type Description
RCTF_START_TIME integer Competition start time (Unix milliseconds)
RCTF_END_TIME integer Competition end time (Unix milliseconds)
RCTF_LOGIN_TIMEOUT integer Verification token expiry in milliseconds
RCTF_USER_MEMBERS boolean Enable team members feature
RCTF_CTFTIME_CLIENT_ID string CTFtime OAuth client ID
RCTF_CTFTIME_CLIENT_SECRET string CTFtime OAuth client secret

UI and meta

Variable Type Description
RCTF_HOME_CONTENT string Home page markdown content
RCTF_FAVICON_URL string Favicon URL
RCTF_META_DESCRIPTION string Meta description
RCTF_IMAGE_URL string Meta image URL
RCTF_GLOBAL_SITE_TAG string Google Analytics tag. Deprecated and auto-converted to analytics.provider at startup. See Upgrading from v1.

Email

Variable Type Description
RCTF_EMAIL_FROM string Email sender address
RCTF_EMAIL_LOGO_URL string Logo URL in email templates

Leaderboard

Variable Type Description
RCTF_LEADERBOARD_MAX_LIMIT integer Max teams per leaderboard page
RCTF_LEADERBOARD_MAX_OFFSET integer Max leaderboard offset
RCTF_LEADERBOARD_UPDATE_INTERVAL integer Leaderboard recalc interval (ms)
RCTF_LEADERBOARD_GRAPH_MAX_TEAMS integer Max teams on score graph
RCTF_LEADERBOARD_GRAPH_SAMPLE_TIME integer Graph 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
shutdownTimeout: 30000 # Graceful-shutdown cap in ms
idleTimeout: 65 # Idle connection timeout in seconds
maxRequestBodySize: 1073741824 # Maximum request body size in bytes
Field Type Default Description
ctfName string - Display name of your CTF
origin string - Public URL of your CTF (no trailing slash)
tokenKey string - Base64-encoded 32-byte key for AES-GCM token encryption
instanceType string all Controls which components run: all (API + leaderboard worker), frontend (API only), leaderboard (worker only). See Scaling before splitting roles.
shutdownTimeout number 30000 (30s) Time in milliseconds to drain in-flight requests and stop the leaderboard worker on SIGTERM/SIGINT before the process force-exits. 0 disables the force-exit cap. See Scaling.
idleTimeout number 65 How long in seconds an idle connection stays open before the server closes it. Set it higher than your reverse proxy’s keep-alive timeout. Bun limits this value to 255, and rCTF rejects larger values at startup. Set it to 0 to disable the timeout.
maxRequestBodySize number 1073741824 (1 GiB) Maximum request body size accepted by the Bun API server. This is the effective backend cap for upload routes when the bundled nginx config sets client_max_body_size to 0 for streaming uploads.
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
Field Type Default Description
database.sql string | object - PostgreSQL connection (required)
database.redis string | object - Redis connection (required)
database.migrate string never before 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
Field Type Default Description
startTime number - Competition start time in Unix milliseconds (required)
endTime number - 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: example.edu
divisions: [student, open]
- match: any
value: ''
divisions: [open]
Field Type Default Description
divisions object { open: "Open" } Map of division ID to display name
defaultDivision string - Default division for new users (optional)
divisionACLs array - 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:

Field Description
match Match type: domain, email, regex, or any
value Value to match. For a domain rule, use the full domain after @, such as example.edu. Email rules take the full address, regex rules take a pattern, and any uses an empty value.
divisions Array of division IDs this rule grants access to

A user receives the divisions from every matching rule, regardless of order. A domain rule matches the full domain exactly. For example, example.edu matches alice@example.edu but not alice@sub.example.edu.

Warning (Email provider required, disable CTFtime auth)

Division ACLs require an email provider. Disable CTFtime authentication when using them because CTFtime sign-ins bypass the ACLs and can select any division.

Authentication

registrationsEnabled: true
userMembers: true
maxMembers: 50
loginTimeout: 3600000
ctftime:
clientId: '12345'
clientSecret: your-secret
Field Type Default Description
registrationsEnabled boolean true Whether new registrations are allowed
userMembers boolean true Enable team members feature
maxMembers number 50 Maximum members per team
loginTimeout number 3600000 Verification/CTFtime token expiry in milliseconds (1 hour)
ctftime.clientId string - CTFtime OAuth client ID (numeric string)
ctftime.clientSecret string - CTFtime OAuth client secret
Note

Auth tokens (used for logging in) never expire. Only verification and CTFtime tokens expire according to loginTimeout.

Providers

Providers are pluggable integrations, each selected by name with provider-specific options. Names follow a <category>/<provider> scheme where the category is a plural or mass noun. The Providers section documents every provider in detail.

Uploads

uploadProvider:
name: uploads/s3
options:
bucketName: my-bucket
awsKeyId: AKIAIOSFODNN7EXAMPLE
awsKeySecret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
awsRegion: us-east-1
Field Type Default Description
uploadProvider object { name: "uploads/local" } File upload provider

See Upload Providers for provider-specific options.

Scoring

scoreProvider:
name: scores/classic
Field Type Default Description
scoreProvider object { name: "scores/classic" } Scoring algorithm provider

See Scoring Providers for provider-specific options.

Captcha

captcha:
provider:
name: captcha/turnstile
options:
siteKey: your-site-key
secretKey: your-secret-key
protectedEndpoints:
- register
- recover
Field Type Default Description
captcha.provider object - Captcha provider config (name + options)
captcha.protectedEndpoints array - List of actions requiring captcha

Available captcha actions: register, recover, setEmail, instancerStart, instancerExtend, avatarUpload, adminBotSubmit.

See Captcha Providers for provider-specific options.

Instancers

instancers:
docker:
name: instancers/docker
options:
apiUrl: http://localhost:8000
authToken: secret
k8s:
name: instancers/k8s
options:
apiUrl: https://k8s.example.com
authToken: <service-account-token>
k8s-arm:
name: instancers/k8s
options:
apiUrl: https://k8s-arm.example.com
authToken: <service-account-token>
defaultInstancer: k8s
Field Type Default Description
instancers object - Map of named challenge instancer providers (optional). Each key is an instancer name a challenge can target.
defaultInstancer string - Name of the instancer used when a challenge doesn’t pick one. Required when more than one instancer is defined. When there is only one, rCTF selects it automatically.

See Instancer for the available instancers and their options.

Admin bot

adminBot:
provider:
name: admin-bots/rctf-ts
options: {}
maxLogsPerUserChallenge: 5
Field Type Default Description
adminBot.provider object - Admin bot provider config
adminBot.maxLogsPerUserChallenge integer 5 Max 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/email-logo.png
Field Type Default Description
email.provider object - Email provider config
email.from string - Sender email address (required if email enabled)
email.logoUrl string - Logo URL for email templates. Falls back to the top-level light and dark logos when unset.

See Email Providers for provider-specific options.

UI

homeContent: |
Welcome to My CTF!
**Markdown** is supported.
sponsors:
- name: Sponsor Name
iconLight: https://example.com/sponsor-light.png
iconDark: https://example.com/sponsor-dark.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]+}'
Field Type Default Description
homeContent string "Home content. Markdown supported." Home page content (Markdown)
sponsors array [] List of sponsors (name, iconLight, iconDark, description, url). When only one mode’s icon is set, the home page renders it color-inverted in the other mode; legacy icon is accepted as the light-mode icon
meta.description string "rCTF event description" HTML meta description
meta.imageUrl string "" HTML meta image URL
faviconUrl string rCTF default Favicon URL
logoLightUrl string "" Logo for light mode
logoDarkUrl string "" Logo for dark mode
flagFormatPlaceholder string "flag{[\\x20-\\x7e]+}" Flag format hint shown to participants
Tip

The admin settings API can update ctfName, event timing, homepage content, sponsors, metadata, favicon, and logos without restarting rCTF.

Analytics

analytics:
provider:
name: analytics/google
options:
siteTag: G-XXXXXXXXX
Field Type Default Description
analytics.provider object - Analytics provider (analytics/google, analytics/cloudflare)

Limits

maxAvatarSize: 1048576 # 1 MB
leaderboard:
maxLimit: 100
maxOffset: 4294967296
updateInterval: 30000
graphMaxTeams: 10
graphSampleTime: 300000
graphWithListLimit: 100
Field Type Default Description
maxAvatarSize number 1048576 (1 MB) Maximum avatar upload size in bytes
leaderboard.maxLimit number 100 Max teams returned per leaderboard request
leaderboard.maxOffset number 4294967296 Max pagination offset
leaderboard.updateInterval number 30000 (30s) Leaderboard recalculation interval in ms
leaderboard.graphMaxTeams number 10 Max teams displayed on score graph
leaderboard.graphSampleTime number 300000 (5min) Time between graph data points in ms
leaderboard.graphWithListLimit number 100 Max teams for combined leaderboard + graph endpoint

Moderation

avatarsModeration:
provider:
name: moderation/openai
options:
apiKey: sk-...
allowOnInternalError: true
Field Type Default Description
avatarsModeration.provider object - Moderation provider for avatar uploads
avatarsModeration.allowOnInternalError boolean true Allow avatar upload if moderation API fails

See Moderation Providers for details.

Proxy

proxy:
cloudflare: true
trust: 2
Field Type Default Description
proxy.cloudflare boolean false Trust Cloudflare CF-Connecting-IP header for client IP
proxy.trust boolean | string | string[] | number 2 Proxy trust setting for X-Forwarded-For. true trusts all, a number trusts exactly that many proxy hops in front of the API, a string or array specifies trusted CIDR ranges or the named subnets loopback, linklocal, uniquelocal

The bundled nginx inside the container counts as the first hop, so the default of 2 matches the standard deployment: the bundled nginx plus one reverse proxy on the host. With it, logs and rate limits use the participant’s IP as reported by the host proxy.

Warning

A hop count must match your topology exactly. Setting it too high lets participants spoof X-Forwarded-For to evade per-IP rate limits, while setting it too low attributes all traffic to one of your proxies. Increase it if you add another layer (such as a load balancer), set it to 1 if the container is exposed directly, and behind Cloudflare set proxy.cloudflare to true instead.

Blood bot

bloodBot:
bloodsCount: 3
destinations:
- provider:
name: messages/discord
options:
url: https://discord.com/api/webhooks/...
bloodEmojis:
- '<:rank1:1008801500736266261>'
- '<:rank2:1008801501776449738>'
- '<:rank3:1008801503080874056>'
messageTemplate: '{{bloodEmoji}} {{bloodNumTitle}} solve for challenge "**{{challengeName}}**" goes to **{{teamName}}**!'
- provider:
name: messages/telegram
options:
botToken: '123456:ABC-DEF...'
chatId: '-1001234567890'
Field Type Default Description
bloodBot.bloodsCount number 1 Number of blood tiers to announce (1-3)
bloodBot.destinations array - At least one destination (required)
bloodBot.destinations[].provider object - Messages provider config
bloodBot.destinations[].bloodEmojis string[] [] Optional blood emoji for this destination
bloodBot.destinations[].messageTemplate string - Custom message template (optional)

Available template variables: {{teamName}}, {{teamUrl}}, {{bloodEmoji}}, {{bloodNumber}}, {{bloodNumOrdinal}}, {{bloodNumSentence}}, {{bloodNumWord}}, {{bloodNumTitle}}, {{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
- recover
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
bloodEmojis:
- '<:rank1:1008801500736266261>'
- '<:rank2:1008801501776449738>'
- '<:rank3:1008801503080874056>'
messageTemplate: '{{bloodEmoji}} {{bloodNumTitle}} solve for challenge "**{{challengeName}}**" goes to **{{teamName}}**!'

Type to search.