CTFd
Overview
CTFd is a popular open-source Capture The Flag competition platform for running security training events, workshops and jeopardy-style competitions. It provides a challenge board, a live scoreboard, individual or team play, solve tracking, hints and a complete administration panel for authoring challenges, flags and pages.
This SecureAnyCloud image runs CTFd 3.8.6 in a dedicated Python virtual environment, served by gunicorn with gevent workers, bound to loopback and reverse-proxied behind nginx. MySQL 8.0 holds durable state and Redis provides caching. First-run setup is completed automatically, and a demo challenge is seeded so the board is a working event. The MySQL root, application database, Flask secret key and CTFd administrator passwords are all rotated per instance at first boot, so nothing ships with a known password.
What you get:
- CTFd 3.8.6 from the official release at
/opt/CTFd, served by gunicorn (gevent, 2 workers) - An nginx reverse proxy on port 80 with an unauthenticated static
/healthzfor load balancer probes - MySQL 8.0.x holding the CTFd database (
ctfd) with a per-VM application user - Redis for CTFd caching and sessions, bound to loopback
- A single
adminaccount created automatically at first boot, with a per-VM password - A seeded demo challenge, so the challenge board and scoreboard are populated on first boot
ctfd-firstboot.service, which rotates every secret, completes setup and seeds the demo challenge- Host-agnostic URLs: CTFd renders on the VM public IP, on 127.0.0.1, or on any domain you point at it
- CTFd, nginx, MySQL and Redis enabled and starting automatically on boot
- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key, and a VNet with a subnet.
Standard_B2s (2 vCPU, 4 GB RAM) is a comfortable starting size for a small event. Move up to a D2s or D4s size for larger competitions with many concurrent players.
On the network security group, open inbound ports 80 and 443, and port 22 to your management network. MySQL, Redis and the gunicorn app listen only on loopback and are never exposed.
Deploy and connect
Launch the image from the Azure Marketplace, then connect over SSH as azureuser:
ssh azureuser@<vm-ip>Within a few seconds of the instance starting, the first-boot service rotates the credentials, completes the CTFd setup wizard and seeds the demo challenge.
Verify the services
Confirm that CTFd, nginx, MySQL and Redis are active. CTFd, MySQL and Redis are bound to loopback only, and nginx is the single ingress on port 80:
sudo systemctl is-active ctfd.service nginx.service mysql.service redis-server.service
sudo ss -ltn | grep -E ':80|:8000|:6379|:3306'Check the versions
CTFd reports its version from the installed package. The runtime is gunicorn on MySQL and Redis:
sudo -u ctfd bash -c "cd /opt/CTFd && /opt/CTFd/venv/bin/python -c 'import CTFd; print(\"CTFd\", CTFd.__version__)'" /opt/CTFd/venv/bin/gunicorn --version mysql --version redis-server --version | awk '{print $1, $3}'Inspect the database
The schema is created and a single administrator account exists, stored with a hashed password, so there is no default or shared login. The seeded demo challenge is present too.
Read the application database password from the root-only credentials file, then inspect the tables, users and challenges:
DB_PASS=$(sudo grep '^CTFD_DB_PASSWORD=' /root/ctfd-credentials.txt | cut -d= -f2-) sudo mysql -uctfd -p"$DB_PASS" ctfd -e "SELECT COUNT(*) AS ctfd_tables FROM information_schema.tables WHERE table_schema='ctfd'"
sudo mysql -uctfd -p"$DB_PASS" ctfd -e "SELECT id,name,type,hidden FROM users"
sudo mysql -uctfd -p"$DB_PASS" ctfd -e "SELECT id,name,category,value,state FROM challenges"Read the administrator password
The per-VM credentials are written to a root-only file. Read it to get the CTFD_ADMIN_USER and CTFD_ADMIN_PASSWORD for signing in:
sudo cat /root/ctfd-credentials.txtKeep this file secure. Change the administrator password from within CTFd after your first sign-in. The Flask secret key lives in /etc/ctfd/ctfd.env.
Sign in
Confirm that CTFd is serving locally. Then browse to http://<vm-ip>/login and sign in as admin with the password from Step 5:
curl -s -o /dev/null -w 'home : HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'healthz : HTTP %{http_code}\n' http://127.0.0.1/healthzOpen the challenge board
After you sign in, you land on the challenge board. Each card is a challenge, and the seeded Welcome challenge is worth 100 points. Click a challenge to open it, read the prompt, and submit a flag in the flag{...} format. You author more challenges from the admin panel in Step 9.
View the scoreboard
The scoreboard ranks players (or teams) by score and shows a timeline of solves as the event runs. It is public by default so that spectators can follow along, and it updates live as flags are submitted.
Open the admin panel
Open the admin panel to run your event. You can create and edit challenges, add flags and hints, manage users and teams, control challenge and scoreboard visibility, set start and end times, and watch statistics. Use it to build out the competition beyond the seeded demo challenge.
Components
Component Path
CTFd install /opt/CTFd/
Python venv /opt/CTFd/venv/
Environment (secret key, DB URL) /etc/ctfd/ctfd.env (mode 0640 root:ctfd)
gunicorn unit /etc/systemd/system/ctfd.service
nginx vhost /etc/nginx/sites-available/sac-ctfd
Firstboot script /usr/local/sbin/ctfd-firstboot.sh
MySQL data /var/lib/mysql/
Credentials /root/ctfd-credentials.txt (mode 0600 root:root)
Security and next steps
- Change the administrator password from within CTFd (Admin, then Config, then your account) after your first sign-in, and add real accounts for your players.
- Enable HTTPS. Install certbot and request a certificate with
sudo apt-get install -y certbot python3-certbot-nginx, thensudo certbot --nginx. - Restrict the network security group so that ports 80 and 443 are open only to your players or load balancer, and port 22 only to your management network.
- Author your challenges in the admin panel. Set the event start and end times, and choose challenge, account and score visibility to match how you want to run the competition.
- Point your domain at the instance. CTFd derives its URLs from the request host, so no rewrite is needed.
- Patch the OS regularly with
sudo apt-get updateandsudo apt-get upgrade.
Licensing
CTFd is distributed under the Apache-2.0 licence and is free to use commercially. SecureAnyCloud packages and supports the image, and support is available 24/7 at sales@secureanycloud.com.
All product and company names are trademarks or registered trademarks of their respective holders. Their use does not imply affiliation or endorsement.
Deploy on Azure
Launch CTFd on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
CTFd is a popular open-source Capture The Flag competition platform for running security training events, workshops and jeopardy-style competitions. From one web interface you get a challenge board, a live scoreboard, individual or team play, solve tracking, hints and a complete administration panel for authoring challenges, flags and pages.
This SecureAnyCloud image runs CTFd as a Python and Flask application served by the gunicorn WSGI server with gevent workers, bound to the loopback interface and reverse-proxied behind nginx on port 80. It completes CTFd's first-run setup for you, so you land on a populated challenge board instead of an open setup screen that a visitor could hijack.
What you get:
- A MySQL 8.0 database for durable state, and Redis for caching and sessions. Both are bound to loopback, so only nginx on port 80 is reachable from the network.
- A static, unauthenticated health endpoint for load balancer probes
- The first-run setup wizard, which CTFd normally asks you to complete by hand, completed automatically on the first boot of every instance
- An administrator account, Flask secret key, MySQL password and application database password generated freshly on each instance, so two instances launched from the same Amazon Machine Image never share a secret
- The administrator credentials written to
/root/ctfd-credentials.txtwith mode 0600, readable only by root
Prerequisites
Before you deploy this image, make sure you have:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network and port 80 for the web interface
- The AWS CLI (version 2) installed locally if you plan to deploy from the command line
Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for CTFd. Select the SecureAnyCloud listing, choose Select, then choose Continue on the subscription summary.
Pick an instance type of m5.large or larger. Choose your EC2 key pair under Key pair (login). Under Network settings, select your VPC and subnet, then create or select a security group that opens port 22 from your management network and port 80 for the web interface. Leave the root volume at the default size or larger.
Select Launch instance. After the instance state becomes Running and the status checks pass, first-boot initialisation takes a short time. It generates the per-instance secrets, completes the setup wizard and seeds a demo challenge.
Launch the Instance from the AWS CLI
The command below launches an instance from the SecureAnyCloud CTFd Marketplace AMI into an existing subnet and security group. Replace the values as follows:
- Replace <ami-id> with the AMI ID shown on the Marketplace listing.
- Replace <key-name> with your EC2 key pair name.
- Replace <subnet-id> with your subnet ID.
- Replace <security-group-id> with a security group that opens port 22 from <your-mgmt-cidr> and port 80 for the web interface.
# launch into a subnet whose security group allows SSH from <your-mgmt-cidr> and HTTP from your users aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ctfd}]'When the instance reaches the Running state and its status checks pass, find its public IP address or DNS name in the EC2 console or with aws ec2 describe-instances.
Connect to Your Instance
Connect over SSH with your key pair. On Ubuntu 24.04, the login user is ubuntu:
ssh -i <key-name>.pem ubuntu@<public-ip>Retrieve the Administrator Password
The administrator password is unique to your instance and was generated on first boot. Read it as root:
sudo cat /root/ctfd-credentials.txtThe file lists the CTFd URL, the administrator login name (admin) and the generated password. It also lists the application database name, its user and password, and the MySQL root password. Keep this file somewhere safe.
Sign In to CTFd
nginx serves the web interface on port 80. In a browser, open the sign-in page:
http://<instance-public-ip>/loginSign in with the login name admin and the password from the credentials file.
CTFd derives its base URL from the address you reach it on. It answers correctly whether you browse by public IP, private IP or a custom domain name, and there is no build-time address to change.
Confirm CTFd Is Running
Over SSH, confirm that the application, web server, database and cache are active:
sudo systemctl is-active ctfd nginx mysql redis-serverAll four services should report active. The unauthenticated health endpoint returns 200:
curl -s -o /dev/null -w 'healthz: %{http_code}\n' http://127.0.0.1/healthzThe home page renders and the setup wizard is closed, so a visitor can never re-run setup. The 302 is the expected redirect away from /setup:
curl -s -o /dev/null -w 'home: %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'setup: %{http_code}\n' http://127.0.0.1/setupCTFd's gunicorn workers, the MySQL database and Redis all listen on the loopback interface only and are never exposed directly. Only nginx on port 80 is reachable from the network:
sudo ss -tlnH | awk '{print $4}' | sort -u | grep -E '127.0.0.1:(8000|3306|6379)'Author Challenges and Flags
Open Admin Panel from the top navigation, then choose Challenges to author your event. The image ships with one demo challenge, Welcome, in the Getting Started category, so the board is a working event from the first boot.
To create a challenge, select + and give it a name, category, description and point value. Choose the challenge type (standard or dynamic scoring), then add one or more flags (static or regular expression) that players submit to score.
You can attach files, add hints, and set unlock requirements so that challenges reveal in sequence. Set a challenge's state to visible to publish it to players.
Run the Event
Players browse the challenge board, submit flags and climb the live scoreboard. The board groups challenges by category and shows each challenge's point value.
Use the Admin Panel to control the event:
- Config sets the CTF name, the start and end times, and whether the event is in users or teams mode.
- Users and Scoreboard manage participants and standings.
- Submissions and Statistics track solves and progression.
In teams mode, players form or join teams and scores aggregate per team.
Enable HTTPS
nginx serves the web interface over plain HTTP on port 80. For production use, place it behind TLS so that the administrator password and player submissions are encrypted in transit.
Point a domain name such as <your-domain> at the instance. Then obtain a certificate for that domain, either as a managed certificate on an Application Load Balancer in front of the instance or with Certbot on the instance, and configure nginx to listen on 443 with your certificate.
CTFd derives its base URL from the request and honours the X-Forwarded-Proto header that nginx sets, so no application change is needed when you move to HTTPS. Once a certificate is in front of the instance and you browse to the https:// address, CTFd generates its links over HTTPS automatically.
Restrict the security group so that ports 80 and 443 are reachable only from the networks that need the web interface.
Backup and Maintenance
To back up the application, snapshot the instance's EBS root volume. The snapshot captures the MySQL database, the CTFd application tree and any uploaded challenge files.
You can also take a logical backup of the database over the loopback interface, using the MySQL root password from the credentials file and writing to your chosen backup directory <backup-dir>:
DBPASS=$(sudo awk -F= '/^MYSQL_ROOT_PASSWORD=/{print $2}' /root/ctfd-credentials.txt)
sudo mysqldump -uroot -p"$DBPASS" ctfd > <backup-dir>/ctfd-backup.sqlApply operating system security updates with sudo apt-get update && sudo apt-get upgrade, and reboot when a new kernel is installed. CTFd, nginx, MySQL and Redis start automatically on boot.
Support
SecureAnyCloud publishes and supports this image. Support covers deployment, challenge and event configuration, team and user management, theme and plugin questions, version upgrades, performance tuning, the MySQL database, TLS and backups. Contact SecureAnyCloud through the support channel listed on the AWS Marketplace listing.
Deploy on AWS
Launch CTFd on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com