Skip to main content
Complete this checklist before deploying FootyCollect to production. This ensures all security settings are configured and the application is production-ready.

Pre-Deployment Checklist

Use this comprehensive checklist to verify your production deployment.

Django Configuration

1

DEBUG Disabled

Critical Security: DEBUG=True exposes sensitive information including settings, environment variables, and stack traces.
.env
DJANGO_DEBUG=False
Verify:
grep "DJANGO_DEBUG" .env
# Should show: DJANGO_DEBUG=False
This is validated by config/checks.py:20.
2

Secure SECRET_KEY

Generate a unique, random SECRET_KEY:
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
Requirements:
  • Minimum 50 characters (config/checks.py:17)
  • Unique per environment
  • Never use default value
  • Never commit to version control
.env
DJANGO_SECRET_KEY=django-insecure-abc123...  # BAD
DJANGO_SECRET_KEY=u8v#^9xzq...50+chars...     # GOOD
3

ALLOWED_HOSTS Configured

Set specific domain names (no wildcards):
.env
DJANGO_ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
Do NOT use:
DJANGO_ALLOWED_HOSTS=*  # Insecure!
Validated by config/checks.py:300.
4

Admin URL Changed

Change from default /admin/ to prevent automated attacks:
.env
DJANGO_ADMIN_URL=secret-admin-path/
Access admin at: https://yourdomain.com/secret-admin-path/

SSL/TLS Security

1

SSL Certificate Installed

Verify SSL certificate is active:
# Test HTTPS
curl -I https://yourdomain.com

# Check certificate
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com
Should show valid certificate from Let’s Encrypt or your CA.
2

HTTPS Redirect Enabled

Force all traffic to HTTPS:
.env
DJANGO_SECURE_SSL_REDIRECT=True
Test:
curl -I http://yourdomain.com
# Should return 301/302 redirect to https://
3

HSTS Configured

Enable HTTP Strict Transport Security:
.env
DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS=True
DJANGO_SECURE_HSTS_PRELOAD=True
Verify header:
curl -I https://yourdomain.com | grep -i strict-transport
# Should show: Strict-Transport-Security: max-age=31536000; includeSubDomains
4

Secure Cookies

Configure cookie security:
.env
DJANGO_SESSION_COOKIE_SAMESITE=Lax
DJANGO_CSRF_COOKIE_SAMESITE=Lax
Validated by config/checks.py:334.

Database Security

1

Strong Database Password

Use a strong password for PostgreSQL:
# Generate secure password
openssl rand -base64 32
Requirements:
  • Minimum 16 characters
  • Mix of letters, numbers, symbols
  • Unique per environment
.env
DATABASE_URL=postgresql://footycollect:strong-password-here@localhost:5432/footycollect_db
2

Database Connectivity

Test database connection:
# Bare metal
sudo -u postgres psql -d footycollect_db -U footycollect -c "SELECT 1;"

# Docker
docker compose -f docker-compose.production.yml exec postgres psql -U footycollect -d footycollect_db -c "SELECT 1;"
Validated automatically by config/checks.py:127.
3

Database Backups Configured

Verify backups are working:Bare metal:
# Check backup directory
ls -lh /var/www/footycollect/backups/

# Test manual backup
sudo -u postgres pg_dump footycollect_db > test_backup.sql
Docker:
# Test backup
docker compose -f docker-compose.production.yml exec postgres backup

# List backups
docker compose -f docker-compose.production.yml exec postgres backups

Redis Configuration

1

Redis Connectivity

Test Redis connection:
# Bare metal
redis-cli ping
# Should return: PONG

# Docker
docker compose -f docker-compose.production.yml exec redis redis-cli ping
Validated by config/checks.py:151.
2

Redis Password (Optional but Recommended)

For additional security, set Redis password:
# /etc/redis/redis.conf
requirepass your-redis-password
Update connection string:
.env
REDIS_URL=redis://:your-redis-password@localhost:6379/0

Storage Configuration

1

Storage Credentials Configured

Verify S3/R2 credentials are set:
# Check storage backend
grep STORAGE_BACKEND .env

# For AWS S3
grep -E "DJANGO_AWS_" .env

# For Cloudflare R2
grep -E "CLOUDFLARE_" .env
Validated by config/checks.py:268.
2

Static Files Collected

Test collectstatic uploads to S3/R2:
# Verify configuration
python manage.py verify_staticfiles

# Collect static files
python manage.py collectstatic --noinput

# Check files are accessible
curl -I https://your-bucket.s3.amazonaws.com/static/css/styles.css
3

CORS Configured (R2 Only)

If using Cloudflare R2 with custom domain, configure CORS:
npx wrangler r2 bucket cors set your-bucket-name --file deploy/r2-cors-wrangler.json
Test fonts/static assets load without CORS errors in browser console.

Email Configuration

1

SendGrid API Key Configured

.env
SENDGRID_API_KEY=SG.xxxxxxxxxxxx
DJANGO_DEFAULT_FROM_EMAIL=FootyCollect <noreply@yourdomain.com>
2

Sender Domain Verified

Verify domain in SendGrid:
  1. SendGrid > Settings > Sender Authentication
  2. Verify domain is authenticated
  3. DNS records are configured
3

Test Email Delivery

# Django shell
python manage.py shell
from django.core.mail import send_mail

send_mail(
    'Test Email',
    'This is a test email from FootyCollect.',
    'noreply@yourdomain.com',
    ['your-email@example.com'],
    fail_silently=False,
)
Check inbox for test email.

Error Tracking

1

Sentry DSN Configured

.env
SENTRY_DSN=https://xxxxx@oxxxxx.ingest.sentry.io/xxxxx
SENTRY_ENVIRONMENT=production
2

Test Sentry Integration

Trigger a test error:
python manage.py shell
from sentry_sdk import capture_message
capture_message("FootyCollect production test")
Verify event appears in Sentry dashboard.

Security Headers

1

Content Security Policy

Configure CSP to allow required sources:
.env
DJANGO_CSP_ENABLED=True
DJANGO_CSP_IMG_SRC='self', data:, blob:, https://www.gravatar.com, https://cdn.footballkitarchive.com, https://your-bucket.s3.amazonaws.com
Test for CSP violations in browser console.
2

Verify Security Headers

Check all security headers are present:
curl -I https://yourdomain.com
Should include:
  • Strict-Transport-Security
  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • Referrer-Policy: strict-origin-when-cross-origin
  • Content-Security-Policy
3

Test with Security Headers Analyzer

Use online tools to verify headers:Aim for A or A+ grade.

Firewall and Network

1

Firewall Configured

Bare metal:
# Check UFW status
sudo ufw status verbose

# Should show:
# - 22/tcp (SSH) ALLOW
# - 80/tcp (HTTP) ALLOW
# - 443/tcp (HTTPS) ALLOW
# - Default: deny incoming, allow outgoing
Docker:
# Verify only required ports exposed
docker compose -f docker-compose.production.yml ps
# Should show 80:80, 443:443
2

Fail2ban Enabled (Bare Metal)

# Check fail2ban is active
sudo systemctl status fail2ban

# Check SSH jail
sudo fail2ban-client status sshd
3

SSH Security

Harden SSH access:
/etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no  # Use SSH keys only
sudo systemctl restart sshd

Service Health

1

All Services Running

Bare metal:
sudo systemctl status gunicorn
sudo systemctl status nginx
sudo systemctl status postgresql
sudo systemctl status redis
Docker:
docker compose -f docker-compose.production.yml ps
# All services should show "Up"
2

Health Endpoints

Test application health:
# Basic health check
curl https://yourdomain.com/health/
# Should return: {"status": "ok"}

# Readiness check (includes DB)
curl https://yourdomain.com/ready/
# Should return: {"status": "ready"}
3

Admin Access

Verify admin panel access:
# Visit admin URL
curl -I https://yourdomain.com/your-admin-url/
# Should return 200 OK or 302 redirect to login
Log in with superuser credentials.

Django Deployment Checks

1

Run Django Checks

Run comprehensive deployment checks:
python manage.py check --deploy
This validates (config/checks.py):
  • ✓ DEBUG disabled (checks.py:20)
  • ✓ SECRET_KEY secure (checks.py:40)
  • ✓ Required environment variables (checks.py:83)
  • ✓ Database connectivity (checks.py:127)
  • ✓ Redis connectivity (checks.py:151)
  • ✓ Storage credentials (checks.py:268)
  • ✓ ALLOWED_HOSTS configured (checks.py:300)
  • ✓ SSL/HTTPS settings (checks.py:334)
All checks must pass before production deployment.
2

Address Warnings

Fix any warnings reported by checks:
python manage.py check --deploy 2>&1 | grep -E "WARNING|ERROR"
Common warnings:
  • Missing SENTRY_DSN (recommended)
  • ALLOWED_HOSTS contains wildcard
  • Missing storage credentials

Production Deployment Checklist

Print and complete this checklist:

Django Configuration

  • DEBUG=False set
  • Unique SECRET_KEY generated (50+ characters)
  • ALLOWED_HOSTS configured (no wildcards)
  • Admin URL changed from default
  • python manage.py check --deploy passes

Security

  • SSL certificate installed and valid
  • HTTPS redirect enabled (SECURE_SSL_REDIRECT=True)
  • HSTS configured
  • Secure cookies configured
  • Content Security Policy enabled
  • Security headers verified
  • Firewall configured (UFW or cloud firewall)
  • Fail2ban enabled (bare metal)
  • SSH hardened (no root, key-based auth)

Database

  • Strong database password set
  • Database connectivity verified
  • Database backups configured
  • Backup restoration tested

Redis

  • Redis connectivity verified
  • Redis password set (recommended)

Storage

  • Storage backend configured (S3/R2)
  • Storage credentials validated
  • collectstatic runs successfully
  • Static files accessible via CDN/bucket URL
  • CORS configured (R2 only)

Email

  • SendGrid API key configured
  • Sender domain verified in SendGrid
  • Test email sent successfully

Monitoring

  • Sentry DSN configured
  • Sentry integration tested
  • Error alerts configured in Sentry

Services

  • All services running and enabled
  • Health endpoints responding
  • Admin panel accessible
  • Logs are being written
  • Log rotation configured

Performance

  • Gunicorn workers configured appropriately
  • Redis caching enabled
  • Compression enabled
  • Static files served from CDN

Documentation

  • Environment variables documented
  • Deployment process documented
  • Recovery procedures documented
  • Team has access to credentials (securely)

Post-Deployment Monitoring

After deployment, monitor for:

First 24 Hours

Monitor Sentry for errors:
  • Check error frequency
  • Review stack traces
  • Verify no critical errors
# Application response time
curl -w "@curl-format.txt" -o /dev/null -s https://yourdomain.com/

# Database performance
sudo -u postgres psql -d footycollect_db -c "SELECT * FROM pg_stat_activity;"

# Redis memory usage
redis-cli info memory
# Application logs
sudo journalctl -u gunicorn --since "1 hour ago"

# Nginx access logs
sudo tail -100 /var/log/nginx/footycollect_access.log

# Check for errors
sudo tail -100 /var/log/nginx/footycollect_error.log
Ensure backups are running:
# Check backup files
ls -lh /var/www/footycollect/backups/

# Verify backup size is reasonable
du -h /var/www/footycollect/backups/

First Week

  • Monitor disk space usage
  • Review user registration and activity
  • Check email delivery success rate
  • Verify SSL certificate auto-renewal is configured
  • Review Sentry performance metrics
  • Test database restore procedure

Ongoing

  • Weekly log reviews
  • Monthly security updates
  • Quarterly credential rotation
  • Regular backup restoration tests
  • Performance monitoring and optimization

Troubleshooting Checklist Failures

# View detailed check output
python manage.py check --deploy --verbosity 2

# Check specific settings
python manage.py shell
>>> from django.conf import settings
>>> settings.DEBUG
False
>>> settings.SECRET_KEY[:10]
'randomXXXX'
# Check Gunicorn is running
sudo systemctl status gunicorn

# Check Gunicorn logs
sudo journalctl -u gunicorn -n 50

# Test directly (bypass Nginx)
curl http://localhost:8000/health/
# Verify collectstatic ran
python manage.py collectstatic --dry-run

# Check storage configuration
python manage.py verify_staticfiles

# Test S3/R2 connectivity
python manage.py check --deploy | grep -i storage
# Test PostgreSQL
sudo -u postgres psql -d footycollect_db -U footycollect

# Check DATABASE_URL format
echo $DATABASE_URL

# Verify pg_hba.conf allows connections
sudo cat /etc/postgresql/*/main/pg_hba.conf

Final Verification

Before announcing your deployment:
1

Complete User Journey

Test the complete user experience:
  1. Visit homepage via HTTPS
  2. Register new account
  3. Verify email received
  4. Log in
  5. Create an item
  6. Upload a photo
  7. View collection
  8. Test search/filter
  9. Log out
2

Security Scan

Run security scanners:
3

Load Test (Optional)

Basic load testing:
# Install Apache Bench
sudo apt-get install apache2-utils

# Test 100 requests, 10 concurrent
ab -n 100 -c 10 https://yourdomain.com/
Review response times and error rates.
Congratulations! If all checklist items are complete and verified, your FootyCollect deployment is production-ready.

Quick Reference Commands

# Run all deployment checks
python manage.py check --deploy

# Test health endpoints
curl https://yourdomain.com/health/
curl https://yourdomain.com/ready/

# Verify static files
python manage.py verify_staticfiles

# Check services (bare metal)
sudo systemctl status gunicorn nginx postgresql redis

# Check services (Docker)
docker compose -f docker-compose.production.yml ps

# View application logs (bare metal)
sudo journalctl -u gunicorn -f

# View application logs (Docker)
docker compose -f docker-compose.production.yml logs -f django

# Database backup (bare metal)
sudo -u postgres pg_dump footycollect_db > backup.sql

# Database backup (Docker)
docker compose -f docker-compose.production.yml exec postgres backup

Next Steps

After completing the checklist:

Monitoring Setup

Configure ongoing monitoring with Sentry, log aggregation, and uptime monitoring

Backup Strategy

Implement automated backup rotation and test restoration procedures

Performance Tuning

Optimize Gunicorn workers, database queries, and caching strategies

Scaling

Plan for horizontal scaling with load balancers and multiple application servers