Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

Forbidding PHP Execution in Upload Folders

A webshell needs two things: a folder it can be written to and a server that executes it. Here is how to take away the second, in every writable folder, not just uploads.

Last updated 6 min readHardening and configuration

In short: To forbid PHP execution in uploads, first determine all folders PHP may write to, and then block script execution in each of them in the server configuration, not via .htaccess. In Nginx the block must take effect before the general PHP block; in Apache it belongs in the VirtualHost with AllowOverride None. security.limit_extensions = .php in the FPM pool and a test with a harmless file complete the work.

Why the upload folders matter right now

After a critical vulnerability like PHP2Shell, attackers rarely want to execute code only once. They want to come back, even if the server is patched later. The easiest way is a webshell: a small PHP file, placed in a writable folder and called later through the browser.

A patch does not remove such a file. Anyone who was online unpatched from 20 October 2026 should therefore not only update but also check whether new PHP files are lying around anywhere, and prevent them from being executed in future. How to interpret finds is described in PHP files in the upload folder: interpreting finds correctly.

The execution block addresses the second condition. It does not prevent a file from being placed, but it prevents the server from treating it as a program.

Inventory first: where may PHP write?

Most guides name only the upload folder. In practice, a PHP application writes in many more places. Each of them that is reachable through the web server needs the block.

Folder type Typical content Why writable
Uploads, media images, PDFs, attachments users and editors upload files
Cache generated pages, thumbnails application speeds up delivery
Temp, import, export CSV files, intermediate states interfaces and data exchange
Backups in the web root .zip, .sql extensions back up “on site”
Logs in the web root error and debug logs misconfigured applications

Which folders these are on your server is shown by a search with the runtime user's permissions. It changes nothing:

# All folders under /var/www the PHP user may write to
# (adapt the user: www-data, nginx, apache or your own pool user)
sudo -u www-data find /var/www -type d -writable 2>/dev/null | sort
# Are there already PHP files in these folders?
sudo -u www-data find /var/www -type d -writable -print0 2>/dev/null \
  | xargs -0 -I{} find {} -maxdepth 1 -type f -iname "*.ph*" 2>/dev/null

If the first search returns the entire web root, the program code belongs to the runtime user. You should change that beforehand, as described in the guide File permissions on the web server. Otherwise there is no folder that would not need blocking.

Forbidding PHP execution in uploads: Nginx

With Nginx, the order decides. According to the Nginx documentation, regular expressions are checked in the order of the configuration file, and the first match wins. If the general block location ~ \.php$ comes before your block, the file is executed. A simple prefix location without ^~ does not protect for the same reason.

Safe is a regular expression for all blocked folders that comes before the PHP block. It also covers rarely used extensions:

server {
    # ... basic configuration ...

    # 1. Block for writable folders, MUST come before the PHP block
    location ~* ^/(uploads|media|cache|tmp|export)/.*\.(php\d?|phtml|phar|pht|phps)$ {
        deny all;
    }

    # 2. General PHP block: pass only existing files
    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/seite1.sock;
    }
}

Adapt the folder names to your inventory. For applications with a single entry point, the allowlist route is even stronger; the pattern is shown in Configuring Nginx for PHP securely.

Apache and PHP-FPM: the block in the right place

In Apache the block belongs in the server configuration or the VirtualHost. An .htaccess in the upload folder is unsuitable because anyone who may write there can change it. For the entire web root, AllowOverride None therefore applies. According to the Apache documentation, AllowOverride is allowed only in <Directory> blocks without regular expressions, so set it at the web root and the block via <DirectoryMatch> below it:

<Directory /var/www/seite1/public>
    AllowOverride None
    Require all granted
</Directory>

<DirectoryMatch "^/var/www/seite1/public/(uploads|media|cache|tmp|export)/">
    <FilesMatch "\.(php\d?|phtml|phar|pht|phps)$">
        Require all denied
    </FilesMatch>
</DirectoryMatch>

If mod_php is still running, add php_admin_flag engine off in the same block. With PHP-FPM, a second barrier works in the pool: security.limit_extensions defines which extensions FPM executes at all. According to the PHP manual the default is .php .phar; limiting to .php is recommended.

; in the pool, e.g. /etc/php/8.3/fpm/pool.d/seite1.conf
security.limit_extensions = .php

Testing with a harmless file

Only a test shows whether the block works. Briefly place a file in each blocked folder that merely outputs a text, call it via the domain and delete it afterwards.

  1. Check the configuration: nginx -t or apachectl configtest, then reload.
  2. Create a test file, for example uploads/sperrtest.php with the content <?php echo 'ausgefuehrt';.
  3. Call it with curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/uploads/sperrtest.php. The expected result is 403.
  4. If 200 and the text “ausgefuehrt” (German for “executed”) appear instead, the block does not take effect. Check configuration and order.
  5. Delete the test file and repeat the test for every folder and every website.
Important

If the inventory already turns up unknown PHP files in writable folders, do not delete anything hastily. First secure the state and the logs, because these files are evidence. How to proceed then is described in Recognizing and removing webshells.

How upload functions are built securely in code, with type checks and storage outside the web root, is covered in the guide Implementing file uploads in PHP securely.

Conclusion: write yes, execute no

The execution block in writable folders is one of the most effective single measures against permanent backdoors. It costs a few lines of configuration and breaks nothing in cleanly built applications. Three things are decisive: a complete inventory instead of just the obvious upload folder, the right order in Nginx or the right level in Apache, and a real test with a harmless file.

Complemented by separate owners for the program code, security.limit_extensions in the pool and a regular search for new PHP files, the result is a state in which a planted file is annoying but hardly usable. This replaces neither the update nor the search for traces, but it takes away one of the attackers' most important tools.

How does PHP SOS help?

  • Inventory: We determine all writable folders per website and check them for existing PHP files.
  • Set blocks: We enter the execution blocks in Nginx, Apache and PHP-FPM, in the right order and without dependence on .htaccess.
  • Test: We check every block with a harmless file and document the result.
  • Clean up: If we find something, we take over evidence preservation and malware removal.

Frequently asked questions

Is an .htaccess in the upload folder enough to forbid PHP?

Not reliably. Anyone who can write to the folder can also change or replace the .htaccess. The block belongs in the server configuration or the VirtualHost, and AllowOverride None should apply to the web root.

Which folders do I need to block?

All folders the PHP user may write to and that are reachable through the web server. Besides upload folders these are often cache, temp, import, export and backup directories. Determine them with a search run as the runtime user.

Will the block break my website?

Usually not, because in upload and cache folders of cleanly built applications there are no scripts that are called directly. Some extensions do place PHP files there, though. So test the most important functions after the changeover.

Does the block protect against PHP2Shell?

No. PHP2Shell is a vulnerability in the PHP interpreter, closed only by the update and the restart of all workers. The block does prevent a planted file from later being called through the browser and used as a permanent backdoor.

Sources

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help