Django Media Files Production Checklist

A production checklist for Django user uploads: object storage instead of local disk, upload limits and validation, private versus public files, unique names, image processing in workers, CDN delivery, backups and versioning, and cleanup of orphaned files.

Problem statement

Media files (user uploads such as avatars, documents, and product images) behave very differently from static files: they change at runtime, belong to users, may be private, and must survive deploys and server replacements. Many production incidents involve them: uploads lost after redeploys, private documents reachable by guessing URLs, servers running out of disk, and slow pages from full-size images. This checklist covers a sound setup.

Media in productionUploadlimits, validationStorageobject storeAccesspublic or signedLifecyclebackup, cleanupUploads need durable storage, access control, and a lifecycle plan.
  1. Store media in object storage (S3, R2, GCS, Azure Blob) with django-storages, not on local disk or container filesystems.
  2. Limit upload size at the proxy and in Django, and validate file type by content, not extension.
  3. Decide per file type: public assets via CDN, private files via signed URLs or authorized views.
  4. Generate unique names (file_overwrite = False or UUID paths) and process images in Celery.
  5. Back up or version the bucket, and clean up orphaned files when models are deleted.

Checklist

Storage

  • STORAGES["default"] points at object storage via django-storages; MEDIA_ROOT on local disk only in development.
  • Separate buckets or prefixes for public and private media.
  • Bucket in the same region as the app; credentials through IAM roles or scoped keys.
  • ACLs disabled on new buckets (default_acl = None); access via bucket policies or CDN origin access.

Uploads

  • Size limits: Nginx client_max_body_size, DATA_UPLOAD_MAX_MEMORY_SIZE, and form-level validators.
  • FILE_UPLOAD_MAX_MEMORY_SIZE modest so large files spool to disk rather than memory.
  • Content validation (magic bytes, Pillow verify() for images), allowed type lists, and dimension limits.
  • Large files uploaded directly to storage with presigned URLs instead of through Django.
who may see the file?Who should access it?everyone (product images)Public via CDNlong cacheowner or staff onlySigned URLsshort expirystrict checks per requestDjango viewX-Accel-RedirectDecide access per file type before choosing storage settingsdjango-deployment.com
Never rely on unguessable file names for privacy.

Names, processing, and delivery

  • Unique names: file_overwrite = False or upload_to functions with UUIDs; sanitized original names.
  • Thumbnails and transcoding in Celery tasks, not during the upload request.
  • Responsive image sizes and modern formats (WebP, AVIF) served for public images.
  • CDN in front of public media with long cache headers; unique names make invalidation unnecessary.

Security

  • Private files not reachable without authorization; signed URL expiry appropriate (minutes to an hour).
  • User-uploaded HTML, SVG, and other active content served with Content-Disposition: attachment or from a separate domain.
  • Antivirus scanning for documents if your risk model requires it.
  • No user-controlled paths (SuspiciousFileOperation protections intact).

Lifecycle

  • Bucket versioning or replicated backups; restore tested.
  • Orphan cleanup: delete files when model instances or fields change (signals, or a periodic job comparing storage with the database).
  • Retention rules for temporary uploads and exports (lifecycle expiration).
  • Storage usage monitored and alerted.
from upload to deletion1Validate and storeobject storage, unique name2Process in Celerythumbnails, scan3Serve by access ruleCDN or signed URL4Back up or versionrestore tested5Delete with the recordorphan cleanupEach stage has its own failure mode to guard againstdjango-deployment.com
Deleting a model instance does not delete its files by default.

Explanation

Django's FileField stores a path in the database and the bytes in a storage backend. The default FileSystemStorage writes to local disk, which is lost when containers are replaced and not shared between servers. Object storage solves durability and sharing, and lets you separate public delivery (CDN) from private access (signed URLs). The remaining work is policy: limits, validation, naming, processing, and lifecycle, which Django leaves to you.

Edge cases / notes

  • Migrations of existing files. Moving from disk to object storage means syncing files with the same relative paths so database values remain valid.
  • Multiple storages. Use storage= on individual fields (Django 4.2+ supports callables) to put private and public files in different buckets.
  • Compliance. Personal data in uploads falls under privacy rules for access, export, and deletion.

Next steps

FAQ

Can media files stay on the server's disk?

Only on a single, persistent server with backups. Containers and multiple servers need object storage.

How do I serve private files?

With short-lived signed URLs from object storage, or through a Django view that checks permissions and uses X-Accel-Redirect.

Does Django delete files when a model is deleted?

No. Delete them explicitly in signals or with a periodic cleanup job.

Should static and media files share a bucket?

Use separate buckets or prefixes; they have different lifecycles and access rules.

Keep going