Caddy as a proxy for Django

I’ve been experimenting with Caddy as a replacement for nginx as a proxy for my Django app in Docker containers. I’ve been very pleased so far with the tests I’ve been doing.

I’m somewhat hesitant deploying it to production for now and I was wondering if any of you kind souls could review my Caddyfile. Any suggestions or improvements are very welcome.

domain.tld {
    proxy / django:5000 {
        transparent
    }

    header / {
        # Don't show Caddy/Gunicorn as server header.
        -Server

        # Enable HTTP Strict Transport Security (HSTS) to force clients to always connect via HTTPS (do not use if only testing)
        # Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

        # Only send Referer header to same origin.
        # Django CSRF protection is incompatible with referrer policy set to none.
        Referrer-Policy "same-origin"

        # Enable cross-site filter (XSS) and tell browser to block detected attacks.
        X-XSS-Protection "1; mode=block"

        # Prevent some browsers from MIME-sniffing a response away from the declared Content-Type
        X-Content-Type-Options "nosniff"

        # Enable a strict content security policy.
        # Edit this if you need external sources on your site.
        # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
        Content-Security-Policy "connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src data: 'self'; object-src 'self'; style-src 'self'; script-src 'self';"

        # Don't allow resources to load within a frame/iframe.
        # This is handled with frame-ancestors 'none' in the content security policy. But not yet supported by older browsers.
        X-Frame-Options "DENY"
    }

    # The email address to use to generate a certificate with Letsencrypt.
    tls {{ cookiecutter.email }}
}

That configuration looks good aside from serving static assets. Django will not serve assets itself once DEBUG = False is set in your proj/settings.py, instead it expects your web server to do so. Assuming you’re serving your assets from /var/www/django-application/static and are pointing to them via /static at the root of your domain (the default), this is what I’d use:

domain.tld {
    root /var/www/django-application

    proxy / django:5000 {
        transparent
        except /static
    }

    header / {
        # Don't show Caddy/Gunicorn as server header.
        -Server

        # Enable HTTP Strict Transport Security (HSTS) to force clients to always connect via HTTPS (do not use if only testing)
        # Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

        # Only send Referer header to same origin.
        # Django CSRF protection is incompatible with referrer policy set to none.
        Referrer-Policy "same-origin"

        # Enable cross-site filter (XSS) and tell browser to block detected attacks.
        X-XSS-Protection "1; mode=block"

        # Prevent some browsers from MIME-sniffing a response away from the declared Content-Type
        X-Content-Type-Options "nosniff"

        # Enable a strict content security policy.
        # Edit this if you need external sources on your site.
        # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
        Content-Security-Policy "connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src data: 'self'; object-src 'self'; style-src 'self'; script-src 'self';"

        # Don't allow resources to load within a frame/iframe.
        # This is handled with frame-ancestors 'none' in the content security policy. But not yet supported by older browsers.
        X-Frame-Options "DENY"
    }

    # The email address to use to generate a certificate with Letsencrypt.
    tls {{ cookiecutter.email }}
}

Thanks for the feedback!
I’m currently serving the static files through Django with Whitenoise, but your suggestion serving them with Caddy is also an option. Do you think there’s any advantages of serving static files with Caddy instead of Whitenoise?

I consider this to be personal taste, so I’ll give you the facts.

WhiteNoise has about the same feature set as Caddy’s own static file handling. Caddy will automatically take care of serving gzip or Brotli-compressed versions of your files (and you can even add gzip to your server block to get automatic gzip). Caddy has a cors plugin for CORS support, and a plugin to take care of expires headers.

While I haven’t tried WhiteNoise, either WhiteNoise or Caddy are good at serving static files, but I’d recommend putting a CDN in front if performance is an important consideration.

1 Like

Thanks, I appreciate the input! I’ll probably do some tests with both and see what works best.

If anyone is interested I made a Django, Docker and Caddy starter template as a repo on GitHub. It’s propably not ready for production as I haven’t done many tests yet, but serves as an example of using Django with Caddy.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.