WordPress hardening: how to change and protect the uploads directory#
The default WordPress uploads path (/wp-content/uploads/) is one of the first targets in automated attacks. Recon bots scan this predictable location for forgotten configs, plugin backups, and script execution vectors (RCE).
In this guide, I document the practical workflow I used to move the directory to a custom location and apply additional server-layer hardening (Apache/Nginx) so malicious code cannot be executed even if a bad file is uploaded.
The goal is straightforward: reduce attacker predictability, preserve integrity, and keep service availability under pressure.
1. Infrastructure planning and preparation#
Before touching code, prepare filesystem structure correctly.
Create the new directory#
Connect through SSH or FTP and create the target folder. From a security standpoint, avoid names containing "uploads" or "media".
- Example path:
/public_html/storage_core/data/
mkdir -p /public_html/storage_core/data/
Permission model (Linux)#
The web service user (www-data, apache, or cPanel account user) must have write permission.
chmod 755 /public_html/storage_core/data/
If needed, fix ownership:
chown -R accountuser:accountuser /public_html/storage_core/data/
2. WordPress core configuration#
Update uploads behavior in wp-config.php.
- Open
wp-config.phpin site root. - Insert this line before:
/ That's all, stop editing! Happy publishing. /
// Define new uploads path relative to WordPress root
define('UPLOADS', 'storage_core/data');
Technical note:UPLOADSmust be relative toABSPATH, without leading slash.
After saving, perform a test upload in Media Library to validate write behavior.
3. Data migration and database integrity#
Move existing files#
For sites with existing media, migrate old files:
- Source:
/wp-content/uploads/* - Destination:
/storage_core/data/
rsync -avh --progress /public_html/wp-content/uploads/ /public_html/storage_core/data/
Search and replace (URL integrity fix)#
This is the most missed step in real migrations: old media URLs remain stored in database fields (wp_posts, wp_postmeta).
- Find:
wp-content/uploads/ - Replace with:
storage_core/data/
WP-CLI approach:
wp search-replace 'wp-content/uploads/' 'storage_core/data/' --all-tables --precise --report-changed-only
Manual SQL fallback:
UPDATE wp_posts
SET post_content = REPLACE(post_content, 'wp-content/uploads/', 'storage_core/data/');
UPDATE wp_postmeta
SET meta_value = REPLACE(meta_value, 'wp-content/uploads/', 'storage_core/data/');
Production practice: always take a full backup before any global replace.
4. Server hardening: block script execution#
Moving path improves obscurity, but does not stop .php execution attempts.
Apache (.htaccess inside new directory)#
Create /public_html/storage_core/data/.htaccess:
# Block PHP execution in media directory
<Files *.php>
deny from all
</Files>
Nginx (server block)#
location /storage_core/data/ {
location ~ \.php$ {
deny all;
}
}
In hybrid stacks (Nginx proxy + Apache backend), enforce blocking in both layers to avoid internal-route bypass.
5. Operational post-hardening validation#
After migration and hardening rules, I run a strict validation cycle:
- New uploads from admin panel (
/wp-admin/upload.php) write into new path. - Existing media in old posts still renders correctly.
- Script execution attempt is blocked.
curl verification:
curl -I https://domain.tld/storage_core/data/test-image.webp
curl -I https://domain.tld/storage_core/data/probe.php
Expected result:
- image:
200 OKor304 Not Modified probe.php:403 Forbiddenor404 Not Found
I also review error logs to confirm no silent breakage on gallery/CDN plugins.
6. Common field failures and fixes#
Failure a: new images fail to upload#
Likely cause:
- wrong permissions
- wrong ownership
open_basedirblocking new path
Fix:
- Revalidate
chmod/chown. - Check
open_basedirinphp.inior FPM pool.
Failure b: old media links break#
Likely cause:
- incomplete
search-replace - stale cache on plugin/CDN
Fix:
- Re-run
search-replacewith report. - Purge app and CDN cache.
Failure c: direct origin IP still exposes uploads#
Likely cause:
- WAF/CDN active, but origin still reachable without local controls
Fix:
- Enforce local Apache/Nginx blocking.
- If reverse proxy is used, restrict origin access to trusted proxy ranges.
Strategic conclusion#
By changing the default directory and blocking script execution, you remove your WordPress from the common recon path used by generic botnets and reduce damage from automated upload payloads.
Final checklist:
- [ ] Full backup before editing
wp-config.php. - [ ] Correct permissions and ownership on new folder.
- [ ] New uploads writing to custom path.
- [ ]
search-replacecompleted for legacy URLs. - [ ] PHP execution blocked in new media path.
- [ ] HTTP behavior validated with
curl.
This is the operational discipline that separates fragile deployments from professional, resilient infrastructure.
Was this article helpful?
Leave a quick reaction to help prioritize future technical guides:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments