HyperSaaS
BackendDeployment

Production Settings

Security, storage, caching, and email configuration for production.

Security

SSL & HTTPS

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True

Cookies

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_NAME = "__Secure-sessionid"
SESSION_COOKIE_HTTPONLY = True

CSRF_COOKIE_SECURE = True
CSRF_COOKIE_NAME = "__Secure-csrftoken"
CSRF_COOKIE_HTTPONLY = True

HSTS

SECURE_HSTS_SECONDS = 60         # Increase to 518400 after verifying
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True

Other

X_FRAME_OPTIONS = "DENY"

Password Hashing

PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.Argon2PasswordHasher",   # Primary
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",   # Fallback
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
]

CORS

CORS_URLS_REGEX = r"^/api/.*$"  # Only API routes
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
    "https://www.yourdomain.com",
    "https://yourdomain.com",
]

Add your frontend domain to CORS_ALLOWED_ORIGINS in settings.

AWS / S3 Storage

Configuration

AWS_ACCESS_KEY_ID = env("DJANGO_AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = env("DJANGO_AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = env("DJANGO_AWS_STORAGE_BUCKET_NAME")
AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None)
AWS_S3_CUSTOM_DOMAIN = env("DJANGO_AWS_S3_CUSTOM_DOMAIN", default=None)
AWS_QUERYSTRING_AUTH = False
AWS_S3_MAX_MEMORY_SIZE = 100 * 1024 * 1024  # 100MB

Storage Backends

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {"location": "media"},
    },
    "staticfiles": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {"location": "static"},
    },
}

Static files and media are stored on S3. collectstatic uploads to the static/ prefix; media uploads go to media/. Document files use the separate presigned URL flow with the documents/ prefix.

Caching Headers

AWS_S3_OBJECT_PARAMETERS = {
    "CacheControl": f"max-age={60*60*24*7}, s-maxage={60*60*24*7}, must-revalidate"
}

Objects are cached for 7 days. Use a CloudFront distribution (AWS_S3_CUSTOM_DOMAIN) for global CDN.

Caching

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": REDIS_URL,
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "IGNORE_EXCEPTIONS": True,
        },
    }
}

Redis is used for both Celery broker and Django cache. IGNORE_EXCEPTIONS = True ensures the application doesn't crash if Redis is temporarily unavailable.

Email

Resend (Default)

EMAIL_BACKEND = "anymail.backends.resend.EmailBackend"
RESEND_API_KEY = env("RESEND_API_KEY")
DEFAULT_FROM_EMAIL = env("DJANGO_DEFAULT_FROM_EMAIL", default="support@yourdomain.com")

Alternative Providers

HyperSaaS supports multiple email backends via django-anymail:

ProviderBackend
Resendanymail.backends.resend.EmailBackend
Amazon SESanymail.backends.amazon_ses.EmailBackend
SendGridanymail.backends.sendgrid.EmailBackend
Console (dev)django.core.mail.backends.console.EmailBackend

Celery

Production Worker

celery -A config.celery_app worker -l INFO

Configuration

CELERY_BROKER_URL = REDIS_URL
CELERY_RESULT_BACKEND = REDIS_URL
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
CELERY_TASK_TIME_LIMIT = 5 * 60        # 5 minutes
CELERY_TASK_SOFT_TIME_LIMIT = 60       # 1 minute
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_WORKER_SEND_TASK_EVENTS = True

Beat Scheduler

Celery Beat uses DatabaseScheduler — periodic tasks are stored in the database and manageable via Django Admin.

Flower

Flower provides real-time Celery monitoring:

CELERY_FLOWER_USER = env("CELERY_FLOWER_USER")
CELERY_FLOWER_PASSWORD = env("CELERY_FLOWER_PASSWORD")

Protected with basic auth in production. Accessible on port 5555 via Traefik.

Environment Variables

Required

VariableDescription
DJANGO_SECRET_KEYDjango secret key
DJANGO_ALLOWED_HOSTSComma-separated allowed hosts
DATABASE_URLPostgreSQL connection string
REDIS_URLRedis connection string
DJANGO_AWS_ACCESS_KEY_IDAWS credentials
DJANGO_AWS_SECRET_ACCESS_KEYAWS credentials
DJANGO_AWS_STORAGE_BUCKET_NAMES3 bucket name
STRIPE_API_SECRETStripe secret key
STRIPE_WEBHOOK_SECRETStripe webhook signing secret
STRIPE_PUBLISHABLE_KEYStripe publishable key
OPENAI_API_KEYEmbeddings, search rewriting and reranking, titles, summaries, transcription
GOOGLE_API_KEYGemini, every plan's default chat model
FRONT_END_BASE_URLFrontend URL

Optional

VariableDefaultDescription
DJANGO_SECURE_SSL_REDIRECTTrueSSL redirect
SENTRY_DSN—Sentry error tracking
SENTRY_ENVIRONMENTproductionSentry environment tag
SENTRY_TRACES_SAMPLE_RATE0.0Performance monitoring sample rate
DJANGO_AWS_S3_REGION_NAME—AWS region
DJANGO_AWS_S3_CUSTOM_DOMAIN—CloudFront domain
RESEND_API_KEY—Email provider API key
CELERY_FLOWER_USER—Flower dashboard username
CELERY_FLOWER_PASSWORD—Flower dashboard password
ANTHROPIC_API_KEY—Anthropic LLM provider
SERPAPI_API_KEY—Web search tool
WEATHERAPI_COM_API_KEY—Weather tool
GOOGLE_MAPS_API_KEY—Location tool
LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY—Chat tracing in Langfuse
CELERY_WORKER_MAX_MEMORY_PER_CHILD—Replace worker processes that grow past this many KB; document parsing can use several GB
THROTTLE_*See Environment VariablesPer-user and per-account rate limits

Going Live

  1. Migrate: python manage.py migrate.
  2. Create the plans in live Stripe: with the live STRIPE_API_SECRET, run python manage.py sync_stripe_plans --dry-run, check the output, then run it without --dry-run. It also sets the billing portal to offer your plans. See Plans.
  3. Add the webhook endpoint in the live Stripe dashboard, https://your-domain.com/api/subscriptions/webhook (no trailing slash), with the subscription, product and price events, and set its signing secret as STRIPE_WEBHOOK_SECRET. See Webhooks.
  4. Block public access on the S3 bucket. Documents are only ever served through presigned URLs, so nothing needs the bucket to be public.
  5. Check the tool prices in TOOL_CALL_PRICES (workspaces/usage.py) match your SerpApi, Google Maps and WeatherAPI plans.
  6. Create an admin: python manage.py createsuperuser.

Upgrading an Existing Install

  • Run migrate, then sync_stripe_plans as above. Products from before plans existed are archived, and their subscribers count as Free until they choose a plan.
  • Remove STRIPE_ENDPOINT_SECRET and STRIPE_PRODUCT_ID; nothing reads them.
  • Run python manage.py delete_orphaned_document_files --dry-run, then without the flag, to remove stored files that no document points to any more.

On this page