While I was working with Joomla on a CentOS server managed through Plesk, I faced a strange problem: no file was uploading to the server, but Joomla displayed no useful error.
I checked php.ini, and everything looked correct. The upload limit was enough, file uploads were enabled, and the site was working normally. Still, the uploader did nothing.
I later saw the same type of issue in PrestaShop and WordPress. That was the clue—the application was not always the real problem. PHP needed a writable temporary directory, the domain needed correct ownership, and Plesk had to apply the right configuration to the active PHP handler.
This article keeps the troubleshooting path that solved my issue, but updates the unsafe parts of my old fix. You should not disable open_basedir globally or run broad recursive permission commands before checking which layer is failing.
The Issue I Faced
The confusing part was the silence. Joomla did not show a clear “permission denied” message. A small file appeared to start uploading, then nothing reached the Media Manager.
A PHP upload passes through more than one location:
- The web server accepts the request.
- PHP writes the incoming file to its temporary upload directory.
- Joomla validates the file and moves it to the destination folder.
If PHP cannot create the temporary file, Joomla never receives a usable upload. If Joomla receives it but cannot write to its final directory, the failure happens one step later. Both can look like the same broken upload button.
Before changing configuration, reproduce the issue with one small JPG or TXT file. Note its size and extension. Testing one known file makes it easier to see whether the failure is caused by size limits, blocked file types, or permissions.
Check the Active PHP Settings
Do not assume /etc/php.ini is the configuration used by the website. Plesk can run multiple PHP versions and handlers. The command-line PHP configuration may differ from PHP-FPM or FastCGI serving Joomla.
In Plesk, open the domain and check PHP Settings. You can also use Joomla’s System Information screen to inspect the active PHP values. If you temporarily create a phpinfo() page, protect it and remove it immediately because it exposes server paths and configuration.
Verify these values:
file_uploads = On
upload_max_filesize = 20M
post_max_size = 24M
upload_tmp_dir = /var/www/vhosts/example.com/tmp
max_file_uploads = 20
post_max_size must be larger than upload_max_filesize because the complete HTTP request includes form fields and multipart overhead. If the request exceeds post_max_size, PHP may leave $_FILES empty, which makes the failure look more mysterious.
upload_tmp_dir can be left unset when the system temporary directory is correct and writable. If you define a domain-specific path, make sure it exists, belongs to the expected subscription user, and is allowed by open_basedir.
After changing PHP settings in Plesk, apply the change and confirm the value from the website’s active PHP runtime—not only from SSH.
Read the Upload Error
When Joomla hides the reason, a small temporary PHP form can tell you whether PHP received the file. Use it only during diagnosis and delete it afterward.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['test_file'] ?? null;
echo '<pre>';
print_r($file);
echo '</pre>';
}
?>
<form method="post" enctype="multipart/form-data">
<input type="file" name="test_file" required>
<button type="submit">Test upload</button>
</form>
The error value is important:
0means PHP accepted the upload.1or2means the file exceeded a size limit.3means the upload was incomplete.4means no file reached PHP.6means PHP could not find a temporary directory.7means PHP could not write the temporary file.8means a PHP extension stopped the upload.
If the array is empty, check post_max_size, the request path, and web-server limits. If PHP reports success but Joomla still fails, continue with Joomla paths and destination permissions.
Check Joomla Paths
Open Joomla’s configuration and confirm that its temporary and log paths point to real directories for this installation. A migrated site may still reference a path from the old server.
The exact values live in configuration.php, typically as $tmp_path and $log_path. Do not copy a path from another domain. Resolve the real subscription path in Plesk and make sure Joomla can write there.
Also check the destination used by the Media Manager. The directory should be writable by the PHP handler’s user without being world-writable.
From the domain’s SSH terminal, inspect the paths rather than changing them blindly:
stat /var/www/vhosts/example.com/tmp
stat /var/www/vhosts/example.com/httpdocs/images
namei -l /var/www/vhosts/example.com/httpdocs/images
namei -l is useful because a correct final directory still fails when one parent directory blocks traversal.
Joomla can also reject a file because of its extension, MIME type, or Media Manager permissions. If PHP receives the test upload correctly, review Joomla’s allowed extensions and the logged-in user’s media permissions before editing server ownership.
Fix Plesk Configuration
My old article created vhost.conf and vhost_ssl.conf under the domain and added:
php_admin_flag engine on
php_admin_value open_basedir none
That helped on my legacy Plesk setup, but open_basedir none is too broad as a modern default. It removes a filesystem boundary instead of allowing only the temporary path Joomla needs. The directive also depends on the active PHP handler; an Apache directive may not control a PHP-FPM pool.
Prefer Plesk > Websites & Domains > PHP Settings for PHP values. Keep open_basedir enabled and add the required domain temp path to its allowed paths.
If a domain genuinely needs custom web-server directives, current Plesk stores persistent files under:
/var/www/vhosts/system/example.com/conf/vhost.conf
/var/www/vhosts/system/example.com/conf/vhost_ssl.conf
Use Plesk’s Additional Apache directives fields when possible, because Plesk applies them through its configuration system. Do not edit generated httpd.conf files; Plesk can overwrite them.
After a manual domain configuration change, regenerate the domain web configuration with the supported Plesk tooling for that installed version. A current repair command is:
plesk repair web example.com -y
Validate the web-server configuration and read the command output before restarting services.
Repair Ownership Safely
The original fix recursively changed httpdocs to apache:psacln, enabled group writes, and set the setgid bit on every directory. That may have matched one old Apache handler, but it is not safe to copy across modern Plesk servers. PHP-FPM commonly runs as the subscription’s system user, not a universal apache user.
First let Plesk detect unexpected ownership and permissions without changing anything:
plesk repair fs example.com -n -v
Review the result. If it matches the problem, let Plesk repair the domain:
plesk repair fs example.com -y
This is safer than guessing an owner and running chown -R over the complete document root. A recursive command can break deployment files, protected configuration, extension updates, or isolation between subscriptions.
Do not use chmod -R 777. World-writable PHP application directories are an unnecessary security risk. Give write access only to Joomla’s temp, cache, log, and media destinations according to the domain’s actual PHP handler.
On SELinux-enabled CentOS systems, Unix permissions can look correct while the security context blocks writes. Check the audit log and restore the expected context instead of disabling SELinux globally.
Verify the Complete Upload
After each change, repeat the same small-file test. Changing one layer at a time tells you which fix actually worked.
Use this order:
- Confirm the active PHP settings from the web runtime.
- Confirm the PHP test form reports upload error
0. - Confirm Joomla’s temp and destination paths exist.
- Confirm Plesk domain ownership and permissions.
- Check Joomla, PHP-FPM, Apache/nginx, and SELinux logs.
- Upload through Joomla Media Manager.
- Remove the temporary diagnostic form.
In my case, php.ini looked correct, but the server-side domain configuration and write access were the missing pieces. Once those were corrected and the web configuration was rebuilt, uploads started working.
The main lesson was simple: when Joomla, WordPress, and PrestaShop show the same silent upload problem on one server, stop blaming each CMS separately. Follow the file from the HTTP request to PHP’s temporary directory and then to the application destination. The layer where it disappears is the layer you need to fix.