Every file you upload to WordPress lands in /wp-content/uploads/, and by default anyone who knows a file’s address can download it. For images that’s exactly what you want. For private documents, it’s a problem.
This guide shows how to check whether your files are exposed, how to block direct access on Apache and Nginx, and the one thing you need to sort out before you do.
Why uploaded files are public
When someone requests a page, WordPress runs and can check whether they’re logged in. When someone requests a file such as /wp-content/uploads/2026/10/staff-handbook.pdf, the web server usually sends it straight away without involving WordPress at all.
So login checks, membership plugins and private pages protect the page that links to a file, not the file itself. And because upload addresses follow a predictable year and month pattern, they are easy to guess.
Test whether your files are exposed
Copy the address of a file that should be private, open a private or incognito browser window, and paste it in.
- The file downloads or opens: it’s exposed.
- You get a 403 Forbidden error: direct access is blocked.
You can also check from the command line. Look for 403 in the first line of the response:
curl -I https://example.com/wp-content/uploads/private-documents/handbook.pdf
Before you block anything: don’t block the whole uploads folder
Your images, and often CSS and other files from themes and page builders, live in /wp-content/uploads/ too. Block the whole folder and your site breaks.
The safe approach is to keep private files in their own subfolder, block only that folder, and serve the files through WordPress code that checks permissions first. Without that last part, blocking the folder just makes the files unavailable to everyone, including the people who should have them.
Apache: use an .htaccess file
Create a file called .htaccess inside your private folder (for example /wp-content/uploads/private-documents/) containing:
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
The first block is for Apache 2.4, the second for older Apache 2.2 servers. Apache must be configured to allow these directives in .htaccess files (AllowOverride All covers it). Most shared hosts allow this.
Some servers, such as OpenLiteSpeed, only partly support .htaccess, so always run the test above afterwards.
Nginx: add a location block
Nginx doesn’t read .htaccess files at all, so the rule has to go in your site’s server configuration:
location ^~ /wp-content/uploads/private-documents/ { deny all; }
The ^~ matters. Many Nginx configs have rules for static files such as location ~* \.(pdf|jpg|png)$, and without ^~ those can take priority and serve the file anyway.
Test the configuration and reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
On managed hosting you usually can’t edit the server configuration yourself. Send your host the rule above and ask them to add it.
Serving the files once they’re blocked
With direct access blocked, links to your private files need to go through a PHP script that checks the visitor’s permission, then reads the file and sends it with the right headers. You can write this yourself, but it needs care: validating file paths so the script can’t be tricked into serving other files on the server, checking the real file type, and handling access rules and caching correctly.
How DocVault handles it
This is the approach DocVault takes for every document:
- Files are stored in their own folder,
/wp-content/uploads/trdv-documents/. - On Apache, DocVault writes the blocking rules for both Apache 2.2 and 2.4 to that folder when the plugin is activated or updated.
- Documents are served through DocVault’s own download and view links, which check access settings and expiry dates and validate the file path before sending anything.
- A Site Health test (Tools → Site Health) requests a real stored file as an anonymous visitor would. Only a 403 response counts as protected. If the files are exposed, it shows the exact Nginx rule for your site.
If you just need to protect documents, it saves writing and maintaining all of this yourself. Read more in the DocVault 1.5.6 release post.
Checklist
- Private files are in their own subfolder, not mixed in with images.
- That subfolder, and only that subfolder, is blocked on your server.
- Opening a private file’s address in a private window returns 403 Forbidden.
- Files reach the right people through code that checks permissions.
