A WordPress migration that copies every file correctly can still break the site if permissions and ownership don’t come across cleanly. This is the single most common “why is my migrated site throwing 500 errors / white screens / can’t upload images” cause, and it’s almost never mentioned in migration plugin documentation because the plugin only controls what happens inside PHP — it can’t control what the destination server’s filesystem does with ownership.
The Correct Baseline Permissions
WordPress core’s own hardening guidance is the starting point: directories should be 755 and files should be 644. wp-config.php is the one file worth locking down further, to 600 or 640 depending on whether your PHP process and web server run as the same user — if they’re different users, 640 is required or PHP won’t be able to read it at all. The wp-content/uploads directory needs to remain writable by the PHP process user specifically, since that’s where media uploads, generated thumbnails, and some caching-plugin files land.
Issue 1: Ownership Mismatch After a File-Manager or FTP Copy
When you copy files via FTP/SFTP or a hosting panel’s file manager into a fresh account, the new files are usually owned by the account you connected as — which is correct on shared hosting where PHP also runs as that account. But on a VPS or Cloudways-style server, PHP may run as a distinct process user (commonly www-data, or an app-specific user like master_ntc on Cloudways). If the uploaded files are owned by your SFTP user but PHP runs as a different user, you’ll get “unable to create directory” errors on media upload even though the file permissions themselves look fine. The fix is a recursive chown to the correct PHP-process user, not a permissions change: chown -R www-data:www-data wp-content/uploads (adjust the user:group to whatever your host’s PHP-FPM pool actually runs as, which you can confirm from the pool config or by asking support).
Issue 2: Permissions Copied Too Loose (777) “to Make It Work”
A very common anti-pattern: something breaks after migration, someone runs chmod -R 777 on the whole install to make the error go away, and it works — because 777 makes every ownership problem disappear by making everything writable by everyone. This also makes every file on the site writable by any other account or process on a shared server, and it’s one of the most common vectors for mass file-injection malware on shared hosting. If 777 “fixes” a permissions error, that’s a signal the real problem is ownership, not permission bits — fix the ownership, then set permissions back to 755/644.
Issue 3: wp-config.php Readable by Other Accounts on Shared Hosting
On shared hosting, other cPanel accounts on the same server technically share the same filesystem in some configurations. A wp-config.php left at the default 644 (world-readable) can, in a misconfigured shared environment, be read by scripts running under a different account. This is rare on well-run hosts with proper user isolation (suPHP, PHP-FPM per-account, or CloudLinux CageFS), but it costs nothing to set wp-config.php to 600 or 640 as a migration-day habit, since only your own PHP process ever needs to read it.
Issue 4: SELinux Contexts on the Destination Server
This one is specific to CentOS/AlmaLinux/Rocky servers with SELinux enforcing, and it’s the permissions issue people burn the most time on because ls -l shows correct 755/644 permissions and correct ownership, yet Apache/Nginx still can’t write to uploads. That’s because SELinux enforces a separate security context (httpd_sys_rw_content_t for writable web content) independent of standard Unix permissions. Run ls -Z wp-content/uploads to check the context; if it’s not set to a writable httpd type, apply it with chcon -R -t httpd_sys_rw_content_t wp-content/uploads and make it persistent across relabels with semanage fcontext. Managed hosts (Kinsta, WP Engine, SiteGround, Cloudways) abstract this away entirely; it’s almost exclusively a raw-VPS/self-managed-server problem.
Issue 5: Plugin/Cache Directories Need Their Own Write Access
Caching plugins (WP Rocket, LiteSpeed Cache, W3 Total Cache) and some SEO/backup plugins write generated files outside the standard uploads path — commonly to wp-content/cache, wp-content/uploads/wp-rocket-config, or a plugin-specific advanced-cache drop-in at wp-content/advanced-cache.php. After migration these directories often don’t exist yet, or exist with the wrong owner, and the plugin fails silently rather than throwing a clear error. Deactivating and reactivating the caching plugin after fixing ownership usually forces it to recreate its directories correctly.
A Practical Fix Sequence
1) Confirm the correct PHP-process user for your destination host (ask support or check the PHP-FPM pool config). 2) Recursively chown the whole WordPress directory to that user:group. 3) Recursively set directories to 755 and files to 644: find . -type d -exec chmod 755 {} \; and find . -type f -exec chmod 644 {} \;. 4) Set wp-config.php to 600 or 640. 5) On SELinux systems, apply the correct httpd context to uploads and cache. 6) Deactivate/reactivate any caching plugin. 7) Try a real media upload as the final test — it exercises write access more reliably than just loading the homepage.
FAQ
Why does my migrated site load fine but image uploads fail? Almost always an ownership mismatch on wp-content/uploads — the PHP process user can’t write to a directory owned by whoever ran the file transfer.
Is 777 ever the correct permission on a production site? No. If 777 is the only thing that fixes an error, the underlying issue is ownership, and 777 just papers over it while creating a security hole.
Do managed hosts have these problems? Rarely, because their migration tooling (or their support team, for a white-glove migration) handles ownership and SELinux contexts for you. This is mostly a self-managed VPS and raw-server issue.
Verdict
Permissions problems after migration are almost always ownership problems wearing a permissions costume. Fix the owner:group of the whole install to match the actual PHP-process user first; only then does setting standard 755/644 permissions actually solve anything. On SELinux-enforcing servers, remember that correct Unix permissions and a correct SELinux context are two separate requirements — both have to be right.

![CATALYST INC AND THE THOMPSON GRAVING DOCK [QUEENS ISLAND BELFAST]-151284](https://hoststackpro.com/wp-content/uploads/2026/06/reviews-739-80x80.jpg)