# Environment Variables Reference Complete reference for all environment variables used in ThrillWiki. ## Quick Start 1. Copy the example file: `cp .env.example .env` 2. Edit `.env` with your values 3. Run validation: `python manage.py validate_settings` ## Required Variables These must be set in all environments. | Variable | Type | Description | |----------|------|-------------| | `SECRET_KEY` | string | Django secret key (minimum 50 characters) | | `DATABASE_URL` | url | Database connection URL | ## Production-Required Variables These variables **must** be explicitly set for production deployments. The application will not function correctly in production without them. > **Important:** See [`.env.example`](../../.env.example) for example values and [`PRODUCTION_CHECKLIST.md`](../PRODUCTION_CHECKLIST.md) for deployment verification steps. | Variable | Type | Format/Example | Description | |----------|------|----------------|-------------| | `DEBUG` | bool | `False` | **Must be `False` in production.** Enables detailed error pages and debug toolbar when `True`. | | `DJANGO_SETTINGS_MODULE` | string | `config.django.production` | **Must use `config.django.production` for production.** Controls which settings module Django loads. | | `ALLOWED_HOSTS` | list | `thrillwiki.com,www.thrillwiki.com` | **Required.** Comma-separated list of valid hostnames. No default in production - requests will fail without this. | | `CSRF_TRUSTED_ORIGINS` | list | `https://thrillwiki.com,https://www.thrillwiki.com` | **Required.** Must include `https://` prefix. CSRF validation fails without proper configuration. | | `REDIS_URL` | url | `redis://[:password@]host:6379/0` | **Required for caching and sessions.** Production uses Redis for caching, session storage, and Celery broker. | ### Production Settings Behavior When `DJANGO_SETTINGS_MODULE=config.django.production`: - `DEBUG` is always `False` - `ALLOWED_HOSTS` has **no default** - must be explicitly set - `CSRF_TRUSTED_ORIGINS` has **no default** - must be explicitly set - SSL redirect is enforced (`SECURE_SSL_REDIRECT=True`) - Secure cookies are enforced (`SESSION_COOKIE_SECURE=True`, `CSRF_COOKIE_SECURE=True`) - HSTS is enabled with 1-year max-age - Redis cache is required (application will fail if `REDIS_URL` is not set) ### Validation Run the following to validate your production configuration: ```bash DJANGO_SETTINGS_MODULE=config.django.production python manage.py check --deploy ``` ## Core Django Settings | Variable | Type | Default | Description | |----------|------|---------|-------------| | `DEBUG` | bool | `True` | Enable debug mode | | `DJANGO_SETTINGS_MODULE` | string | `config.django.local` | Settings module to use | | `ALLOWED_HOSTS` | list | `localhost,127.0.0.1` | Comma-separated allowed hosts | | `CSRF_TRUSTED_ORIGINS` | list | `` | Comma-separated trusted origins | ## Database Configuration | Variable | Type | Default | Description | |----------|------|---------|-------------| | `DATABASE_URL` | url | (required) | Database connection URL | | `DATABASE_CONN_MAX_AGE` | int | `600` | Connection pool timeout (seconds) | | `DATABASE_CONNECT_TIMEOUT` | int | `10` | Connection timeout (seconds) | | `DATABASE_STATEMENT_TIMEOUT` | int | `30000` | Query timeout (milliseconds) | | `DATABASE_READ_REPLICA_URL` | url | `` | Optional read replica URL | ### Database URL Formats ```bash # PostgreSQL with PostGIS DATABASE_URL=postgis://user:password@host:5432/dbname # PostgreSQL without PostGIS DATABASE_URL=postgres://user:password@host:5432/dbname # SQLite (development only) DATABASE_URL=sqlite:///path/to/db.sqlite3 ``` ## GeoDjango Settings | Variable | Type | Default | Description | |----------|------|---------|-------------| | `GDAL_LIBRARY_PATH` | path | `/opt/homebrew/lib/libgdal.dylib` | Path to GDAL library | | `GEOS_LIBRARY_PATH` | path | `/opt/homebrew/lib/libgeos_c.dylib` | Path to GEOS library | ### Platform-Specific Paths ```bash # macOS (Homebrew) GDAL_LIBRARY_PATH=/opt/homebrew/lib/libgdal.dylib GEOS_LIBRARY_PATH=/opt/homebrew/lib/libgeos_c.dylib # Linux (Ubuntu/Debian) GDAL_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/libgdal.so GEOS_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/libgeos_c.so ``` ## Cache Configuration | Variable | Type | Default | Description | |----------|------|---------|-------------| | `REDIS_URL` | url | `redis://127.0.0.1:6379/1` | Redis connection URL | | `REDIS_SESSIONS_URL` | url | (from REDIS_URL) | Redis URL for sessions | | `REDIS_API_URL` | url | (from REDIS_URL) | Redis URL for API cache | | `REDIS_MAX_CONNECTIONS` | int | `100` | Maximum Redis connections | | `REDIS_CONNECTION_TIMEOUT` | int | `20` | Redis connection timeout | | `REDIS_IGNORE_EXCEPTIONS` | bool | `True` | Graceful degradation | | `CACHE_MIDDLEWARE_SECONDS` | int | `300` | Cache middleware timeout | | `CACHE_MIDDLEWARE_KEY_PREFIX` | string | `thrillwiki` | Cache key prefix | | `CACHE_KEY_PREFIX` | string | `thrillwiki` | Default cache key prefix | ## Email Configuration | Variable | Type | Default | Description | |----------|------|---------|-------------| | `EMAIL_BACKEND` | string | `django_forwardemail.backends.ForwardEmailBackend` | Email backend class | | `SERVER_EMAIL` | email | `django_webmaster@thrillwiki.com` | Server email address | | `DEFAULT_FROM_EMAIL` | string | `ThrillWiki ` | Default from address | | `EMAIL_SUBJECT_PREFIX` | string | `[ThrillWiki]` | Subject prefix | | `EMAIL_TIMEOUT` | int | `30` | Email operation timeout | ### ForwardEmail Settings | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FORWARD_EMAIL_BASE_URL` | url | `https://api.forwardemail.net` | API base URL | | `FORWARD_EMAIL_API_KEY` | string | `` | API key | | `FORWARD_EMAIL_DOMAIN` | string | `` | Sending domain | ### SMTP Settings | Variable | Type | Default | Description | |----------|------|---------|-------------| | `EMAIL_HOST` | string | `localhost` | SMTP server | | `EMAIL_PORT` | int | `587` | SMTP port | | `EMAIL_USE_TLS` | bool | `True` | Use TLS | | `EMAIL_USE_SSL` | bool | `False` | Use SSL | | `EMAIL_HOST_USER` | string | `` | SMTP username | | `EMAIL_HOST_PASSWORD` | string | `` | SMTP password | ## Security Settings ### SSL/HTTPS | Variable | Type | Default | Description | |----------|------|---------|-------------| | `SECURE_SSL_REDIRECT` | bool | `False` | Redirect HTTP to HTTPS | | `SECURE_PROXY_SSL_HEADER` | tuple | `` | Proxy SSL header | ### HSTS (HTTP Strict Transport Security) | Variable | Type | Default | Description | |----------|------|---------|-------------| | `SECURE_HSTS_SECONDS` | int | `31536000` | HSTS max-age | | `SECURE_HSTS_INCLUDE_SUBDOMAINS` | bool | `True` | Include subdomains | | `SECURE_HSTS_PRELOAD` | bool | `False` | Allow preload | | `SECURE_REDIRECT_EXEMPT` | list | `` | URLs exempt from redirect | ### Security Headers | Variable | Type | Default | Description | |----------|------|---------|-------------| | `SECURE_BROWSER_XSS_FILTER` | bool | `True` | XSS filter header | | `SECURE_CONTENT_TYPE_NOSNIFF` | bool | `True` | Nosniff header | | `X_FRAME_OPTIONS` | string | `DENY` | Frame options | | `SECURE_REFERRER_POLICY` | string | `strict-origin-when-cross-origin` | Referrer policy | | `SECURE_CROSS_ORIGIN_OPENER_POLICY` | string | `same-origin` | COOP header | ### Cookie Security | Variable | Type | Default | Description | |----------|------|---------|-------------| | `SESSION_COOKIE_SECURE` | bool | `False` | HTTPS-only session cookie | | `SESSION_COOKIE_HTTPONLY` | bool | `True` | No JS access | | `SESSION_COOKIE_SAMESITE` | string | `Lax` | SameSite attribute | | `SESSION_COOKIE_AGE` | int | `3600` | Cookie age (seconds) | | `CSRF_COOKIE_SECURE` | bool | `False` | HTTPS-only CSRF cookie | | `CSRF_COOKIE_HTTPONLY` | bool | `True` | No JS access | | `CSRF_COOKIE_SAMESITE` | string | `Lax` | SameSite attribute | ### Cloudflare Turnstile | Variable | Type | Default | Description | |----------|------|---------|-------------| | `TURNSTILE_SITE_KEY` | string | `` | Turnstile site key | | `TURNSTILE_SECRET_KEY` | string | `` | Turnstile secret key | | `TURNSTILE_VERIFY_URL` | url | `https://challenges.cloudflare.com/turnstile/v0/siteverify` | Verification URL | ## API Configuration ### CORS | Variable | Type | Default | Description | |----------|------|---------|-------------| | `CORS_ALLOWED_ORIGINS` | list | `` | Allowed origins | | `CORS_ALLOW_ALL_ORIGINS` | bool | `False` | Allow all origins | ### Rate Limiting | Variable | Type | Default | Description | |----------|------|---------|-------------| | `API_RATE_LIMIT_PER_MINUTE` | int | `60` | Requests per minute | | `API_RATE_LIMIT_PER_HOUR` | int | `1000` | Requests per hour | | `API_RATE_LIMIT_ANON_PER_MINUTE` | int | `60` | Anonymous rate limit | | `API_RATE_LIMIT_USER_PER_HOUR` | int | `1000` | Authenticated rate limit | ### Pagination | Variable | Type | Default | Description | |----------|------|---------|-------------| | `API_PAGE_SIZE` | int | `20` | Default page size | | `API_MAX_PAGE_SIZE` | int | `100` | Maximum page size | | `API_VERSION` | string | `1.0.0` | API version string | ## JWT Configuration | Variable | Type | Default | Description | |----------|------|---------|-------------| | `JWT_ACCESS_TOKEN_LIFETIME_MINUTES` | int | `15` | Access token lifetime | | `JWT_REFRESH_TOKEN_LIFETIME_DAYS` | int | `7` | Refresh token lifetime | | `JWT_ISSUER` | string | `thrillwiki` | Token issuer | ## Cloudflare Images | Variable | Type | Default | Description | |----------|------|---------|-------------| | `CLOUDFLARE_IMAGES_ACCOUNT_ID` | string | `` | Account ID | | `CLOUDFLARE_IMAGES_API_TOKEN` | string | `` | API token | | `CLOUDFLARE_IMAGES_ACCOUNT_HASH` | string | `` | Account hash | | `CLOUDFLARE_IMAGES_WEBHOOK_SECRET` | string | `` | Webhook secret | | `CLOUDFLARE_IMAGES_DEFAULT_VARIANT` | string | `public` | Default variant | | `CLOUDFLARE_IMAGES_UPLOAD_TIMEOUT` | int | `300` | Upload timeout | | `CLOUDFLARE_IMAGES_CLEANUP_HOURS` | int | `24` | Cleanup interval | | `CLOUDFLARE_IMAGES_MAX_FILE_SIZE` | int | `10485760` | Max file size (bytes) | | `CLOUDFLARE_IMAGES_REQUIRE_SIGNED_URLS` | bool | `False` | Require signed URLs | ## Road Trip Service | Variable | Type | Default | Description | |----------|------|---------|-------------| | `ROADTRIP_USER_AGENT` | string | `ThrillWiki/1.0` | OSM API user agent | | `ROADTRIP_CACHE_TIMEOUT` | int | `86400` | Geocoding cache timeout | | `ROADTRIP_ROUTE_CACHE_TIMEOUT` | int | `21600` | Route cache timeout | | `ROADTRIP_MAX_REQUESTS_PER_SECOND` | int | `1` | Rate limit | | `ROADTRIP_REQUEST_TIMEOUT` | int | `10` | Request timeout | | `ROADTRIP_MAX_RETRIES` | int | `3` | Max retries | | `ROADTRIP_BACKOFF_FACTOR` | int | `2` | Retry backoff | ## Logging Configuration | Variable | Type | Default | Description | |----------|------|---------|-------------| | `LOG_DIR` | path | `logs` | Log directory | | `ROOT_LOG_LEVEL` | string | `INFO` | Root log level | | `DJANGO_LOG_LEVEL` | string | `WARNING` | Django log level | | `DB_LOG_LEVEL` | string | `WARNING` | Database log level | | `APP_LOG_LEVEL` | string | `INFO` | Application log level | | `PERFORMANCE_LOG_LEVEL` | string | `INFO` | Performance log level | | `QUERY_LOG_LEVEL` | string | `WARNING` | Query log level | | `NPLUSONE_LOG_LEVEL` | string | `WARNING` | N+1 detection level | | `REQUEST_LOG_LEVEL` | string | `INFO` | Request log level | | `CELERY_LOG_LEVEL` | string | `INFO` | Celery log level | | `CONSOLE_LOG_LEVEL` | string | `INFO` | Console output level | | `FILE_LOG_LEVEL` | string | `INFO` | File output level | | `FILE_LOG_FORMATTER` | string | `json` | File log format | ## Monitoring ### Sentry | Variable | Type | Default | Description | |----------|------|---------|-------------| | `SENTRY_DSN` | url | `` | Sentry DSN | | `SENTRY_ENVIRONMENT` | string | `production` | Sentry environment | | `SENTRY_TRACES_SAMPLE_RATE` | float | `0.1` | Trace sampling rate | ### Health Checks | Variable | Type | Default | Description | |----------|------|---------|-------------| | `HEALTH_CHECK_DISK_USAGE_MAX` | int | `90` | Max disk usage % | | `HEALTH_CHECK_MEMORY_MIN` | int | `100` | Min available memory MB | ## Feature Flags | Variable | Type | Default | Description | |----------|------|---------|-------------| | `ENABLE_DEBUG_TOOLBAR` | bool | `True` | Enable debug toolbar | | `ENABLE_SILK_PROFILER` | bool | `False` | Enable Silk profiler | | `TEMPLATES_ENABLED` | bool | `True` | Enable Django templates | | `AUTOCOMPLETE_BLOCK_UNAUTHENTICATED` | bool | `False` | Require auth for autocomplete | ## File Upload Settings | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FILE_UPLOAD_MAX_MEMORY_SIZE` | int | `2621440` | Max in-memory upload (bytes) | | `DATA_UPLOAD_MAX_MEMORY_SIZE` | int | `10485760` | Max request size (bytes) | | `DATA_UPLOAD_MAX_NUMBER_FIELDS` | int | `1000` | Max form fields | ## WhiteNoise Settings | Variable | Type | Default | Description | |----------|------|---------|-------------| | `WHITENOISE_COMPRESSION_QUALITY` | int | `90` | Compression quality | | `WHITENOISE_MAX_AGE` | int | `31536000` | Cache max-age | | `WHITENOISE_MANIFEST_STRICT` | bool | `False` | Strict manifest mode | ## Secret Management | Variable | Type | Default | Description | |----------|------|---------|-------------| | `SECRET_ROTATION_ENABLED` | bool | `False` | Enable rotation checks | | `SECRET_KEY_VERSION` | string | `1` | Current secret version | | `SECRET_EXPIRY_WARNING_DAYS` | int | `30` | Warning threshold | | `PASSWORD_MIN_LENGTH` | int | `8` | Minimum password length | ## Celery Configuration | Variable | Type | Default | Description | |----------|------|---------|-------------| | `CELERY_TASK_ALWAYS_EAGER` | bool | `False` | Run tasks synchronously | | `CELERY_TASK_EAGER_PROPAGATES` | bool | `False` | Propagate task errors | ## Third-Party Integrations | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FRONTEND_DOMAIN` | url | `https://thrillwiki.com` | Frontend URL | | `LOGIN_REDIRECT_URL` | path | `/` | Post-login redirect | | `ACCOUNT_LOGOUT_REDIRECT_URL` | path | `/` | Post-logout redirect | | `ACCOUNT_EMAIL_VERIFICATION` | string | `mandatory` | Email verification mode |