Some WordPress outages don’t look like outages. The homepage loads, marketing is happy, and then an editor tries to upload an image and gets a cheery “Unable to create directory.” Or auto-updates stall forever. Or the site flips to a 403 after a “quick permission fix” someone ran at 2 a.m.
File permissions are one of those problems that start as a papercut and end as a postmortem. They’re also one of the few WordPress issues where you can be technically correct and still break production. Let’s do the boring parts right: what 755/644 should be, why those numbers exist, and how to diagnose permission errors fast without turning your web root into a public writeable sandbox.
What 755/644 actually mean (and why WordPress people keep repeating them)
When people say “set directories to 755 and files to 644,” they’re not reciting a spell. They’re describing a safe baseline for a typical Linux web server where:
- Directories need the execute bit to be traversable (yes, execute for directories is “can enter”).
- Files generally need to be readable by the web server but not writable by everybody.
- The owner can edit content; the web server can read it; random users on the same box can’t modify it.
Permissions in one paragraph you can actually use
Unix permissions are three sets of bits: owner, group, others. Each set can have r (read), w (write), x (execute). The numeric form is octal: r=4, w=2, x=1. Add them and you get a digit. So 7 means rwx, 6 means rw-, 5 means r-x, 4 means r–.
So why 755 for directories?
755 is:
- Owner: 7 (rwx) – can list, create, delete, traverse.
- Group: 5 (r-x) – can list and traverse, but can’t modify.
- Others: 5 (r-x) – same.
For directories, “read” lets you list names, “execute” lets you traverse into it (and access files if you know the names), and “write” lets you create/delete/rename entries. A directory that is 644 is usually broken because it’s missing execute, so you can’t enter it even if you can “read” it.
And why 644 for files?
644 is:
- Owner: 6 (rw-) – can edit.
- Group: 4 (r–) – can read.
- Others: 4 (r–) – can read.
This is sane for PHP files, images, CSS, JS. The web server needs to read them. It does not need to edit them in place.
The part people forget: ownership and the process user
Permissions are only half the story. The other half is: which Unix user is the web server (or PHP-FPM) running as? If your WordPress directory is owned by deploy:deploy but PHP-FPM runs as www-data, then 755/644 won’t magically allow uploads unless the writeable paths are owned or group-writeable in a controlled way.
Opinionated guidance: don’t solve this by making everything 777. That’s not fixing; it’s replacing a lock with a sticky note that says “please don’t steal.”
Joke #1: Setting a web root to 777 is like putting your house key under the doormat, then tweeting the doormat’s GPS coordinates.
What WordPress really needs to write to (and what it should never write to)
WordPress is an application that wants to modify itself. That’s convenient in shared hosting. In production, it’s a risk decision.
Paths WordPress commonly needs to write
- wp-content/uploads/ – media uploads, image resizing, generated files.
- wp-content/cache/ (or plugin-specific cache dirs) – depending on caching plugins.
- wp-content/upgrade/ – temporary during updates.
- wp-content/languages/ – language packs (sometimes).
- wp-content/plugins/ and wp-content/themes/ – only if you allow in-place installs/updates from the admin UI.
Paths WordPress should not be able to write in a well-run setup
- Core files like
wp-admin/,wp-includes/. - Server configuration like Nginx/Apache configs (obvious, but I’ve seen things).
- Anything outside the site root (unless you explicitly designed it that way).
The “FS_METHOD” reality check
If WordPress can’t write where it wants, it may ask for FTP credentials or fail updates. That’s WordPress trying to compensate for hosting environments. On a properly managed server, you usually choose one of these operating modes:
- Immutable code + deploy pipeline: WordPress cannot update core/plugins/themes; updates happen via CI/CD (best for reliability).
- Writable wp-content only: let admins manage plugins/themes; keep core read-only to the web user (reasonable compromise).
- Everything writable by web user: fastest path to regret (avoid).
One quote that operations people live by
“Hope is not a strategy.” — paraphrased idea commonly attributed to reliability and operations circles
Permissions are where hope goes to die. Model the users, model the writes, then apply the minimum privilege that still lets the business work.
Fast diagnosis playbook (check 1/2/3)
If you’re in the middle of an incident, don’t start with recursive chmods. Start with the smallest set of checks that identify the failing actor (user/process) and the failing path.
1) Identify the exact failing path and syscall-level symptom
- Look for “Permission denied,” “Operation not permitted,” “Read-only file system,” “No such file or directory,” “SELinux is preventing,” or “open_basedir restriction.” These are different problems wearing the same hat.
- Check web server and PHP logs for the path.
2) Identify which Unix user is doing the write
- PHP-FPM pool user? Apache module user? Nginx worker user? CLI user running WP-CLI?
- Confirm with service config and live process list.
3) Check ownership + mode + mount flags + MAC controls
- Ownership and mode:
namei -landstaton the full path. - Mount flags:
ro,noexec,nosuid, and container overlay quirks. - SELinux/AppArmor: if enabled, DAC permissions can look fine but still fail.
If you do those three checks, you usually get to a real fix instead of a permission whack-a-mole.
Practical tasks: commands, outputs, decisions (12+)
Every task below includes: a command, what typical output means, and the decision you make. These are written for Debian/Ubuntu-ish systems using www-data; adjust for your distro (apache, nginx, etc.).
Task 1: Find the web/PHP user actually serving WordPress
cr0x@server:~$ ps -eo user,comm | egrep 'php-fpm|apache2|httpd|nginx' | head
root nginx
www-data nginx
root php-fpm8.2
www-data php-fpm8.2
What it means: Nginx workers and PHP-FPM are running as www-data (good; consistent).
Decision: Files/dirs that must be writable by the app should be writable by www-data (via ownership or group strategy). If you see mixed users (e.g., Nginx as nginx, PHP-FPM as www-data), pick one model and align.
Task 2: Confirm PHP-FPM pool user and group (the source of truth)
cr0x@server:~$ sudo grep -R "^\s*user\s*=" /etc/php/*/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/www.conf:user = www-data
cr0x@server:~$ sudo grep -R "^\s*group\s*=" /etc/php/*/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/www.conf:group = www-data
What it means: PHP scripts run as www-data:www-data.
Decision: Any directory WordPress needs to write to must be writable by www-data, directly or via group permissions/ACLs.
Task 3: Verify which filesystem path WordPress is using (docroot)
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R "root " -n | head
cr0x@server:~$ sudo grep -R "root " -n /etc/nginx/sites-enabled | head
/etc/nginx/sites-enabled/example.conf:12: root /var/www/example/current/public;
What it means: Your actual docroot is /var/www/example/current/public, not whatever you vaguely remember from six months ago.
Decision: Run all permission checks against the real path, not an old symlink target or a staging directory.
Task 4: Inspect permissions on the failing directory (quick glance)
cr0x@server:~$ ls -ld /var/www/example/current/public/wp-content/uploads
drwxr-xr-x 2 deploy deploy 4096 Dec 27 09:14 /var/www/example/current/public/wp-content/uploads
What it means: Directory is 755 but owned by deploy:deploy. The web user www-data is “other” here, so it cannot write.
Decision: Change ownership or adopt a group/ACL strategy so www-data can write only where needed.
Task 5: Check the full path traversal permissions with namei (catches the parent directory gotcha)
cr0x@server:~$ namei -l /var/www/example/current/public/wp-content/uploads
f: /var/www/example/current/public/wp-content/uploads
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-x--- deploy deploy example
lrwxrwxrwx deploy deploy current -> releases/2025-12-27_0910
drwxr-xr-x deploy deploy releases
drwxr-xr-x deploy deploy 2025-12-27_0910
drwxr-xr-x deploy deploy public
drwxr-xr-x deploy deploy wp-content
drwxr-xr-x deploy deploy uploads
What it means: The directory /var/www/example is 750 (drwxr-x---) owned by deploy:deploy. Even if you fixed uploads, www-data might not traverse into the tree.
Decision: Ensure every parent directory is traversable by the user that needs access (often via group execute). This is the hidden cause of “but uploads is 777 and it still fails.”
Task 6: Check file mode and ownership precisely with stat
cr0x@server:~$ stat -c '%A %a %U:%G %n' /var/www/example/current/public/wp-config.php
-rw-r----- 640 deploy deploy /var/www/example/current/public/wp-config.php
What it means: wp-config.php is 640 and not readable by “others”. If PHP runs as www-data and is not in the deploy group, WordPress may fail with a 500.
Decision: Either make it group-readable and put PHP user in that group, or change ownership appropriately. Don’t just chmod 644 blindly if you can keep it tighter with the right group.
Task 7: Confirm group memberships (does www-data belong to the deploy group?)
cr0x@server:~$ id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
What it means: www-data is not in deploy. Group-based reads/writes won’t work until you adjust groups or use ACLs.
Decision: Choose: add www-data to a shared group (common on single-tenant servers), or use ACLs for just the needed paths (cleaner in multi-tenant setups).
Task 8: Fix writable path ownership safely (uploads only)
cr0x@server:~$ sudo chown -R www-data:www-data /var/www/example/current/public/wp-content/uploads
cr0x@server:~$ ls -ld /var/www/example/current/public/wp-content/uploads
drwxr-xr-x 2 www-data www-data 4096 Dec 27 09:14 /var/www/example/current/public/wp-content/uploads
What it means: Now the PHP user can write to uploads without making your entire codebase mutable.
Decision: If you use deployment tools that rotate releases, consider a shared persistent uploads directory and symlink it, otherwise the next deploy may reset ownership.
Task 9: Set baseline perms (dirs 755, files 644) without clobbering special cases
cr0x@server:~$ sudo find /var/www/example/current/public -type d -print0 | sudo xargs -0 chmod 755
cr0x@server:~$ sudo find /var/www/example/current/public -type f -print0 | sudo xargs -0 chmod 644
What it means: Standardizes modes. If this “fixes” the site, you probably had someone run a recursive chmod to something wrong like 600 or 700.
Decision: Immediately re-tighten sensitive files (like wp-config.php) after the baseline, and ensure writable directories stay writable for the right user. Baselines are scaffolding, not architecture.
Task 10: Detect world-writable files and directories (security tripwire)
cr0x@server:~$ sudo find /var/www/example/current/public -perm -0002 -ls | head
412310 4 drwxrwxrwx 2 www-data www-data 4096 Dec 27 09:15 /var/www/example/current/public/wp-content/cache
What it means: Something is world-writable (...rwx for others). Sometimes plugins do this. Sometimes humans do it in panic.
Decision: Remove world-writable unless you have an explicit reason. Prefer 775 with controlled group membership or ACLs.
Task 11: Check for immutable bit (the “chmod did nothing” mystery)
cr0x@server:~$ lsattr -d /var/www/example/current/public/wp-content/uploads
-------------e------- /var/www/example/current/public/wp-content/uploads
What it means: No immutable flag. If you see an i in the attributes, the kernel will block changes even for root.
Decision: If immutable is set unintentionally, remove it for the specific path (carefully), not the whole tree.
Task 12: Confirm the filesystem isn’t mounted read-only
cr0x@server:~$ findmnt -no TARGET,OPTIONS /var/www
/var/www rw,relatime
What it means: The mount is read-write. If it were ro, WordPress could never upload regardless of chmod/chown.
Decision: If you see ro, stop permission fiddling. Fix the storage issue: remount, resolve filesystem errors, check your container overlay, or investigate why the host remounted read-only.
Task 13: Check SELinux status (permissions can be “correct” and still fail)
cr0x@server:~$ sestatus
SELinux status: disabled
What it means: SELinux is disabled; you can ignore contexts.
Decision: If SELinux is enforcing, you must check contexts on writable directories (and fix them) rather than broadening Unix modes.
Task 14: AppArmor status (common on Ubuntu)
cr0x@server:~$ sudo aa-status | head
apparmor module is loaded.
10 profiles are loaded.
What it means: AppArmor is active. A profile could be denying PHP write access even if Unix permissions allow it.
Decision: If you suspect AppArmor, check denial logs and adjust the profile for the specific write paths. Don’t “fix” it with 777.
Task 15: Reproduce the write as the PHP user (tests the real permission boundary)
cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/example/current/public/wp-content/uploads/.permtest && echo ok'
ok
What it means: The app user can write. If this fails with “Permission denied,” your problem is ownership/mode/ACL/SELinux/mount flags.
Decision: Use this to validate your fix before asking editors to retry uploads.
Task 16: Inspect ACLs (when permissions look fine but aren’t)
cr0x@server:~$ getfacl -p /var/www/example/current/public/wp-content/uploads | sed -n '1,12p'
# file: /var/www/example/current/public/wp-content/uploads
# owner: www-data
# group: www-data
user::rwx
group::r-x
other::r-x
What it means: No extra ACL rules. If you see ACL entries, they can grant or restrict access beyond the basic mode bits.
Decision: In multi-user environments, ACLs are often the cleanest way to grant www-data write access without giving it ownership.
Task 17: Find recently changed permissions (the “who touched this?” question)
cr0x@server:~$ sudo find /var/www/example/current/public -printf '%TY-%Tm-%Td %TH:%TM %m %u:%g %p\n' | sort -r | head
2025-12-27 09:16 755 www-data:www-data /var/www/example/current/public/wp-content/uploads
2025-12-27 09:12 640 deploy:deploy /var/www/example/current/public/wp-config.php
What it means: You can see what changed recently, which helps correlate to deploys or “quick fixes.”
Decision: If a deploy changed ownership of shared directories, update your deploy scripts. Permission drift is an automation bug, not a human training issue.
Common mistakes: symptom → root cause → fix
1) “Unable to create directory wp-content/uploads/…”
Symptom: Media uploads fail; WordPress dashboard complains about directory creation.
Root cause: uploads or a parent directory isn’t writable/traversable by the PHP user; sometimes the parent is 750 and blocks traversal.
Fix: Check with namei -l. Ensure the full path is traversable (x bit) and the uploads dir is writable by PHP user via ownership, group, or ACL. Avoid world-writable.
2) Plugin/theme install prompts for FTP credentials
Symptom: WordPress asks for FTP even though you’re on your own server.
Root cause: WordPress can’t write to wp-content/plugins or wp-content/themes, so it falls back to FTP-style methods.
Fix: Decide your operating model. If you allow in-dashboard installs, make those directories writable by PHP user (preferably group-write with a shared group). If you don’t, disable filesystem writes and update via deploy pipeline.
3) 403 Forbidden after “tightening permissions”
Symptom: Entire site returns 403; static assets may also fail.
Root cause: Directories set to 644 or 600, removing execute bit so the web server can’t traverse.
Fix: Directories should generally be 755 (or 750 when you explicitly control group access). Use find -type d -exec chmod 755 and validate with namei -l.
4) 500 Internal Server Error, logs show “failed to open stream: Permission denied” for wp-config.php
Symptom: PHP can’t read config; site whitescreens.
Root cause: wp-config.php is 600 or 640 with the wrong group; PHP user lacks read permission.
Fix: Make it readable by the PHP user via group strategy or owner change. Avoid making it world-readable if you can; 640 with correct group is fine.
5) “Operation not permitted” when running chown/chmod
Symptom: Even root can’t change ownership, or changes revert.
Root cause: Immutable bit set, or the path is on a filesystem that forbids chown (some network mounts), or you’re inside a container with mapped IDs.
Fix: Check lsattr, mount type, and container runtime UID mapping. Fix the underlying constraint; don’t brute-force.
6) Everything looks correct, but writes still fail
Symptom: Modes/ownership seem right; touch as root works; WordPress still can’t write.
Root cause: SELinux/AppArmor denials, read-only remount, or PHP-FPM pool running under a different user than expected.
Fix: Confirm pool user, check MAC system status/logs, verify mount options. Test with sudo -u www-data touch ... to validate the actual actor.
Three corporate-world mini-stories from the permission trenches
Mini-story 1: The incident caused by a wrong assumption
They’d inherited a WordPress install that “always worked.” A new engineer got tasked with moving it onto a freshly built VM. Nginx in front, PHP-FPM behind, modern TLS. The migration checklist was tidy: rsync files, import database, update DNS. It launched on schedule.
Two hours later the content team started uploading images for a campaign. Every upload failed. The on-call engineer did the usual: chmod -R 755 on directories, chmod -R 644 on files, then retried. Still broken. Someone suggested the nuclear option: chmod -R 777 wp-content. Uploads worked instantly. Everyone exhaled.
Two days later, a security scan flagged the host as compromised. The postmortem was unpleasant but educational. The wrong assumption wasn’t “755/644 is enough.” It was “the app user can traverse the path.” The docroot lived under /srv/sites/clientA which was 750 owned by deploy:deploy. So the PHP user couldn’t even reach wp-content reliably. The 777 change masked the traversal problem and turned the rest of the tree into a writable playground.
They fixed it properly by setting a shared group for the site, making only the needed subdirectories group-writable, and validating traversal with namei -l. The compromise was simple: let WordPress write to uploads and cache, but keep code immutable. The security team stopped calling. The marketing team never knew what almost happened, which is how it should be.
Mini-story 2: The optimization that backfired
A different company wanted “faster deploys.” Their answer was to mount wp-content/uploads on a network filesystem so multiple app servers could share media. Reasonable goal, risky implementation. They used a mount that didn’t behave like a local ext4 volume: ownership mapping was inconsistent, and chmod/chown semantics were not what the team expected.
The first sign of trouble: image resizing intermittently failed. Thumbnails would appear for some uploads but not others. Editors blamed WordPress. Engineers blamed the cache plugin. Everyone blamed everyone’s browser. The logs showed Permission denied on files that looked writeable when checked on one host, but not on another.
The “optimization” was also a reliability regression. Their network filesystem setup had subtle permission translation differences between nodes, and a deploy sometimes created directories with a default umask that made them non-writable to the PHP user on half the fleet. It wasn’t a constant failure. It was worse: it was intermittent.
The eventual fix was not “chmod harder.” They standardized the mount options, enforced directory creation permissions using setgid on the shared directory (so new files inherited the correct group), and added a health check that performed a write test as the PHP user on every node. They also documented the operational model: if you’re going to share uploads, you must treat it like a storage system, not a folder.
Mini-story 3: The boring but correct practice that saved the day
A retail organization ran WordPress as part of a larger stack. Nothing fancy. The team did one deeply unsexy thing: they made their WordPress code deploy read-only, and they stored uploads outside the release directory on a persistent path. Deploys created a symlink from public/wp-content/uploads to /srv/wordpress-uploads/site1.
Then a plugin vulnerability hit the news. Not a theoretical one: the kind where bots spray the internet looking for writable PHP files. They were already patched in staging, but production patching had to wait for a maintenance window because the marketing team was mid-campaign (it’s always mid-campaign).
During that window, bots did what bots do. They found the login page and tried. They found plugin endpoints and tried. They managed to hit the vulnerable code path but couldn’t persist a web shell where they wanted because the code directories were not writable by the PHP user. Uploads were writable, but executing PHP from uploads was blocked at the web server level.
The incident response was calm. No scramble to diff thousands of files, no “is this file supposed to exist?” archaeology. They patched, rotated secrets, and moved on. The boring practice—separating mutable and immutable data—turned a potential compromise into background noise.
Joke #2: Permissions are like a dress code—too strict and nobody gets work done, too loose and suddenly it’s “casual spear-phishing Friday.”
Interesting facts and historical context (because Unix is old and petty)
- The execute bit on directories predates most web software. It means “search” (traverse), not “run.” Confusing on purpose, apparently.
- Octal notation became the human shorthand because permission bits are naturally grouped in threes. It’s not arbitrary; it’s the number system that fits the data.
- “Sticky bit” on directories (like
/tmp) exists because multi-user systems needed shared writable spaces without letting users delete each other’s files. - Setgid on directories is a classic collaboration trick: new files inherit the directory’s group. It’s older than most CI/CD pipelines and often more reliable.
- Umask is why two servers with the same chmod commands can still produce different permissions for newly created files. It’s a process-level default mask, not a filesystem property.
- Apache’s historical model of many processes influenced a lot of legacy “just chown it to www-data” advice. PHP-FPM and container runtimes changed the shape of the problem.
- NFS and permission semantics have burned countless teams. Some mounts map root to nobody (root-squash), and some don’t support Unix ownership the way you assume.
- SELinux was designed because discretionary access control (Unix perms) can’t express “this process may write only to these labeled directories.” WordPress often trips that wire.
- WordPress’s self-updating behavior is a product of its shared-hosting roots. Modern production practice often disables in-place mutation and treats code as artifacts.
Checklists / step-by-step plan
Step-by-step: fix permission errors without making everything writable
- Confirm the actor: determine the runtime user (
www-data,nginx, etc.) viapsand PHP-FPM pool config. - Find the failing path: from WordPress error, PHP logs, or web server logs. Don’t guess.
- Validate traversal: run
namei -lon the failing path and look for a parent directory that blocks execute permission. - Decide your model:
- Immutable code + CI/CD updates, or
- Writable
wp-content(or parts of it), or - Fully mutable (don’t).
- Fix ownership for writable paths only: typically
wp-content/uploadsand maybe cache directories. Usechown -R www-data:www-dataon those paths. - Apply baseline modes: directories 755, files 644 for the code tree. Tighten special files afterward (
wp-config.php). - Run a write test as the PHP user:
sudo -u www-data touch ...in the specific directory that failed. - Check mount/MAC: ensure the filesystem is
rw; check SELinux/AppArmor if applicable. - Prevent drift: bake the desired ownership/mode into deploy automation. If a deploy resets perms, you’ll be back here next week.
Checklist: hardened-but-functional WordPress permissions (typical single-site VM)
- Code directories: owned by
deploy(or root), readable by web user, not writable by web user. wp-content/uploads: writable by web user; preferably a separate persistent path with controlled permissions.- No world-writable directories in the docroot.
- Optionally block PHP execution in uploads at the web server level (not a permission bit, but matters).
- Use group strategy or ACLs rather than “everything owned by www-data” if humans need to edit files.
Checklist: multi-server or shared storage (where things get spicy)
- Standardize the runtime user across nodes.
- Use setgid on shared directories to preserve group.
- Validate mount options and ownership mapping.
- Add a node-level “uploads write test” to your health checks.
- Prefer object storage for uploads if your architecture supports it; shared POSIX mounts are operationally expensive.
FAQ
1) Are 755 and 644 always correct for WordPress?
No. They’re a safe baseline for readability and traversal. Correctness depends on ownership, the PHP user, and which directories must be writable. In some setups, 750/640 with proper groups is better.
2) Why do directories need execute permission?
Because execute on a directory means “can traverse/search it.” Without it, the process can’t access files inside, even if it can read the directory listing.
3) Should wp-config.php be 644?
Often it works, but it’s not the best default. Prefer 640 with the correct group so only the owner and the web/PHP group can read it. If your server is single-tenant and you trust local users less than the web process, keep it tighter.
4) Is it safe to chown everything to www-data?
It’s functional and common, and it’s also an easy way to let a compromised web process modify your application code. If you care about containment, keep code owned by a deploy user and only grant write access to uploads/cache.
5) WordPress updates fail unless I make plugins/themes writable. What should I do?
Pick a policy. If you want click-to-update, you must allow writes to plugin/theme directories (ideally via group-write/ACLs, not 777). If you want a safer system, disable in-place updates and deploy plugin/theme changes through CI/CD.
6) What’s the difference between “Permission denied” and “Operation not permitted”?
“Permission denied” usually means the current user lacks rights (mode/ACL/MAC). “Operation not permitted” often indicates a stronger restriction: immutable attribute, filesystem rules, or container/user namespace constraints.
7) My perms are correct but uploads still fail. What else could it be?
Common culprits: filesystem mounted read-only, SELinux/AppArmor denial, wrong PHP-FPM pool user, disk full (writes fail), or a parent directory blocking traversal. Check those before changing modes again.
8) Do I need 775 instead of 755?
Only if you intentionally use group-based collaboration (e.g., deploy user and PHP user share a group). 775 grants group write. That’s fine when the group membership is controlled and intentional.
9) What about 700 for directories?
700 is great for private directories that only the owner should traverse. It’s terrible for a web root unless the web process is the owner. Use 750 if you want “owner+group only.”
10) How do I stop permission drift after deploys?
Make it an automation responsibility. Ensure deploy scripts set ownership/modes on release creation, preserve persistent directories (uploads), and verify with a write test as the runtime user.
Conclusion: next steps that won’t wake you at 3 a.m.
Most WordPress permission incidents are not about the magic numbers. They’re about the mismatch between who is writing and where you accidentally blocked traversal or ownership. 755/644 are good defaults, but they’re not a strategy.
Do this next
- Write down your operating model: can WordPress update itself or not? Decide, then enforce with permissions.
- Separate mutable from immutable: keep uploads (and maybe cache) writable; keep core and most code read-only to the web user.
- Automate the desired state: deploy scripts should set ownership/mode and verify them. Manual chmod is how you get permission folklore.
- Add a health check: a simple write test as the PHP user to the uploads directory catches drift before editors do.
If you follow that, 755/644 becomes what it should have always been: a baseline you understand, not a ritual you repeat.