Skip to content

Going to Production 🚀

You've built your application. It runs locally. Now let's make it bulletproof for the real world.

Security Headers 🛡️

By default, Heaven does not set opinionated security headers. In production, you absolutely should.

Copy this snippet into your application:

async def security_headers(req, res, ctx):
    # Prevent MIME-Sniffing
    res.headers = 'X-Content-Type-Options', 'nosniff'

    # Clickjacking Protection (Same Origin Only)
    res.headers = 'X-Frame-Options', 'SAMEORIGIN'

    # Cross-Site Scripting Protection (Legacy Browsers)
    res.headers = 'X-XSS-Protection', '1; mode=block'

    # HTTP Strict Transport Security (HSTS) - Enforce HTTPS
    # Max-age: 1 year (31536000 seconds)
    # includeSubDomains: Apply to all subdomains
    # preload: Allow inclusion in browser HSTS preload list
    res.headers = 'Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'

    # Referrer Policy - Don't leak paths to other sites
    res.headers = 'Referrer-Policy', 'strict-origin-when-cross-origin'

# Apply globally
app.AFTER('/*', security_headers)

# OR

app.AFTER('/*', 'path.to.security_headers')

Content Security Policy (CSP) 👮

CSP is the heavy artillery of web security. It prevents script injection (XSS) by whitelisting exactly where content can come from.

Warning

A strict CSP can break your app if you use inline scripts or external CDNs not whitelisted. Test thoroughly!

Strict Default Example

This policy allows scripts/styles only from your own domain ('self').

async def csp_header(req, res, ctx):
    policy = (
        "default-src 'self'; "
        "script-src 'self'; "
        "style-src 'self'; "
        "img-src 'self' data:; " # Allow images from self and base64 data URIs
        "font-src 'self'; "
        "object-src 'none'; "     # Block Flash/Plugins
        "base-uri 'self'; "
        "form-action 'self'; "
        "frame-ancestors 'none';" # Prevent embedding (Clickjacking)
    )
    res.headers = 'Content-Security-Policy', policy

app.AFTER('/*', csp_header)

Secrets & Sessions 🔑

Never hardcode secrets in your code. Use Environment Variables.

import os
from heaven import Router

# Load from environment (or .env file using python-dotenv)
SECRET_KEY = os.getenv('SECRET_KEY')

if not SECRET_KEY:
    raise ValueError("No SECRET_KEY set for production application")

app = Router()

# Enable sessions with the secure key
app.sessions(secret_key=SECRET_KEY, max_age=86400, cookie_name='__Secure-Session')

Deployment 🏭

Running with Gunicorn (Process Manager)

While python main.py uses Uvicorn, in production you should use Gunicorn to manage multiple worker processes.

Install Gunicorn:

pip install gunicorn

Run Command:

# -w 4: Run 4 worker processes
# -k uvicorn.workers.UvicornWorker: Use Uvicorn for async
# main:app : The file `main.py` and the variable `app`
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app

Nginx Configuration (Reverse Proxy)

Put Nginx in front of Gunicorn to handle SSL termination and static files.

server {
    listen 80;
    server_name example.com;

    # Redirect HTTP to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Serve Static Assets directly
    # This bypasses Heaven for maximum performance
    location /public {
        alias /var/www/my-app/public;
        expires 30d;
        access_log off;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Websocket Support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}