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.
- Check the configuration:
nginx -torapachectl configtest, then reload. - Create a test file, for example
uploads/sperrtest.phpwith the content<?php echo 'ausgefuehrt';. - Call it with
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/uploads/sperrtest.php. The expected result is403. - If
200and the text “ausgefuehrt” (German for “executed”) appear instead, the block does not take effect. Check configuration and order. - Delete the test file and repeat the test for every folder and every website.
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.