This document is the comprehensive operating guide for deploying the CodeX Backend on Amazon Web Services (AWS EC2), configuring all third-party integrations (MongoDB Atlas, Cloudflare, Cloudinary, Brevo SMTP), and recovering the service from scratch in the event of an outage.
Any developer or DevOps engineer can follow this guide to bring the entire system online and troubleshoot every edge case.
flowchart TD
User([End Users / Mobile / Web]) --> CF[Cloudflare CDN / DNS / WAF / Turnstile]
CF -->|Port 443 HTTPS| Nginx[Nginx Reverse Proxy on AWS EC2]
Nginx -->|Proxy Pass 127.0.0.1:5000| DockerApp[Docker Container: qucodex/codex-backend:latest]
subgraph ExternalServices [Third-Party Cloud Services]
DockerApp -->|Mongoose TLS| Mongo[(MongoDB Atlas Database)]
DockerApp -->|REST API Uploads| Cloudinary[(Cloudinary Media CDN)]
DockerApp -->|SMTP TLS 587| Brevo[(Brevo SMTP Email Relay)]
DockerApp -->|Siteverify API| Turnstile[Cloudflare Turnstile Verification]
end
- Production API URL:
https://api.qucodex.com - Internal Port:
5000(strictly bound to127.0.0.1:5000inside Docker) - Healthcheck Endpoint:
https://api.qucodex.com/api/v1/healthcheck - Automated Deployment Script:
deploy-shareable.sh
Follow these steps when provisioning a new server on AWS.
- Log in to the AWS EC2 Console.
- Click Launch an instance.
- Configure the following parameters:
- Name:
codex-production-backend - AMI (Operating System):
Ubuntu Server 24.04 LTSorUbuntu Server 22.04 LTS(64-bit x86 architecture). - Instance Type:
t3.small(recommended for production: 2 vCPU, 2 GB RAM) ort3.micro/t2.micro(minimum). - Key Pair: Select your existing key pair or generate a new
.pemkey (e.g.,codex-key.pem). Save this key securely.
- Name:
In Network settings, create or select a Security Group with these exact Inbound Rules:
| Type | Protocol | Port | Source | Description |
|---|---|---|---|---|
| SSH | TCP | 22 |
My IP (or Admin CIDR) |
Remote terminal access |
| HTTP | TCP | 80 |
0.0.0.0/0 (Anywhere) |
Certbot Let's Encrypt SSL & HTTP redirects |
| HTTPS | TCP | 443 |
0.0.0.0/0 (Anywhere) |
Encrypted API traffic from clients |
Caution
Never open port 5000 or port 27017 to 0.0.0.0/0. Port 5000 is bound strictly to 127.0.0.1 and proxied through Nginx. Exposing raw application ports bypasses rate limiting, SSL, and security headers.
- Root Volume: 20 GB gp3 SSD (general purpose, 3000 IOPS).
- Click Launch instance.
An Elastic IP (EIP) is a permanent static IPv4 address. Without it, AWS assigns a new IP address every time the instance is stopped or rebuilt.
- Navigate to EC2 Console > Network & Security > Elastic IPs.
- Click Allocate Elastic IP address > Allocate.
- Select the new Elastic IP > Actions > Associate Elastic IP address.
- Select your instance (
codex-production-backend) and click Associate. - Note this Elastic IP (e.g.
13.235.120.45).
Before running the deployment script, set up and verify the following 4 cloud services:
MongoDB Atlas is the managed database provider for CodeX.
- Log in to MongoDB Atlas.
- Create a cluster (e.g.,
CodeXcluster on AWS inap-south-1Mumbai).
- Go to Security > Database Access > Add New Database User.
- Authentication Method: Password.
- Username:
codexclub_db_user(or your chosen username). - Password: Generate a strong password (minimum 16 characters).
- Database User Privileges:
Read and write to any database(or specific tocodexdb). - Click Add User.
Important
MongoDB Atlas rejects connections by default unless the client IP is whitelisted.
- Go to Security > Network Access > Add IP Address.
- Option A (Recommended for Production): Add your AWS EC2 Elastic IP address (e.g.,
13.235.120.45/32) with commentAWS EC2 Backend Elastic IP. - Option B (Zero downtime on IP changes): Add
0.0.0.0/0(Allow access from anywhere) and rely on strong database credentials and scram-sha-1/256 authentication.
- Go to Database > Deployments > Click Connect on your cluster.
- Select Drivers (Node.js version 5.5 or later).
- Copy the URI. Format:
mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/codex?retryWrites=true&w=majority&appName=CodeX - Replace
<username>and<password>with the actual database credentials.
Cloudflare handles DNS routing, DDoS protection, edge caching, and bot validation.
- Log in to the Cloudflare Dashboard.
- Select your domain (
qucodex.com). - Navigate to DNS > Records > Add record:
- Type:
A - Name:
api(resolves toapi.qucodex.com) - IPv4 address: Your AWS EC2 Elastic IP (e.g.
13.235.120.45) - Proxy status:
- Initial Setup: Set to DNS Only (Grey Cloud) so Let's Encrypt Certbot can issue the SSL certificate via port 80 HTTP challenge.
- Post Setup: Once SSL is issued on the server, you may switch to Proxied (Orange Cloud) for Cloudflare CDN and WAF protection.
- TTL:
Auto
- Type:
- In Cloudflare, navigate to SSL/TLS > Overview.
- Select Full (strict) encryption mode.
Warning
Avoid "Flexible" SSL Mode! If Cloudflare is set to Flexible, Cloudflare sends plain HTTP to your server on port 80, but Nginx automatically redirects HTTP to HTTPS on port 443. This causes an infinite redirect loop (ERR_TOO_MANY_REDIRECTS). Always set Cloudflare SSL/TLS to Full (strict).
CodeX uses Cloudflare Turnstile to protect registration and authentication routes from automated bots.
- In Cloudflare Dashboard, go to Turnstile > Add Site.
- Site name:
CodeX Production - Domain: Add
qucodex.com,www.qucodex.com, andapi.qucodex.com. - Widget Mode: Managed (invisible or smart interactive).
- Click Create:
- Copy Site Key -> Provide this to Frontend (
VITE_TURNSTILE_SITE_KEY). - Copy Secret Key -> Provide this to Backend (
TURNSTILE_SECRET_KEY).
- Copy Site Key -> Provide this to Frontend (
CodeX uses Cloudinary for storing user avatars, event banners, team photos, and boarding passes.
- Log in to Cloudinary Console.
- On the Dashboard, locate the Product Environment Credentials:
- Cloud Name: (e.g.
fswmfdcp) - API Key: (e.g.
857412934439237) - API Secret: Click the eye icon to reveal the secret.
- Cloud Name: (e.g.
- In Settings > Upload, ensure standard image formats (
.webp,.jpg,.png,.svg) are allowed. - Max file size in CodeX backend is 50MB. Verify your Cloudinary account tier accommodates your expected monthly bandwidth.
CodeX uses standard SMTP transport to send verification OTPs, event registration tickets, certificates, boarding passes, and admin notifications.
- Primary Production Provider: AWS SES (Amazon Simple Email Service) — Highly scalable, cost-effective, native to the AWS infrastructure, and ensures enterprise-grade deliverability.
- Temporary / Backup Provider: Brevo (formerly Sendinblue) — Ideal for rapid temporary setup, staging, or as an immediate fallback while awaiting AWS SES production quota approval.
- In the AWS Management Console, navigate to Amazon Simple Email Service (SES) (recommended region:
ap-south-1Mumbai orus-east-1N. Virginia). - Go to Configuration > Identities > Click Create identity.
- Select Domain and enter
qucodex.com. - Enable Easy DKIM:
- DKIM signing key length:
RSA 2048-bit. - Publish DNS records: SES displays 3 CNAME records for Easy DKIM verification.
- DKIM signing key length:
- In Cloudflare DNS, add all 3 CNAME records provided by SES. Verification usually completes within 1–5 minutes.
To ensure high deliverability and prevent emails from landing in spam:
- In SES under Identity details for
qucodex.com, scroll to Custom MAIL FROM domain:- Click Set custom MAIL FROM domain.
- Subdomain:
mail.qucodex.com. - Behavior on MX failure:
Use default MAIL FROM domain.
- In Cloudflare DNS, add the required DNS records:
- MX Record: Name:
mail, Mail Server:feedback-smtp.<region>.amazonses.com(e.g.feedback-smtp.ap-south-1.amazonses.com), Priority:10. - SPF TXT Record: Name:
mail, Content:v=spf1 include:amazonses.com ~all. - Root SPF TXT Record: Name:
@, Content:v=spf1 include:amazonses.com ~all(if you already have an SPF record, appendinclude:amazonses.com). - DMARC TXT Record: Name:
_dmarc, Content:v=DMARC1; p=none; rua=mailto:admin@qucodex.com.
- MX Record: Name:
Important
All new AWS SES accounts start in the SES Sandbox. In sandbox mode, you can only send emails to pre-verified email addresses or recipient domains. To send to all students and users:
- In SES Console, navigate to Account dashboard.
- Under Sandbox status, click Request production access:
- Mail type: Transactional (OTPs, event tickets, system receipts).
- Website URL:
https://qucodex.com. - Use case description: State that emails are purely transactional (registration OTPs, certificate delivery, boarding passes) sent only to registered users with bounce and complaint monitoring.
- AWS approves production access typically within 12–24 hours.
- In the SES Console left sidebar, click SMTP Settings.
- Note your SMTP endpoint: e.g.,
email-smtp.ap-south-1.amazonaws.com(or your chosen AWS region). - Port:
587(STARTTLS). - Click Create SMTP credentials:
- AWS automatically generates an IAM user (e.g.
ses-smtp-user.codex) with policyAmazonSesSendingAccess. - Click Create credentials and immediately download/copy the generated SMTP Username and SMTP Password. (Note: The SES SMTP password is a specially transformed version of the IAM secret key for SMTP authentication).
- AWS automatically generates an IAM user (e.g.
If AWS SES is pending production quota approval or you need a quick temporary email relay:
- Log in to Brevo.
- Click your account profile > SMTP & API > SMTP tab.
- Note SMTP Server:
smtp-relay.brevo.com, Port:587. - Click Generate a new SMTP key and copy it as your
SMTP_PASSWORD. - Under Senders, Domains & IPs > Senders, ensure
noreply@qucodex.comis listed and verified.
Once the AWS EC2 instance is created and third-party credentials are ready:
chmod 400 path/to/codex-key.pem
ssh -i "path/to/codex-key.pem" ubuntu@<YOUR_ELASTIC_IP># Update package list and install git
sudo apt update && sudo apt install -y git
# Clone CodeX repository
git clone https://github.com/QuCodeXClub/CodeX.git
cd CodeX
# Ensure script is executable
chmod +x deploy-shareable.sh
# Run the automated deployment script
./deploy-shareable.shThe script prompts for each configuration item. You can press Enter to accept defaults/samples or enter custom values:
| Variable | Recommended Production Value | Description |
|---|---|---|
DOMAIN |
api.qucodex.com |
Public API domain name |
EMAIL |
codex.club@quantumeducation.in |
Alert email for Certbot SSL renewals |
PORT |
5000 |
Internal Node.js port (mapped in Docker) |
CORS_ORIGIN |
https://qucodex.com,https://www.qucodex.com |
Allowed origins for web requests |
FRONTEND_URL |
https://qucodex.com |
Base URL used in emails and QR links |
CONTAINER_NAME |
codex-backend |
Docker container identifier |
IMAGE |
qucodex/codex-backend:latest |
Production image on Docker Hub |
DOCKER_USER |
qucodex |
Docker Hub username/org |
DOCKER_TOKEN |
Your Docker PAT | Personal Access Token (or Enter for public pull) |
MONGODB_URI |
mongodb+srv://... |
Full MongoDB Atlas connection string |
ACCESS_TOKEN_SECRET |
Press Enter to auto-generate | 64-character random cryptographically secure key |
ACCESS_TOKEN_EXPIRY |
10d |
JWT session validity duration |
CLOUDINARY_CLOUD_NAME |
Cloudinary Cloud Name | Cloudinary cloud identifier |
CLOUDINARY_API_KEY |
Cloudinary API Key | Cloudinary public key |
SMTP_HOST |
email-smtp.ap-south-1.amazonaws.com (or smtp-relay.brevo.com) |
AWS SES (Production) or Brevo (Temporary) |
SMTP_PORT |
587 |
TLS / STARTTLS port |
SMTP_USER |
SES SMTP Username (or Brevo email) | IAM SES SMTP user or Brevo login |
SMTP_PASSWORD |
SES SMTP Password (or Brevo key) | IAM SES SMTP password or Brevo key |
FROM_EMAIL |
noreply@qucodex.com |
Outbound sender email |
FROM_NAME |
CodeX Club |
Outbound sender display name |
ADMIN_EMAIL |
admin@qucodex.com |
Initial superadmin email seed |
ADMIN_PASSWORD |
Your strong password | Initial superadmin password seed |
TURNSTILE_SECRET_KEY |
Cloudflare Turnstile Secret | Server-side Turnstile verification key |
TURNSTILE_HOSTNAMES |
qucodex.com,www.qucodex.com,api.qucodex.com |
Allowed hostnames for Turnstile tokens |
This section details all known operational edge cases and their exact solutions.
Symptom: The container starts, but crashes or healthcheck fails with MongooseServerSelectionError: Could not connect to any servers in your MongoDB Atlas cluster.
Root Causes & Solutions:
- EC2 IP Not in Atlas Network Access List:
- If the EC2 instance was replaced or its IP changed, Atlas rejects the connection.
- Fix: Go to MongoDB Atlas > Network Access > Add the Elastic IP address (
<EIP>/32) or temporarily allow0.0.0.0/0.
- Special Characters in Database Password Not URL-Encoded:
- If the database password contains special characters like
@,:,/,?,#,[,], Mongoose fails to parse the connection string. - Fix: URL-encode the characters in the password:
@->%40#->%23:->%3A$->%24/->%2F
- Example: If password is
Pass@123, usePass%40123inMONGODB_URI.
- If the database password contains special characters like
- TLS/SSL Handshake Failure:
- Ensure the connection URI uses
mongodb+srv://. The+srvprotocol automatically negotiates TLS.
- Ensure the connection URI uses
Symptom: Browsing to https://api.qucodex.com produces browser error ERR_TOO_MANY_REDIRECTS.
Root Cause:
- Cloudflare SSL/TLS mode is set to Flexible. Cloudflare connects to your EC2 instance over HTTP port 80. Nginx responds with a
301 Moved Permanentlyredirecting tohttps://api.qucodex.com. Cloudflare receives the redirect and requests port 80 again, creating an infinite loop.
Solution:
- In Cloudflare Dashboard, go to SSL/TLS > Overview.
- Change the SSL mode from Flexible to Full (strict) (or Full).
- Clear Cloudflare cache: Caching > Configuration > Purge Everything.
Symptom: During deployment step [7/7], Certbot outputs:
Certbot failed to authenticate some domains (authenticator: nginx). Some challenges have failed.
Root Causes & Solutions:
- Cloudflare Proxy (Orange Cloud) Active During Initial Issuance:
- Cloudflare WAF or proxy intercepts HTTP port 80 requests before reaching your Nginx server.
- Fix: In Cloudflare DNS, edit the
apirecord and switch Proxy status to DNS Only (Grey Cloud). Wait 60 seconds, then re-run:sudo certbot --nginx -d api.qucodex.com --agree-tos --email codex.club@quantumeducation.in --redirect
- Once the certificate is issued, you can safely switch Cloudflare back to Proxied (Orange Cloud)!
- AWS Security Group Port 80 Closed:
- Certbot requires port 80 open to complete the ACME HTTP-01 challenge.
- Fix: In AWS EC2 Console, verify your Security Group has an inbound rule for
HTTP (80)from0.0.0.0/0.
Symptom: Admin password or secrets with # are mysteriously truncated upon container launch.
Root Cause:
- In Docker
.envfiles, any text after an unquoted#is interpreted by Docker Compose as a comment. For example,ADMIN_PASSWORD=Xb8&L2!fY#5vK*9mQ$3pgets truncated toADMIN_PASSWORD=Xb8&L2!fY.
Solution:
deploy-shareable.shautomatically quotes all variables in the generated.env:ADMIN_PASSWORD="Xb8&L2!fY#5vK*9mQ$3p"- If manually editing
~/codex/backend/.env, always wrap values containing#,$,&, or!in double quotes.
Symptom: Student registrations or login requests fail with 400 Bad Request and message Captcha verification failed.
Root Causes & Solutions:
- Site Key / Secret Key Mismatch:
- The frontend used the Site Key of one Turnstile widget, while the backend
.envused the Secret Key of a different widget. - Fix: Verify that
TURNSTILE_SECRET_KEYin~/codex/backend/.envbelongs to the exact widget used by the frontend.
- The frontend used the Site Key of one Turnstile widget, while the backend
- Hostname Whitelist Mismatch:
- In Cloudflare Turnstile settings,
qucodex.com,www.qucodex.com, andapi.qucodex.commust be explicitly added to the allowed domain list.
- In Cloudflare Turnstile settings,
Symptom: Image uploads for events or team members fail.
Root Causes & Solutions:
- Invalid API Secret or Typo in Cloud Name:
- Verify
CLOUDINARY_CLOUD_NAME,CLOUDINARY_API_KEY, andCLOUDINARY_API_SECRETin~/codex/backend/.env.
- Verify
- Nginx Payload Size Limit:
- If users upload high-resolution images (> 1MB), Nginx returns
413 Request Entity Too Largeby default. - Fix:
deploy-shareable.shautomatically configuresclient_max_body_size 50M;in Nginx.
- If users upload high-resolution images (> 1MB), Nginx returns
Symptom: Verification OTPs or ticket confirmation emails are not delivered or bounce.
Root Causes & Solutions:
- AWS SES Sandbox Mode Restriction (Primary Production):
- In SES Sandbox mode, emails to unverified recipients fail with error
554 Message rejected: Email address is not verified. - Fix: Submit a Production Access Request in the SES Account Dashboard. While waiting, you can temporarily test by verifying individual recipient emails in SES > Identities, or temporarily switch to Brevo.
- In SES Sandbox mode, emails to unverified recipients fail with error
- Standard IAM Secret Key Used Instead of SES SMTP Password:
- AWS IAM Secret Access Keys cannot be used directly as SMTP passwords.
- Fix: Generate SMTP credentials exclusively from Amazon SES > SMTP Settings > Create SMTP credentials.
- Brevo SMTP Authentication Failure (Temporary Fallback):
- Brevo rejects account login passwords on port 587. You must generate an SMTP Key under Brevo > SMTP & API > SMTP.
- Unverified Sender Address:
- The sender email (
noreply@qucodex.com) must be a verified identity in SES or Brevo. If unverified, the SMTP server will immediately reject outbound requests.
- The sender email (
Symptom: Docker terminates unexpectedly, or Node.js crashes with Killed in logs.
Solution:
- If running on
t2.microort3.micro(1 GB RAM), add a 2GB swap file to prevent the Linux Out-Of-Memory (OOM) killer from stopping the backend:sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Follow this section if api.qucodex.com stops responding.
- Start the instance:
- Go to AWS EC2 Console > Instances.
- Select
codex-production-backend> Instance state > Start instance.
- Verify Elastic IP:
- Because an Elastic IP is associated, the public IP remains identical. No DNS adjustments needed.
- Verify automated container recovery:
- Both Docker and Nginx start automatically on system boot.
- Verify status via SSH:
ssh -i "codex-key.pem" ubuntu@<ELASTIC_IP> sudo docker compose -f ~/codex/backend/docker-compose.yml ps sudo systemctl status nginx
- Manual restart if necessary:
cd ~/codex/backend sudo docker compose up -d sudo systemctl restart nginx
If the server is unrecoverable, follow this 5-minute restoration procedure:
- Launch a new Ubuntu 24.04/22.04 LTS instance (Part 1, Steps 1–3).
- Attach the existing Security Group (ports 22, 80, 443).
- Go to EC2 Console > Elastic IPs.
- Select your existing Elastic IP > Actions > Associate Elastic IP address.
- Select the new replacement instance and click Associate.
- Immediate benefit:
api.qucodex.comnow routes to the new server instantly.
# Connect to the replacement server
ssh -i "codex-key.pem" ubuntu@<ELASTIC_IP>
# Clone repository
git clone https://github.com/QuCodeXClub/CodeX.git
cd CodeX
# Run deployment
chmod +x deploy-shareable.sh
./deploy-shareable.sh- Enter your MongoDB Atlas URI, JWT Secret, Cloudinary credentials, and Brevo SMTP password.
- The script automatically installs Docker, Nginx, Certbot, configures SSL, and launches the container.
curl -i https://api.qucodex.com/api/v1/healthcheck- SSH into the server:
ssh -i "codex-key.pem" ubuntu@<ELASTIC_IP>
- Check container status:
cd ~/codex/backend sudo docker compose ps
- Check recent application logs:
sudo docker compose logs --tail=100 -f backend
- Restart the backend container:
sudo docker compose restart
When a new backend Docker image has been pushed to Docker Hub (qucodex/codex-backend:latest):
# SSH into EC2
ssh -i "codex-key.pem" ubuntu@<ELASTIC_IP>
# Navigate to backend directory
cd ~/codex/backend
# Pull latest image and restart container cleanly
sudo docker compose pull
sudo docker compose up -d --remove-orphans
# Clean unused image layers
sudo docker image prune -f
# Verify running container
sudo docker compose ps
curl -s http://127.0.0.1:5000/api/v1/healthcheck# Edit environment configuration
nano ~/codex/backend/.env
# Restart container to apply new environment variables
cd ~/codex/backend
sudo docker compose up -d
# Verify logs
sudo docker compose logs --tail=30 backend| Action | Command |
|---|---|
| View Live Container Logs | sudo docker logs -f --tail=100 codex-backend |
| Restart Backend Container | cd ~/codex/backend && sudo docker compose restart |
| Recreate & Launch Container | cd ~/codex/backend && sudo docker compose up -d --force-recreate |
| Check Healthcheck Endpoint | curl -i https://api.qucodex.com/api/v1/healthcheck |
| Test Nginx Syntax | sudo nginx -t |
| Restart Nginx | sudo systemctl restart nginx |
| Check SSL Certificates | sudo certbot certificates |
| Force SSL Renewal | sudo certbot renew --force-renewal |
| Check Disk Space | df -h / |
| Check RAM Usage | free -h |
| Inspect Systemd Docker Status | sudo systemctl status docker |