Security Headers for Business Websites: The HTTP Headers That Make a Real Difference

15 min read 2,841 words
Security Headers for Business Websites: The HTTP Headers That Make a Real Difference featured image

File Upload Security in PHP: Configuration, Validation, and Safe Storage Practices

File upload functionality appears on many business websites. Job application forms, document management systems, profile picture uploads, and support ticket attachments all rely on the ability to receive files from users. This convenience comes with real security risks if the implementation is not carefully configured and validated.

When a file upload is handled poorly, an attacker can use it to upload executable scripts, overwrite existing files, or gain access to the underlying server. The consequences can range from a defaced website to a complete server compromise. Understanding how to handle file uploads safely is an essential part of any PHP-based website that accepts user-submitted files.

This guide covers the key areas where file upload security can fail, and what proper configuration and validation look like in practice. If you are managing a business website and want to check whether your current setup is secure, a practical review of your upload handling code and server configuration is a sensible first step.

Why File Uploads Are a Common Attack Vector

When a website accepts a file from a user, it must process that file on the server. That processing step is where many implementations fall short. The server is being asked to accept data from an untrusted source and do something useful with it. Without strict controls in place, the server may end up executing code that an attacker embedded inside an uploaded file.

PHP has historically been a popular target for this type of attack because of how it handles file requests. An uploaded file that arrives with a .php extension and is placed in a publicly accessible directory can sometimes be executed directly by the web server, giving an attacker a foothold on the system.

The risk extends beyond PHP files. Scripts in other languages, shell scripts, and executable binaries can all cause problems if the server treats them as files to be run rather than files to be stored. Understanding the full attack surface helps when reviewing your own implementation.

PHP Configuration Settings for File Uploads

The first layer of protection starts with PHP's own configuration settings. These are typically set in php.ini or through server configuration files like .htaccess for Apache or Nginx site configs.

Restricting Upload File Size

Limiting how large an uploaded file can be prevents denial-of-service attacks where a user floods the server with enormous files to fill up disk space or exhaust server resources. Set a reasonable maximum based on what your application actually needs.

upload_max_filesize = 2M
post_max_size = 8M

The post_max_size value should be larger than upload_max_filesize because the POST request includes form fields and other data alongside the file itself. A typical ratio is to set post_max_size to roughly four times the maximum upload size to account for form metadata and any multiple file uploads.

Disabling Dangerous PHP Settings

Some PHP configuration settings increase the risk associated with file uploads. The enable_post_data_reading setting should generally remain enabled, but the file_uploads directive should only be set to On on pages that actually need to accept uploads.

file_uploads = On ; only on pages that need uploads
max_file_uploads = 5

If your application does not need file uploads at all, disabling file_uploads entirely removes the attack surface. For applications that do need uploads, limiting the number of files per request with max_file_uploads prevents attackers from overwhelming the server with many files in a single request.

Memory and Execution Limits

Restricting memory limits and script execution time prevents uploaded files from consuming excessive server resources during processing. Set these to values appropriate for your application's actual needs rather than leaving them at unsafe defaults.

memory_limit = 128M
max_execution_time = 60
max_input_time = 60

When a script needs to process large files, it requires more memory and execution time. However, these values should be tuned to the specific requirements of your upload handling code, not set arbitrarily high.

Server-Level Configuration for Uploaded Files

PHP configuration alone is not sufficient. The web server also plays a critical role in how uploaded files are handled. Your server configuration should prevent the web server from executing uploaded files as scripts.

Nginx Configuration

In Nginx, you can explicitly define which file types the server is allowed to serve, and disable script execution for the upload directory.

location /uploads/ {
    alias /var/www/example.com/uploads/;
    location ~ \.php$ {
        deny all;
    }
}

This configuration serves files from the uploads directory but explicitly blocks any attempt to execute PHP files within that directory. The uploads directory is treated as a static file store only.

Apache Configuration

For Apache servers, use .htaccess or the site configuration to disable script execution in upload directories.

<Directory "/var/www/example.com/uploads">
    <FilesMatch "\.php$">
        Order Deny,Allow
        Deny from all
    </FilesMatch>
</Directory>

Disabling script execution at the server level provides a fallback defence. Even if PHP configuration is changed or the application code has a flaw, the web server will not execute uploaded files as scripts.

Content Security Policy and Related Headers

HTTP security headers add another layer of protection. A strict Content Security Policy can help limit what resources a page can load, reducing the impact of any vulnerability that might allow an attacker to inject script content. For file upload pages specifically, a well-configured CSP can restrict script sources and inline execution.

For more detail on configuring these headers correctly across different server types, the security headers guide covers X-Frame-Options, Content-Security-Policy, HSTS, and other relevant directives in depth.

File Validation: Verifying What Users Upload

Configuration and server settings are the foundation, but your application code must validate every uploaded file before it is stored or processed. Validation should happen on multiple levels because relying on a single check is insufficient.

Checking the File Extension

The file extension is the most basic check, but it is also the easiest to forge. Attackers can upload a file named malicious.php.jpg and depending on server configuration, the extension might be read as .jpg by some parts of the system while the file itself remains executable. Use the extension only as a first, quick filter, not as a definitive check.

$allowed_extensions = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'];
$file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

if (!in_array($file_extension, $allowed_extensions)) {
    die('File type not allowed.');
}

Verifying the MIME Type

The MIME type indicates what the file claims to be based on its content. PHP's finfo_file function can read the actual file content and determine its MIME type, which is more reliable than trusting the browser-supplied MIME type.

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $temporary_file_path);
finfo_close($finfo);

$allowed_mime_types = [
    'image/jpeg',
    'image/png',
    'application/pdf',
    'application/msword'
];

if (!in_array($mime_type, $allowed_mime_types)) {
    die('Invalid file type detected.');
}

Checking both the extension and the MIME type together makes it harder for an attacker to bypass validation. A file with a .jpg extension should have a MIME type that matches an image format.

Validating File Size on the Server Side

Client-side file size checks can be bypassed easily, so always validate file size in your server-side code as well. PHP provides the uploaded file size through the $_FILES array, and you should cross-check this against your maximum allowed size.

if ($_FILES['uploaded_file']['size'] > 2097152) { // 2MB in bytes
    die('File exceeds maximum size allowed.');
}

Scanning for Malicious Content

For higher-security environments, consider integrating a file scanning tool to check uploaded files for known malware signatures. ClamAV is a popular open-source antivirus engine that can be integrated into PHP applications to scan files before they are stored.

Scanning adds processing overhead and is not always necessary for every use case, but for file uploads that accept executable content or scripts from untrusted users, it provides meaningful protection against known threats.

Safe File Storage Practices

Where and how you store uploaded files matters as much as validating them. Poor storage practices can expose files to unauthorized access, allow attackers to overwrite system files, or make files publicly accessible when they should not be.

Store Files Outside the Web Root

The most important storage rule is to keep uploaded files outside the directory that the web server serves publicly. If the upload directory is inside the web root and script execution is not properly disabled, uploaded files could be executed directly.

Store files in a directory that is accessible only to your application code, not to the web server directly. Serve them through a PHP script that handles authentication and validation before delivering the file to the user.

$upload_dir = '/var/www/example.com/private_uploads/';
$filename = uniqid('file_', true) . '_' . basename($original_filename);
$destination = $upload_dir . $filename;

Use a Randomised Filename

Storing files with their original names can cause problems. An attacker could upload a file with a name that overwrites an existing system file, or a file with a name containing path traversal characters like ../. Generating a random filename eliminates these risks.

$new_filename = bin2hex(random_bytes(16)) . '.' . $safe_extension;
$destination = $upload_dir . $new_filename;

Keep a record of the original filename in your database so you can display it to users or include it in email notifications, but store the file itself under a name that an attacker cannot predict or manipulate.

Set Correct File Permissions

Uploaded files should have restrictive permissions that prevent unauthorized reading or modification. The upload directory itself should be writable only by the application, and readable only where necessary.

chmod($destination, 0644); // Readable, not executable
chmod($upload_dir, 0755); // Directory readable and traversable, writable only by owner

Files should never have execute permissions. If your application needs to modify uploaded files, use PHP's file writing functions rather than relying on execute permissions.

Serving Uploaded Files Safely

Uploaded files stored outside the web root cannot be accessed directly by users. They must be served through a PHP script that validates the user's permission to access the file before delivering it.

Using a Read-Through Script

A read-through script receives a request for a file, checks if the user is authorised to access it, and then reads the file from the private storage location before outputting it to the user.

$file_id = $_GET['id'] ?? null;

if (!$file_id) {
    http_response_code(400);
    exit('No file specified.');
}

$file_record = get_file_record($file_id);

if (!$file_record || !user_can_access_file($user_id, $file_record)) {
    http_response_code(403);
    exit('Access denied.');
}

$file_path = '/var/www/example.com/private_uploads/' . $file_record['stored_name'];

header('Content-Type: ' . $file_record['mime_type']);
header('Content-Length: ' . filesize($file_path));
readfile($file_path);

This approach ensures that even if someone knows the storage path of an uploaded file, they cannot access it without proper authorisation through the application logic.

Preventing Directory Traversal

Always validate any file identifier used in requests. If you use a filename or path component directly from user input, an attacker could attempt a directory traversal attack by submitting something like ../../etc/passwd. Use whitelisting and parameterised queries when working with file identifiers.

Common File Upload Security Mistakes

Several recurring mistakes appear frequently in file upload implementations. Recognising them helps when reviewing an existing setup.

  • Relying solely on client-side validation: JavaScript checks can be bypassed by disabling JavaScript or using browser developer tools. Server-side validation is always required.
  • Trusting the uploaded filename: Never use the filename provided by the user directly in file operations without sanitisation and renaming.
  • Storing files in the web root: Files placed in publicly accessible directories can be executed if the server is misconfigured.
  • Using blacklists instead of whitelists: Blocking specific dangerous file types is harder than allowing only known safe types. Whitelisting is more reliable.
  • Not verifying the file content: Checking only the extension or MIME type is insufficient. File content verification using finfo_file or similar tools is necessary.
  • Leaving upload directories world-writable: Excessive permissions on upload directories can allow attackers to overwrite existing files or store malicious content.

Integrating Secure File Uploads with Business Workflows

File uploads often support business workflows such as contract submissions, support ticket attachments, or order document uploads. When building these systems, the security of the upload mechanism should be considered alongside the workflow requirements.

If your upload system supports document generation or pricing tools, those integrations should be reviewed for security as well. For example, a quote generator that processes uploaded files needs careful input validation to prevent malformed data from causing calculation errors. The quote generator without arithmetic errors guide covers this area in more detail for systems that combine file processing with business logic.

Similarly, if your uploads are served through a CDN or content delivery layer, the security headers applied to those assets should be reviewed to ensure they are appropriate for the content type. The CDN setup guide for business websites explains how to configure delivery for static assets while maintaining security controls.

When to Review Your Upload Configuration

File upload security should be reviewed when setting up a new system, when adding new upload functionality to an existing site, and periodically as part of routine maintenance. Changes to PHP version, web server configuration, or hosting provider can all affect how uploads are handled.

If your business website has forms that accept file attachments and you have not reviewed the upload handling code recently, a security review is worthwhile. For sites running older PHP versions or relying on older upload libraries, updating the implementation to follow current best practices can reduce risk significantly.

Regular PHP security reviews help catch configuration drift and outdated practices before they become a problem. If you need help reviewing your current setup, you can get in touch with details of what your upload forms do and where the files are stored.

Related practical reading

These related guides can help you connect this topic with the wider website, server, security, and support decisions around it.

Keeping File Uploads Secure Over Time

File upload security is not a one-time configuration. As your application grows, new upload features may be added, server configurations may change, and new attack techniques may emerge. Maintaining secure file uploads requires ongoing attention to how files are received, validated, stored, and served.

The most reliable approach combines server-level configuration to prevent script execution, application-level validation to verify file types and content, and safe storage practices that keep uploaded files separate from executable code. When these layers work together, the risk of a file upload vulnerability being exploited is significantly reduced.

If you want a practical review of your current upload handling setup, prepare details of what your upload forms accept, where files are stored, and how they are served to users before getting in touch.

Frequently Asked Questions

What is the safest way to store uploaded files in PHP?
Store uploaded files outside the web root in a directory that is not directly accessible to the web server. Generate a random filename and keep a database record that maps the random name to the original filename and metadata. Serve files through a PHP script that checks authorisation before delivering them to the user.
Can I rely on file extension checking alone?
No. File extensions can be forged or disguised. A file named image.php.jpg might pass a basic extension check while still containing executable code. Always combine extension checking with MIME type verification using finfo_file and content validation appropriate to your use case.
Should I disable file uploads entirely if my site does not need them?
If your application does not require file uploads, disabling file_uploads = Off in PHP removes the attack surface completely. This is the safest option for sites that do not need to receive files from users.
How do I serve uploaded files without exposing them publicly?
Store files in a private directory outside the web root. Create a PHP script that reads the requested file and outputs it with appropriate headers. The script should validate the user's permission to access the file before delivering it. This prevents direct URL access to uploaded files.
What PHP settings should I check for file upload security?
Review upload_max_filesize, post_max_size, max_file_uploads, max_execution_time, and memory_limit. These should be set to values appropriate for your application without being unnecessarily high. Also check file_uploads to confirm it is only enabled on pages that need it.
How often should I review my file upload implementation?
Review your file upload configuration when you make significant changes to your application, switch hosting providers, or update PHP or web server versions. Periodic reviews every six to twelve months are sensible for active business websites, particularly if they handle sensitive documents or operate in regulated industries.