Free tools Windows power users keep installed
One-click scans. No signup required.
To accept visitor uploads on a LAMP server, you must configure five layers together: an HTML form encoded as multipart/form-data, PHP upload limits, a writable temporary directory, application-level validation and storage, and Apache rules that cap requests and prevent uploaded scripts from executing. The safest design stores files outside the web root, gives them generated names, and serves approved downloads through an authorization-aware PHP endpoint.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
What must be configured
A browser upload is not enabled by a single switch. The browser, PHP, the filesystem, your application, and Apache each handle a different part of the request.
As an Amazon Associate I earn from qualifying purchases.
| Layer | What it does | Typical failure |
|---|---|---|
| HTML form | Packages the selected file in an HTTP POST request | $_FILES is empty because the encoding is wrong |
| PHP directives | Enables uploads and limits file size, request size, and file count | PHP rejects a request before your script runs |
| Temporary directory | Holds the upload while PHP processes it | “Unable to create temporary file” or upload error code 6 |
| Application code | Authenticates, validates, renames, and moves the file | Malicious content, collisions, or unauthorized files are accepted |
| Apache | Applies an independent request-body ceiling and controls execution | Oversized requests consume resources or an uploaded script runs |
Configure all five layers before exposing the form to untrusted visitors. If an application does not need uploads, OWASP recommends disabling PHP uploads with file_uploads = Off instead.
1. Build the upload form correctly
PHP’s documentation is explicit: “Be sure your file upload form has attribute enctype="multipart/form-data" otherwise the file upload will not work.” The form also needs method="POST", and the file input’s name becomes the key in $_FILES.
<form action="/upload.php" method="POST" enctype="multipart/form-data">
<label for="document">Choose a document</label>
<input type="file" id="document" name="document" accept=".pdf,.txt" required>
<button type="submit">Upload</button>
</form>
The accept attribute only guides the browser’s file picker. It is not a security control; a visitor can submit a different file with a crafted request.
Optional early size warning
A hidden MAX_FILE_SIZE field can tell a browser to reject an oversized selection before submission:
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →<input type="hidden" name="MAX_FILE_SIZE" value="10485760">
That value is expressed in bytes. PHP warns that the field can be fooled, so your server-side byte check remains mandatory. Keep the hidden value synchronized with the user-facing limit, but never treat it as proof that a file is safe.
Protect the form itself
Require the same authentication and CSRF protection you use for other state-changing forms. For public uploads, add rate limits, quotas, and abuse monitoring before permitting anonymous traffic. A successful HTTP submission is not evidence that the file passed your policy; the response should report the application’s validation result.
2. Set PHP’s upload directives
PHP’s core directives determine whether uploads are accepted and how much data PHP will read. A practical starting example is:
file_uploads = On
upload_tmp_dir = /var/lib/php-upload-tmp
upload_max_filesize = 10M
post_max_size = 12M
max_file_uploads = 5
These are examples, not universal recommendations. Select values from the feature’s real file types, available disk space, expected concurrency, and abuse tolerance. The 12 MB POST limit is larger than the 10 MB per-file limit to leave room for multipart boundaries, form fields, and request overhead.
| Directive | Purpose | Important detail |
|---|---|---|
file_uploads |
Enables HTTP file uploads | Set it to Off when the application has no upload feature |
upload_tmp_dir |
Temporary location used before your script moves a file | The PHP runtime must be able to write there |
upload_max_filesize |
Maximum size of one uploaded file | PHP can reject the file before application validation |
post_max_size |
Maximum complete POST body | Set it higher than upload_max_filesize |
max_file_uploads |
Maximum number of files in one request | Protects against requests containing excessive file parts |
Find the active configuration
Editing a file that the web process does not read is a common source of confusion. Distribution package layouts and service names differ, and the command-line PHP binary may use a different configuration from Apache’s PHP module or PHP-FPM.
Rank #2
Create a temporary diagnostic page in a protected location containing <?php phpinfo();, inspect the web SAPI values, and remove the page immediately afterward. Verify file_uploads, upload_tmp_dir, upload_max_filesize, post_max_size, and max_file_uploads as seen by the actual upload endpoint.
After changing the active php.ini or PHP-FPM pool configuration, reload the PHP and/or Apache service used by the site. Then check the effective values through the web SAPI again; a CLI check alone is insufficient.
3. Create a safe temporary directory
PHP first writes the incoming stream to a temporary file. The directory specified by upload_tmp_dir must exist and be writable by the PHP runtime. If it cannot be used, PHP may fall back to the system temporary directory, subject to open_basedir restrictions. That fallback can be unexpected and may fail in a hardened installation.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
- Create a dedicated directory such as
/var/lib/php-upload-tmpon the same host as PHP. - Assign ownership or group access to the account that actually runs PHP, such as the PHP-FPM pool user. Do not assume the interactive shell user is the runtime user.
- Grant only the write and search permissions required for temporary files. Avoid making the whole web root or filesystem broadly writable.
- Set
upload_tmp_dirto the directory in the active PHP configuration and reload the relevant service. - Submit a small test file and inspect PHP logs if the temporary-file error code appears.
Temporary files should be on a filesystem with enough free space for concurrent uploads. A request limit of 10 MB does not mean the server needs only 10 MB: several simultaneous requests, multipart overhead, and application copies can consume substantially more.
4. Choose where uploaded files live
The strongest default is a directory outside Apache’s document root, for example a path under /srv/app-data/uploads. Apache cannot serve a file it cannot map to a public URL, which removes direct browser access and materially reduces the chance that an uploaded script will be executed.
| Storage design | Advantages | Risks and work | Best fit |
|---|---|---|---|
| Outside document root | Direct URL access is absent; authorization can be enforced before every download | Requires a download endpoint or controlled server integration | Private documents, profile data, business records |
| Inside document root with execution disabled | Simple static delivery and caching | A configuration mistake can expose files or enable script execution | Truly public, non-sensitive assets after strict controls |
| Quarantine or object storage | Separates untrusted files from the web host and supports asynchronous scanning | More components, credentials, retry logic, and lifecycle management | High-volume or high-risk uploads |
Apache’s FileSystemSecurity guidance describes the typical attack model as someone uploading a PHP script into the document root and executing it through a browser. Writable directories outside the document root are safer because the uploaded file is not automatically reachable by URL.
Permissions
Give only the destination and temporary directories the write permission required by PHP. Keep application code, virtual-host configuration, and secrets non-writable by the web process whenever possible. A web process that can rewrite its own PHP files can turn a file-upload bug into a broader compromise.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match5. Validate and move the upload in PHP
Never trust the visitor’s filename, extension, MIME header, or client-side controls. Authenticate the operation, check PHP’s upload error code, enforce a byte limit, validate the content, generate a storage name, and move the file only after all checks pass.
<?php
declare(strict_types=1);
$maxBytes = 10 * 1024 * 1024;
$allowed = [
'pdf' => 'application/pdf',
'txt' => 'text/plain',
];
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit('POST required');
}
if (!isset($_FILES['document']) || !is_array($_FILES['document'])) {
http_response_code(400);
exit('No file received');
}
$file = $_FILES['document'];
if ($file['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
exit('Upload failed');
}
if (!is_uploaded_file($file['tmp_name'])) {
http_response_code(400);
exit('Invalid upload source');
}
if ($file['size'] < 1 || $file['size'] > $maxBytes) {
http_response_code(413);
exit('File size is not allowed');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$extension = array_search($mime, $allowed, true);
if ($extension === false) {
http_response_code(415);
exit('File type is not allowed');
}
$destinationDir = '/srv/app-data/uploads';
if (!is_dir($destinationDir)) {
throw new RuntimeException('Upload directory is missing');
}
$storageName = bin2hex(random_bytes(16)) . '.' . $extension;
$destination = $destinationDir . DIRECTORY_SEPARATOR . $storageName;
if (!move_uploaded_file($file['tmp_name'], $destination)) {
error_log('Upload move failed');
http_response_code(500);
exit('Could not store file');
}
// Store $storageName and the authenticated owner in your database.
echo 'Upload accepted';
The example intentionally stores an application-generated identifier rather than the submitted name. Keep the original name, if users need to see it, as metadata after normalizing and escaping it for display; do not use it as a filesystem path.
Use an allowlist, not a blacklist
Permit only the formats the feature needs. Check the detected content type with finfo or an equivalent server-side parser, compare it with the allowed extension mapping, and reject mismatches. A filename such as exploit.php.jpg illustrates why checking only the last-looking extension is unsafe: depending on Apache and PHP integration, a script-like name can still be interpreted as PHP.
For images, decode and re-encode with an image library rather than trusting an image MIME label. For office documents, PDFs, archives, and media, consider malware scanning and decompression limits. Do not log file contents; record identifiers, authenticated user IDs, rejection reasons, sizes, and timestamps instead.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Handle every PHP upload error
| Code | Meaning | Useful response |
|---|---|---|
UPLOAD_ERR_INI_SIZE |
File exceeds upload_max_filesize |
Tell the user the file exceeds the configured limit |
UPLOAD_ERR_FORM_SIZE |
File exceeds the form’s MAX_FILE_SIZE |
Return a size error; do not rely on this field for security |
UPLOAD_ERR_PARTIAL |
Only part of the file arrived | Ask the user to retry and inspect network or proxy logs |
UPLOAD_ERR_NO_FILE |
No file was selected | Return a validation message |
UPLOAD_ERR_NO_TMP_DIR |
PHP cannot find its temporary directory | Check upload_tmp_dir and runtime permissions |
UPLOAD_ERR_CANT_WRITE |
PHP cannot write the temporary file | Check ownership, mode, disk space, and filesystem errors |
UPLOAD_ERR_EXTENSION |
A PHP extension stopped the upload | Review enabled extensions and PHP logs |
6. Add Apache request and execution controls
Cap the request body independently
Apache’s LimitRequestBody directive limits the allowed HTTP request message body. Put it in the appropriate server, virtual-host, directory, or location context for the upload endpoint. For a 12 MiB request ceiling, an example value is:
LimitRequestBody 12582912
Keep Apache’s ceiling at least as high as the intended PHP post_max_size, otherwise Apache will reject a request that PHP was configured to accept. You can deliberately make Apache lower for a specific endpoint, but document that choice and return a clear error. The separate ceilings provide defense in depth rather than a substitute for PHP validation.
Prevent uploaded code from executing
Storing outside the document root is preferable. If business requirements demand a public upload directory, configure that directory so PHP is not enabled there and reject script-like extensions in application code. Do not depend on renaming a file from .php to another extension alone.
Rank #4
Place the execution restriction in the virtual-host or directory configuration where it is guaranteed to apply, test the effective Apache configuration, reload Apache, and then request a test file. The expected result is either a download of inert data or an authorization failure, never PHP output.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsDo not grant write access to the whole document root merely to make one upload directory work. Isolate the directory, keep configuration files protected, and ensure any download endpoint sets an appropriate content disposition and content type instead of evaluating the file as a server-side script.
7. Serve private files through authorization-aware downloads
For files outside the web root, expose a route such as /download?id=... that looks up an internal storage identifier, verifies the requesting user’s permission, confirms the file still exists, and streams it. Never concatenate an unchecked query-string value into a filesystem path.
- Accept an opaque database identifier, not a visitor-supplied path.
- Load the stored path and owner or access-control record from the database.
- Authorize the request before opening the file.
- Set a safe download name from stored metadata and escape it for the response header.
- Stream the file or delegate to a trusted internal file-serving mechanism without exposing the storage directory.
- Log authorization failures and repeated attempts without logging sensitive contents.
For large files or high concurrency, a quarantine and object-storage workflow can isolate untrusted content and let a background worker scan it before publication. That design adds queues, retries, lifecycle policies, and credential management, so use it when the isolation and operational benefits justify the complexity.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.8. Verification and failure diagnosis
Run these tests from a non-privileged client before announcing the feature:
Recommended Free Tools
- Confirm the active web SAPI reports
file_uploads=Onand the intended size and count limits. - Upload a small allowed file and verify that it receives a generated storage name and the correct owner record.
- Upload a file larger than
upload_max_filesize; confirm PHP rejects it cleanly. - Send a request larger than
post_max_size; confirm the endpoint reports an oversized request rather than partially processing it. - Submit a disallowed extension and a file whose detected content does not match its extension; both should be rejected.
- Confirm the temporary and destination directories are writable by PHP but not broadly writable by unrelated users.
- Try to request an uploaded script directly. It must be unreachable or served as inert data, never executed.
- Review Apache and PHP logs for rejected uploads, permission errors, disk-full conditions, and oversized requests.
Common symptoms and fixes
| Symptom | Likely cause | Check |
|---|---|---|
$_FILES is empty |
Missing multipart encoding, wrong input name, or non-POST submission | Inspect the form attributes and compare the input name with the PHP key |
| Every upload fails with a temporary-file error | Missing directory, wrong owner, restrictive mode, open_basedir, or full disk |
Check the effective upload_tmp_dir, permissions, mount, and logs |
| Small files work but larger ones fail | PHP or Apache size ceiling | Compare upload_max_filesize, post_max_size, and LimitRequestBody |
| Files appear with the visitor’s name | Application used the submitted filename as storage name | Generate a random identifier and keep the original only as escaped metadata |
| A test PHP file executes | Upload directory is under the document root with PHP execution enabled | Move storage outside the root or disable PHP handling for that directory immediately |
| Uploads work in CLI tests but not through the site | CLI and web SAPI use different PHP configurations or users | Inspect the values and identity seen by the web request |
Operational safeguards after launch
- Set per-user and global quotas, and reject uploads when free space or an application quota is exhausted.
- Rate-limit repeated attempts and cap concurrent processing for anonymous users.
- Schedule cleanup of abandoned temporary files and expired application records.
- Back up private uploads according to their sensitivity, and test restoration separately from the web server.
- Patch Apache, PHP, the operating system, and file-scanning components promptly.
- Monitor rejection rates, storage growth, permission failures, and unexpected executable-file requests.
- Recheck that the upload feature still needs to be enabled; if it is removed, set
file_uploads = Off.
FAQ
Why does PHP say the request is too large even though the file is under the file limit?
The complete multipart POST includes boundaries and other fields. If the request exceeds post_max_size, PHP can reject it even when the individual file is below upload_max_filesize. Raise the request limit deliberately or reduce the permitted file size and form payload.
Can I trust the MIME type sent by the browser?
No. Treat the browser’s type and filename as hints only. Detect the temporary file’s content on the server, compare it with an allowlist, and apply format-specific parsing or scanning where the risk warrants it.
Best Value
Should I preserve the original filename?
Preserve it only as display metadata after normalization and output escaping. Use a random, application-generated identifier for the filesystem name so path traversal, collisions, and script-like names cannot control storage.
What should happen when an upload is interrupted?
Return a retryable error, discard any partial temporary file, and inspect the client, proxy, and PHP logs. Do not move a file unless PHP reports UPLOAD_ERR_OK and the server-side validation succeeds.
Is disabling PHP execution in a public upload directory enough?
It removes one execution path, but it does not make untrusted files safe. Keep the allowlist, content checks, generated names, authorization, quotas, and logging. Store sensitive files outside the document root whenever possible.
Frequently Asked Questions
Why does PHP say the request is too large even though the file is under the file limit?
The complete multipart POST includes boundaries and other fields. If the request exceeds post_max_size, PHP can reject it even when the individual file is below upload_max_filesize.
Can I trust the MIME type sent by the browser?
No. Detect the temporary file’s content on the server and compare it with an allowlist; browser-supplied metadata is only a hint.
Should I preserve the original filename?
Keep it only as escaped display metadata. Use a random application-generated storage name.
What should happen when an upload is interrupted?
Return a retryable error, discard the partial file, and move files only after PHP reports UPLOAD_ERR_OK and validation succeeds.
Is disabling PHP execution in a public upload directory enough?
No. Continue validating content, generating names, enforcing authorization and quotas, and monitoring uploads.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




