I’ve been there building a file upload feature in PHP, selecting a file, hitting upload, and boom “Sorry, there was an error uploading your file.” No PHP error logs, nothing in the browser console, and the file never shows up in the directory. Frustrating, Let me walk you through what caused this, how I fixed it, and how you can improve your own upload code to make it more reliable and user-friendly.
My Original Code
Here’s the initial code I used:
if(isset($_POST["btn-vd-submit"]) AND $vd_perm_actual > 0) {
$filename = $_FILES['vdfile']['name'];
$target_dir = "./voice-demo-files/";
$target_file = $target_dir . basename($_FILES['vdfile']['name']);
$uploadOk = 1;
$vdFileType = pathinfo($target_file,PATHINFO_EXTENSION);
if (file_exists($target_file)) {
echo "Sorry, file already exists.";
$uploadOk = 0;
}
if ($_FILES['vdfile']['size'] > 50000000000) {
echo "Sorry, your file is too large.";
$uploadOk = 0;
}
if($vdFileType != "mp3") {
echo "Sorry, only mp3 files are allowed.";
$uploadOk = 0;
}
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
} else {
if (move_uploaded_file($_FILES['vdfile']['tmp_name'], $target_file)) {
echo "The file ". basename($_FILES['vdfile']['name']). " has been uploaded.";
} else {
echo "Sorry, there was an error uploading your file.";
}
}
}
And the HTML form:
<form class="custom-form" method="post" enctype="multipart/form-data">
<div class="s-4 m-4 center">
<center>Choose A Voice Demo File (.mp3) to upload and link with your profile:<br><br><br>
<input type="file" name="vdfile" id="vdfile"><br><br><br>
</div>
<div class="s-4 m-4 center">
<button class="submit-form center button background-primary text-white" name="btn-vd-submit" type="submit">
Upload This Voice Demo!
</button>
</div>
</form>
At first glance, it looks fine, but it only gave me generic failure messages. I had no idea what was really wrong.
Why the File Upload Failed Without Showing Error
After some digging, I found the real issue:
I never checked $_FILES['vdfile']['error']
. PHP sets error codes during upload (UPLOAD_ERR_*
), but my code ignored them.
Here are common reasons this happens:
- PHP upload errors:
UPLOAD_ERR_INI_SIZE
→ file exceededupload_max_filesize
in php.iniUPLOAD_ERR_FORM_SIZE
→ file exceededMAX_FILE_SIZE
in the HTML formUPLOAD_ERR_NO_FILE
→ no file was uploadedUPLOAD_ERR_PARTIAL
→ file only partially uploaded
- Server limits:
upload_max_filesize
too smallpost_max_size
smaller than the filefile_uploads
turned off
- Directory problems:
./voice-demo-files/
didn’t exist- Directory not writable by PHP
- Insecure validation:
- I trusted only the file extension instead of checking MIME type
That’s why move_uploaded_file()
silently failed.
The Fixe and Improved Upload Code
Here’s the robust, production-ready version I now use:
<?php
ini_set('display_errors', 1);
ini_set('log_errors', 1);
error_reporting(E_ALL);
$errors = [];
$messages = [];
$vd_perm_actual = $vd_perm_actual ?? 1;
if (isset($_POST['btn-vd-submit']) && $vd_perm_actual > 0) {
if (!isset($_FILES['vdfile'])) {
$errors[] = 'No file field named vdfile was submitted.';
} else {
$file = $_FILES['vdfile'];
$errorMap = [
UPLOAD_ERR_OK => 'Upload successful.',
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize in php.ini.',
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE in the form.',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded.',
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder.',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload.'
];
if ($file['error'] !== UPLOAD_ERR_OK) {
$errors[] = $errorMap[$file['error']] ?? 'Unknown upload error.';
} else {
$targetDir = __DIR__ . '/voice-demo-files/';
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true)) {
$errors[] = 'Upload directory missing and could not be created.';
}
if (!is_writable($targetDir)) {
$errors[] = 'Upload directory is not writable.';
}
$maxBytes = 20 * 1024 * 1024; // 20 MB
if ($file['size'] > $maxBytes) {
$errors[] = 'File too large. Max allowed is 20 MB.';
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowed = ['audio/mpeg' => 'mp3', 'audio/mp3' => 'mp3'];
if (!isset($allowed[$mime])) {
$errors[] = 'Invalid type. Only MP3 files allowed.';
}
$ext = $allowed[$mime] ?? 'bin';
$safeBase = preg_replace('/[^A-Za-z0-9_\-]/', '_', pathinfo($file['name'], PATHINFO_FILENAME));
$safeBase = $safeBase ?: 'upload';
$destFilename = $safeBase . '__' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
$destPath = $targetDir . $destFilename;
if (empty($errors) && is_uploaded_file($file['tmp_name'])) {
if (move_uploaded_file($file['tmp_name'], $destPath)) {
$messages[] = 'Upload completed: ' . htmlspecialchars($destFilename);
} else {
$errors[] = 'Failed to move uploaded file. Check directory permissions.';
}
}
}
}
}
?>
This script tells me exactly why the upload failed, instead of just “error uploading file.”
Server Checklist
Before testing again, I also checked my server config:
- In
php.ini
:file_uploads = On
upload_max_filesize = 20M
post_max_size = 22M
max_file_uploads = 20
- Directory setup:
mkdir -p voice-demo-files chown www-data:www-data voice-demo-files chmod 775 voice-demo-files
- On SELinux systems:
chcon -t httpd_sys_rw_content_t voice-demo-files -R
Extra Practice Feature
Once my upload worked, I added some fun practice improvements:
- Auto-rename on duplicate: append counters like (1), (2).
- List uploaded files with delete links:
$files = glob(__DIR__.'/voice-demo-files/*.mp3'); foreach ($files as $f) echo basename($f)."<br>";
- Check both extension & MIME for extra safety.
- Limit duration/bitrate using
ffprobe
if you want stricter audio validation. - Progress bar using JavaScript
XMLHttpRequest.onprogress
.
My Troubleshooting Flow
If you’re still stuck, here’s what I do:
- Upload a tiny MP3 (<100 KB).
- Print
$_FILES['vdfile']['error']
. - Check PHP limits (
upload_max_filesize
,post_max_size
). - Verify directory path and permissions.
- Look at
error_log
for hidden PHP issues.
Final Thought
What I learned from this debugging session is simple: never trust generic error messages. Always inspect $_FILES['vdfile']['error']
, validate the directory, and double-check PHP/server limits. Once I added clear error handling, not only did the problem disappear, but I also gained confidence that future uploads will fail safely and clearly when something goes wrong.