Last updated: 2026-08-09
OSError: [Errno 2] No such file or directory:
'/opt/odoo/.local/share/Odoo/filestore/your_db/3f/3f2a1b...'
Short answer: the database restored correctly (attachment records exist with file hashes referencing the filestore), but the actual filestore directory either wasn't copied, was copied to the wrong path, or was copied for the wrong database name. Odoo stores file content separately from the database by design, both halves have to be restored together and match.
How Odoo's filestore actually works, briefly
Attachments (uploaded documents, generated PDFs, images) aren't stored as blobs inside PostgreSQL. Odoo stores them as files on disk, named by content hash, under filestore/<database_name>/. The database's ir.attachment table stores metadata and the hash-based filename, not the file content itself. A database restore alone never brings the actual files with it, that's a separate filesystem copy you have to do deliberately.
The content-hash naming is deliberate and worth understanding because it explains several behaviours people find surprising. Two identical files uploaded by two different users, even to two different records, are stored once on disk, Odoo just adds a second ir.attachment row pointing at the same hash. This is why filestore size doesn't grow linearly with attachment count on instances with a lot of duplicate content (the same PDF template regenerated repeatedly, for example), and it's also why you cannot simply delete a file from the filestore directory because one record doesn't need it anymore, other records may share that exact same hash.
Odoo's own database-manager export/import (used from the web UI's Database Manager, not pg_dump directly) does bundle the filestore automatically into a single .zip, which is why UI-based backups don't hit this problem nearly as often as manual pg_dump-based backup scripts do. If your backup process uses raw pg_dump for speed or automation reasons rather than the UI export, the filestore has to be handled as an explicit, separate step every time, and that's exactly the step that gets forgotten under time pressure during an urgent restore.
Causes, ranked
1. Filestore wasn't copied at all, only the database dump
The most common cause by far. Confirm:
ls -la /opt/odoo/.local/share/Odoo/filestore/
# or wherever your data_dir is configured
If the directory for your restored database name doesn't exist, or is empty, the filestore copy step was simply skipped.
2. Filestore copied under the wrong database name
The filestore directory name must exactly match the PostgreSQL database name. If you restored the dump into a database called production_restore but the filestore directory is still named production, Odoo looks in the wrong place.
sudo -u postgres psql -l | grep your_db_name
ls /opt/odoo/.local/share/Odoo/filestore/
3. Wrong ownership or permissions on the restored filestore directory
ls -la /opt/odoo/.local/share/Odoo/filestore/your_db/
# should be owned by the odoo system user, typically odoo:odoo
A filestore copied via scp or a manual archive extraction as root commonly ends up owned by root, which the Odoo service user can't read.
4. Partial copy, rsync/tar interrupted or excluded hidden subdirectories
The filestore uses a two-level hash-prefix directory structure (filestore/db/3f/3f2a1b...). A copy command that doesn't recurse fully, or was interrupted partway through a large transfer, leaves some hash-prefix subdirectories missing while the top-level directory looks present.
# compare file counts between source and destination
find /path/to/source/filestore/your_db -type f | wc -l
find /opt/odoo/.local/share/Odoo/filestore/your_db -type f | wc -l
If the counts differ, don't assume it's safe to just re-run the same copy command, especially with tar over an unstable connection, a partial extraction can leave files that appear present but are truncated. Compare total byte size as a second check:
du -sh /path/to/source/filestore/your_db
du -sh /opt/odoo/.local/share/Odoo/filestore/your_db
5. Restoring onto a different data_dir than the one Odoo is actually configured to use
Odoo's filestore root isn't always the default ~/.local/share/Odoo/filestore path, many production setups override it with data_dir in odoo.conf to point somewhere else entirely, a dedicated disk or volume mount. If you restored the filestore to the default path but the running config has a custom data_dir, Odoo will never look where you put the files.
grep data_dir /etc/odoo/odoo.conf
# confirm this is the actual path you restored into, not an assumption
6. Multi-company or multi-database filestore confusion
On a server hosting several databases, each gets its own filestore subdirectory under the shared data_dir root. A restore that recreates the database under a new name but reuses an old filestore directory by mistake (copy-pasting a path from a previous restore) silently attaches the wrong company's files, or none. Always double check the exact database name in the restored PostgreSQL instance against the exact filestore subdirectory name, character for character, rather than assuming they match because the restore "seemed to work."
The fix
# Correct filestore copy, preserving structure and using checksums
rsync -a --checksum /path/to/source/filestore/your_db/ /opt/odoo/.local/share/Odoo/filestore/your_db/
# Fix ownership after any manual copy
sudo chown -R odoo:odoo /opt/odoo/.local/share/Odoo/filestore/your_db/
# Restart Odoo after any filestore or data_dir change
sudo systemctl restart odoo
If the database name doesn't match the filestore directory name and you can't or don't want to rename the directory, set data_dir explicitly in your Odoo config and confirm the database name used in the restore matches what the filestore directory expects, rather than guessing at a workaround.
How to verify
Open a record with a known attachment (a product image, an invoice PDF) in the restored instance. A clean restore shows the image/file immediately. You can also verify at the filesystem level for a specific attachment:
# In an Odoo shell
sudo -u odoo /opt/odoo/venv/bin/python3 /opt/odoo/odoo-bin shell -d your_db --no-http
>>> att = env['ir.attachment'].search([], limit=1)
>>> att.store_fname
# confirm this file actually exists on disk at that hash path
Preventing this on your next restore
The reliable pattern is to always restore the database dump and the filestore archive as a single paired operation, taken from the same backup run, never mixing a database dump from one date with a filestore snapshot from another. If your backup automation keeps them as separate files (see our backup automation post for one working pattern), name them with a shared timestamp so it's obvious at restore time which pairs with which, rather than guessing from file modification dates weeks later.
Still stuck
If files exist on disk with correct ownership and paths but attachments still fail to load in the UI, work through these:
- A reverse proxy or CDN static-file rule intercepting attachment requests before they reach Odoo. Check whether your Nginx config has a
locationblock matching attachment URL patterns that serves from a local path instead of proxying to Odoo, that's a separate, less common Nginx routing issue, most likely on servers where someone previously tried to offload static file serving to Nginx directly. - Browser caching a broken result. Hard refresh or test in a private window before concluding the fix didn't work, browsers cache broken image responses more aggressively than most people expect.
- The attachment record's
store_fnamepointing at a completely different hash than any file on disk. This happens if the database was restored from one backup and the filestore from a different, unrelated backup, the fix isn't a filesystem permissions or path issue at that point, it's re-pairing the correct database dump with its matching filestore snapshot.
Sources: Odoo documentation.