SpipCP
Sites

File ownership

A site's docroot has two readers — the site's own Linux owner and the web server — and one ownership contract that lets both do their job. This is what that contract says, how to see whether the files on disk still match it, and the one button that puts them back.

Two identities need the same directory. The site's Linux owner writes the files — over SFTP, from the site terminal, through a deploy. The web server reads them, and for WordPress it also has to write a small part of them, because installing a plugin, updating a theme and accepting a media upload are all file writes performed by the running site.

Give the tree to one of them and the other stops working. That is why a docroot has an ownership contract rather than a single owner, and why "the site is up but it cannot install a plugin" is a recognisable failure with a specific cause.

What the contract says

Every web site's docroot is owned by the site's own Linux owner, with www-data as the group — written the same way from both directions, by the owner provisioning step and by the launch's hardening step, so the result never depends on which ran last.

Site typeWhat the contract covers
WordPresswp-config.php at 640. Everything under wp-content at 2775 (directories) and 664 (files). WordPress core at 755 and 644.
PHP / staticOwnership only — <owner>:www-data across the docroot, with no mode claimed.
Node / Python / Go / RustNo contract. The tree belongs entirely to the site's own runtime user; no web server has to read it.

Three of those numbers carry the whole design:

  • wp-content at 2775, not 755. The group needs the write bit, because the group is how the running site reaches its own uploads, plugins and updates. At 755 the group has read and execute and nothing else, and WordPress responds by asking for FTP credentials on every screen that would have written a file.
  • The leading 2 is the setgid bit. It makes anything WordPress creates inherit the group, so a directory created by a plugin at midnight is still readable by the site's owner in the morning. Without it the split reopens one directory at a time.
  • wp-config.php at 640. The database password is not world-readable, which is exactly what makes the group assignment load-bearing: the web server reads that file through www-data and through nothing else.

Core stays read-only to the web server on purpose. A compromised worker process cannot rewrite wp-admin, wp-includes or the front controller.

An ownerless site is not a special case

A site launched without isolation has no per-site Linux owner, so the contract resolves to www-data:www-data — the same rule with the same two roles collapsed onto one user. Nothing else about it changes.

Seeing whether the files still match

File ownership appears on a site's WordPress tab, directly under Upload limits, and again in the File manager below the file list. The two are the same card: the WordPress tab is where the symptom is usually read, and the file manager is where the ownership of an individual file is being looked at.

Check ownership walks the docroot and reports only the entries that violate the contract — the expected owner:group mode against what is actually on disk, per entry, with the rule that governs it named. A populated WordPress docroot is tens of thousands of files; shipping all of them back to answer a question that is usually "nothing is wrong" would be megabytes for a null result, so the contract is pushed down into the walk and only the differences travel.

The check never runs on its own. It executes inside a live instance, and opening a tab is not consent to run I/O across a production docroot.

Repair permissions

Repair permissions re-applies the contract to the whole tree. It does not consult the check first and does not act only on the entries the check listed — it re-runs the launch's own hardening step, in full, every time. There is one thing that decides what a docroot should look like, and the repair asks it rather than carrying a second copy of the answer.

Why a serving site drifts at all

A relaunch skips every step it judges unnecessary, and a serving site's health check always passes — so the hardening step stops running after the day the site was created, while everything that writes into the docroot keeps going. Drift accumulates in exactly the window where nothing re-asserts the contract. That is the gap this button closes, and it is why the repair is unconditional.

What creates drift

Anything that writes into the docroot as a different identity than the site runs as.

Scheduled work is the case that used to matter most. WordPress's scheduled tasks run on a real timer rather than on page loads (see WordPress settings), and a plugin doing filesystem work on that timer — a backup plugin, a cache warmer, a store's queued jobs — writes files owned by whoever the timer runs as. A site's timers now run as that site's own runtime user, the same user its PHP pool runs as, so scheduled work creates files the site can already write.

A site launched before this may need one repair

Two directories WordPress needs — wp-content/uploads and wp-content/upgrade — ship in no WordPress release; the launch creates them. On sites created earlier they were created after the docroot was handed to its owner, so they were left belonging to root and the running site could not write inside them. The symptom is a plugin install ending in Could not create directory .../wp-content/upgrade/<plugin-name>, and a media upload failing with an HTTP error on a site that otherwise serves perfectly. Check ownership lists both directories; Repair permissions clears them in one pass. A site launched since is created correctly and needs nothing.

When the answer is not permissions

An upload that fails is not always an ownership problem — the writable destination is only the last of four gates, and the other three fail in ways that look similar from the browser. The two cards sit next to each other for that reason: one names the smallest ceiling in the chain, the other names the entry whose ownership is wrong. Reading them in that order costs a minute and rules out three wrong theories.

Permissions

Viewing the contract and running the check need SITES_FILES_VIEW. Running the repair needs SITES_FILES_MANAGE. The repair writes an audit row naming the blueprint step it re-ran.

Next steps

  • Upload limits — the other three gates, and whether the panel's limits are the ones the server enforces.
  • Site owners — who the owner is, and what it is allowed to do.
  • Site files — the file manager the second copy of this card lives in.

On this page