The Problem
Users click on an attachment in Odoo — a PDF invoice, an uploaded document, an image — and nothing downloads. They get a broken file, a 404 error, an empty download, or the browser just spins. Attachment issues can affect every part of Odoo since attachments are used for documents, images, reports, and email attachments.
Common Symptoms
- Download button does nothing
- 404 Not Found when clicking attachment link
- File downloads but is empty (0 bytes)
- Image attachments show broken image icon
- Email attachments missing from sent emails
- "File not found" error in Odoo logs
How Odoo Stores Attachments
Odoo stores file attachments in two ways:
- Filestore: Files stored on disk at
~/.local/share/Odoo/filestore/dbname/ - Database: Files stored as base64 in the
ir_attachmenttable (older method or small files)
The ir_attachment record contains metadata, and the store_fname field points to the file on disk. If either the record or the file is missing, attachments break.
Causes and Fixes
1. Filestore Path Mismatch
Odoo cannot find the filestore directory, usually after migration, server change, or Docker volume misconfiguration.
Fix:
# Check where Odoo expects the filestore:
# In odoo.conf:
data_dir = /var/lib/odoo
# Filestore will be at:
# /var/lib/odoo/filestore/your_database_name/
# Verify the directory exists and has files:
ls -la /var/lib/odoo/filestore/your_database_name/
du -sh /var/lib/odoo/filestore/your_database_name/2. File Permission Issues
The Odoo process user cannot read files in the filestore directory.
Fix:
# Check Odoo process user:
ps aux | grep odoo
# Fix permissions:
chown -R odoo:odoo /var/lib/odoo/filestore/
chmod -R 755 /var/lib/odoo/filestore/3. Missing Files on Disk
The database record exists but the actual file was deleted from disk (manual cleanup, disk failure, or Docker volume issue).
Diagnose:
# Find attachments with missing files:
SELECT id, name, store_fname, res_model, res_id
FROM ir_attachment
WHERE store_fname IS NOT NULL
AND type = 'binary'
ORDER BY id DESC
LIMIT 20;
# Check if the file exists on disk:
# store_fname looks like: ab/ab1234567890abcdef
# Full path: /var/lib/odoo/filestore/dbname/ab/ab1234567890abcdefFix: If files are permanently lost, remove the broken attachment records. If you have backups, restore the missing files from the filestore backup.
4. Nginx/Proxy Not Serving Large Files
Large file downloads may timeout or fail due to proxy buffering limits.
Fix:
# In nginx config:
location / {
proxy_pass http://127.0.0.1:8069;
client_max_body_size 100m; # max upload size
proxy_read_timeout 600s; # allow long downloads
proxy_buffering off; # stream large files
proxy_request_buffering off; # stream large uploads
}5. S3/External Storage Misconfiguration
If using S3 or another external storage backend, credential or bucket issues prevent file access.
Fix:
- Verify S3 credentials are correct and not expired
- Check bucket permissions allow read access
- Test S3 connectivity from the Odoo server
- Check that the S3 module is properly configured
# Test S3 access from server:
aws s3 ls s3://your-bucket-name/filestore/ --region your-region6. Database Attachments Too Large
If ir_config_parameter key ir_attachment.location is set to db, all files are stored in PostgreSQL. Large files can cause database bloat and slow downloads.
Fix: Migrate to filestore storage:
# Set storage to filestore:
# Settings > Technical > System Parameters
# Key: ir_attachment.location
# Value: file
# Then run Odoo with --force-filestore to migrate existing attachments:
./odoo-bin -d mydb --stop-after-init7. CORS Issues for External Access
If accessing attachments from a different domain (e.g., a separate frontend app), CORS headers may block the download.
Fix: Add CORS headers for attachment routes in nginx:
location /web/content/ {
add_header Access-Control-Allow-Origin *;
proxy_pass http://127.0.0.1:8069;
}8. Attachment Access Rights
Odoo restricts attachment access based on the related record's permissions. If a user cannot access the parent record, they cannot download its attachments.
Fix:
- Check the user has read access to the parent record (res_model/res_id on ir_attachment)
- For public attachments, set
public = Trueon the attachment record - Check ir.model.access and record rules for the parent model
Diagnostic Steps
- Check Odoo logs for errors when downloading
- Verify the filestore directory exists and has correct permissions
- Query ir_attachment for the specific file's store_fname
- Check if the physical file exists on disk
- Test download with curl to isolate browser vs server issues
- Check nginx/proxy error logs for timeout or buffer issues