Safeguard
Security Guides•

Django user uploads: a security checklist

How to accept file uploads in Django without serving a malicious file back: size limits, extensions, a separate domain and why image checks are not enough.

Safeguard Security Team
3 min read

For safe uploads in Django, limit request size at the web server, allow only known file extensions, never let the server execute uploaded files, and serve user content from a separate domain. Checking that a file is a valid image is not enough on its own.

Limit the size at the web server

The Django docs say to limit request body size at the web server, for example with LimitRequestBody on Apache, and not to rely only on DATA_UPLOAD_MAX_MEMORY_SIZE or FILE_UPLOAD_MAX_MEMORY_SIZE. Two details from the docs are worth knowing. Form submissions with files are not limited by DATA_UPLOAD_MAX_MEMORY_SIZE. And under ASGI, the whole body may be written to disk before validation runs.

Never execute what users upload

Treat everything under MEDIA_ROOT as untrusted. The docs tell you to disable script handlers such as mod_php for media directories, so an uploaded script is served as a file and never run.

Allowlist extensions

Decide what you accept, for example .png, .jpg and .pdf, and configure the web server to serve only those. A denylist of bad extensions always misses one.

Image validation is not a safety check

This one surprises people. The docs note that Pillow accepts an HTML file with a valid PNG header. So a file that passes image validation can still be rendered as HTML by a browser, depending on how it is served. Validation tells you the file is decodable. It does not tell you it is harmless.

Serve user content from a separate domain

Put MEDIA_URL on a distinct top level or second level domain, such as a dedicated user-content domain. The docs are specific: a subdomain like usercontent.example.com is not sufficient. The reason is that cookies and browser trust boundaries can be shared across subdomains of your main site. A separate registrable domain removes that.

Consider a CDN or cloud storage

The docs suggest a cloud service or CDN for serving static and user-uploaded files. That moves the risky traffic away from your application servers.

A compact checklist

  1. Request size limit at the web server.
  2. Extension allowlist at the web server.
  3. No script execution in media paths.
  4. Media on a separate registrable domain.
  5. Content type set explicitly when you serve downloads.
  6. Uploads stored outside the code directory.

Quick check

If Pillow opens the file, is it safe to serve?

No. A file can be a valid image and also contain HTML. Serve it from a separate domain with an allowlisted extension.

Is a media subdomain good enough?

The Django docs say no. Use a separate top level or second level domain.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.