Fix django-storages Overwriting Files With the Same Name
Fix uploads replacing each other on S3 or R2 with django-storages: understand AWS_S3_FILE_OVERWRITE, generate unique upload_to paths, avoid cache-stale files at the same URL, and recover overwritten objects with bucket versioning.
Problem statement
Two users upload a file called invoice.pdf or image.jpg, and the second upload replaces the first. The first user's record now shows someone else's file. With local FileSystemStorage, Django would have renamed the second file to invoice_a8F3kQ2.pdf. With django-storages on S3, R2, or Google Cloud Storage, it silently overwrote it.
S3Storage sets file_overwrite = True by default (AWS_S3_FILE_OVERWRITE). Checking whether a key exists on object storage costs a network request per upload, so the library skips it and writes straight to the key. If your upload_to path is the same for two uploads, the second one wins.
- Generate unique keys in
upload_to, for exampleuploads/<uuid>/<filename>or a UUID filename. - Or set
AWS_S3_FILE_OVERWRITE = False(orfile_overwrite: FalseinSTORAGESoptions) to get Django's rename behaviour. - Enable bucket versioning so overwritten objects can be recovered.
- Never use user-supplied filenames as the whole key; sanitise and prefix them.
- Audit existing records for shared keys after fixing.
Step-by-step solution
1. Confirm the setting and the keys
from django.core.files.storage import default_storage
print(type(default_storage).__name__, getattr(default_storage, "file_overwrite", None))
Then look for duplicates among stored paths:
from django.db.models import Count
Document.objects.values("file").annotate(n=Count("id")).filter(n__gt=1)
Rows sharing a path point at the same object; only the last upload's content survives.
2. Generate unique paths
The most robust fix is to make collisions impossible:
import uuid, os
from django.utils.text import get_valid_filename
def document_path(instance, filename):
base, ext = os.path.splitext(get_valid_filename(filename))
return f"documents/{uuid.uuid4().hex}/{base[:80]}{ext.lower()}"
class Document(models.Model):
file = models.FileField(upload_to=document_path)
Keeping the original (sanitised) name in the path gives users a sensible download name, while the UUID directory makes the key unique. Per-user prefixes (documents/<user_id>/...) also help with lifecycle rules and permissions.
3. Or turn off overwriting
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {"bucket_name": "myapp-media", "file_overwrite": False},
},
# staticfiles ...
}
Django then calls exists() and appends a random suffix on collision, at the cost of one extra request per upload. This fixes new uploads but does not make paths unguessable.
4. Beware cached stale content
Even intentional overwrites (a user replacing their avatar at avatars/42.jpg) cause trouble: CDNs and browsers cache the old object at that URL. New keys per version avoid cache invalidation entirely; if you must keep a fixed key, set short Cache-Control or invalidate the CDN path.
5. Turn on versioning and recover
S3, R2, and GCS support object versioning. With it enabled, an overwritten object's previous versions remain:
aws s3api put-bucket-versioning --bucket myapp-media --versioning-configuration Status=Enabled
aws s3api list-object-versions --bucket myapp-media --prefix invoices/invoice.pdf
Copy an old version to a new unique key and point the affected record at it. Add a lifecycle rule that expires noncurrent versions after, say, 30 days to control cost.
Verification check: upload two files with the same name from different accounts; both records should have different keys and each should download its own content. Confirm versioning with aws s3api get-bucket-versioning --bucket myapp-media.
Explanation
Django's Storage.save() normally calls get_available_name(), which checks exists() and adds a random suffix if the name is taken. S3Storage overrides this when file_overwrite is true and returns the name unchanged, because object stores make existence checks slower and they are racy anyway: two concurrent uploads can both see "not exists" and both write. Generating unique keys in upload_to sidesteps both the cost and the race, which is why it is the recommended pattern for user uploads on object storage.
Edge cases / notes
- Static files use a separate storage; overwriting there is expected (manifest storage hashes names instead).
- Google Cloud Storage backend has the same
file_overwriteoption, also defaulting to true. - Long names. S3 keys can be up to 1,024 bytes, but
FileFielddefaults tomax_length=100; raise it if paths are long. - Private files. Unique keys are not access control; keep private buckets and use signed URLs.
Next steps
- Review the full S3 setup in Store Django Media Files on S3.
- Back media up properly with Backup Django Media Files.
FAQ
Why does django-storages overwrite files?
S3Storage defaults to file_overwrite = True to avoid an existence check per upload.
Should I set AWS_S3_FILE_OVERWRITE to False?
It works, but unique keys in upload_to are faster, race-free, and less guessable.
Can I recover an overwritten file?
Only if bucket versioning was enabled before the overwrite.
Does this affect static files?
Static files use their own storage where overwriting is normal; this issue concerns user uploads.