A media library should just be a folder
Every quota you invent is a second number to keep in sync with reality. The filesystem already knows how much space there is.
When you build a media library the temptation is to make it a system: a database of assets, a quota, an import pipeline, a separate place where files live. Then somebody uploads a file over FTP, or a script writes an image, and your library does not know about it — because the library and the storage are two different things that have to be kept in agreement.
They do not have to be. A media library can be a directory.
What that buys you
- Anything dropped in from a file manager, FTP or a script appears in the library, because listing the library is listing the directory.
- There is no import step, no sync job, and no state that can drift.
- Deleting a file in either place deletes it in both, because there is only one place.
- The storage limit is the disk, which is a number you already have and already monitor.
The quota question answers itself. If the folder is on the same volume as everything else, the only real ceilings are what the platform accepts for a single upload and how much disk is free. Inventing a 2 GB library cap adds a number that will be wrong on a 200 GB server and wrong again on a 20 GB one.
df -h /var/www | tail -1
# that is your quota, and it updates itselfWhere it gets interesting: it is inside a public docroot
Serving the files is the easy part — put the folder under the document root and the web server serves them with no route, no PHP, and full caching. The cost is that anything landing in that folder is publicly readable, and possibly executable, so uploads need real gates.
Decide the type by content, never by name
$fi = new finfo(FILEINFO_MIME_TYPE);
$mime = $fi->file($tmp); // what it IS
$ext = $allow[$mime] ?? null; // not what it was called
if (!$ext) reject();Then rebuild the stored filename yourself from a slug plus the extension you chose. A file that arrives as cat.jpg but sniffs as PHP is refused; a file that arrives as cat.php.jpg is stored as cat.jpg. The name the browser sent never reaches the filesystem.
.php, .phar, .cgi and friends by extension as well, regardless of what the sniff says — defence in depth is nearly free here. And block .html and .js: a page you did not write, hosted on your own domain, is a phishing and XSS vector even though it is not executable.SVG is an image that can carry script
It is worth allowing, because logos need it, but it is markup and it will be served same-origin:
if ($ext === 'svg' && preg_match(
'/<script|javascript:|on[a-z]+\s*=|<foreignObject|<iframe/i', $svg)) {
reject('That SVG contains script.');
}And do not delete out from under a live page
Before removing a file, search your content for references to it and say which pages use it. Offer the override — sometimes you really do want it gone — but make the consequence visible first. A broken image on a published article is not an undoable mistake in the sense that matters: nobody notices until a reader does.
Alien XP
Runs the servers behind AlphaPanel and writes up what breaks.




