# π Per-Site PHP Upload Guard Integration Guide
This guide explains how to integrate a global PHP upload monitoring script
using `auto_prepend_file`, on a **per-site basis**, with isolated security
folders.
---
## π 1. Recommended Folder Structure
Each website should contain its own hidden security directory:
```
/var/www/sites/example-site/
βββ public/
βββ app/
βββ uploads/
βββ .security/
β βββ upload_guard.php
β βββ logs/
β βββ uploads.log
````
Benefits:
- Per-site isolation
- Easier debugging
- Independent log files
- Reduced attack surface
---
## π§ 2. Create the Security Directory
From the site root:
```bash
cd /var/www/sites/example-site
mkdir .security
mkdir .security/logs
````
Set secure permissions:
```bash
chown -R root:www-data .security
chmod 750 .security
chmod 750 .security/logs
```
---
## π 3. Install the Upload Guard Script
Create the script file:
```bash
nano .security/upload_guard.php
```
Paste your hardened upload monitoring script.
Inside the script, configure logging:
```php
$logFile = __DIR__ . '/logs/uploads.log';
```
Lock the script:
```bash
chown root:root .security/upload_guard.php
chmod 644 .security/upload_guard.php
```
---
## βοΈ 4. Enable auto_prepend_file (Per Site)
### Option A β PHP-FPM Pool (Recommended)
Edit the siteβs PHP-FPM pool configuration:
```bash
nano /etc/php/8.x/fpm/pool.d/example-site.conf
```
Add:
```ini
php_admin_value[auto_prepend_file] = /var/www/sites/example-site/.security/upload_guard.php
```
Reload PHP-FPM:
```bash
systemctl reload php8.x-fpm
```
# π Per-Site PHP Upload Guard Integration Guide
This guide explains how to integrate a global PHP upload monitoring script
using `auto_prepend_file`, on a **per-site basis**, with isolated security
folders.
---
## π 1. Recommended Folder Structure
Each website should contain its own hidden security directory:
```text
/var/www/sites/example-site/
βββ public/
βββ app/
βββ uploads/
βββ .security/
β βββ upload-logger.php
β βββ logs/
β βββ uploads.log
```
Benefits:
- Per-site isolation
- Easier debugging
- Independent log files
- Reduced attack surface
---
## π§ 2. Create the Security Directory
From the site root:
```bash
cd /var/www/sites/example-site
mkdir .security
mkdir .security/logs
```
Set secure permissions:
```bash
chown -R root:www-data .security
chmod 750 .security
chmod 750 .security/logs
```
---
## π 3. Install the Upload Guard Script
Create the script file:
```bash
nano .security/upload-logger.php
```
Paste your hardened upload monitoring script.
Inside the script, configure logging:
```php
$logFile = __DIR__ . '/logs/uploads.log';
```
Lock the script:
```bash
chown root:root .security/upload-logger.php
chmod 644 .security/upload-logger.php
```
---
## βοΈ 4. Enable auto_prepend_file (Per Site)
### Option A β PHP-FPM Pool (Recommended)
Edit the siteβs PHP-FPM pool configuration:
```bash
nano /etc/php/8.x/fpm/pool.d/example-site.conf
```
Add:
```ini
php_admin_value[auto_prepend_file] = /var/www/sites/example-site/.security/upload-logger.php
```
Reload PHP-FPM (adjust service name to match your PHP version):
```bash
systemctl reload php8.x-fpm
```
---
### Option B β Apache Virtual Host
If using a shared PHP-FPM pool, configure in the vHost:
```apache
php_admin_value auto_prepend_file /var/www/sites/example-site/.security/upload-logger.php
```
Reload Apache:
```bash
systemctl reload apache2
```
---
## π« 5. Block Web Access to `.security`
Prevent direct HTTP access to the security folder.
In the vHost:
```apache
Require all denied
```
Or in `.htaccess` (if allowed):
```apache
Require all denied
```
---
## β 6. Verify Installation
Create a temporary file:
```php
```
Upload any file and check logs:
```bash
cat .security/logs/uploads.log
```
You should see a new entry.
---
## π 8. Disable PHP Execution in Uploads
Always block PHP execution in upload directories.
Example (Apache):
```apache
php_admin_flag engine off
AllowOverride None
```
Reload Apache after changes.
---
## π‘οΈ 9. Enable Blocking Mode (Optional)
After monitoring for some time, enable blocking.
Edit:
```php
$BLOCK_SUSPICIOUS = true;
```
Then reload PHP-FPM.
---
## π 10. (Optional) Fail2Ban Integration (JSON logs)
Create a JSON-aware filter that matches `event: "suspicious"` and extracts the IP address.
```bash
nano /etc/fail2ban/filter.d/php-upload.conf
```
```ini
[Definition]
# Match JSON lines where event == "suspicious" and capture the IPv4 address as
failregex = ^.*"event"\s*:\s*"suspicious".*"ip"\s*:\s*"(?P\d{1,3}(?:\.\d{1,3}){3})".*$
ignoreregex =
```
Create a jail that points to the per-site logs (or a central aggregated log):
```ini
[php-upload]
enabled = true
filter = php-upload
logpath = /var/www/sites/*/.security/logs/uploads.log
maxretry = 3
findtime = 600
bantime = 86400
action = iptables-multiport[name=php-upload, port="http,https", protocol=tcp]
```
Restart Fail2Ban:
```bash
systemctl restart fail2ban
```
### Fail2Ban action: nftables example
If your host uses nftables, prefer the `nftables` action so bans use the system firewall:
```ini
[php-upload]
enabled = true
filter = php-upload
logpath = /var/www/sites/*/.security/logs/uploads.log
maxretry = 3
findtime = 600
bantime = 86400
action = nftables[name=php-upload, port="http,https", protocol=tcp]
```
This uses Fail2Ban's `nftables` action (available on modern distributions). Adjust `port`/`protocol` to match your services.
### Central log aggregation (Filebeat / rsyslog)
Forwarding per-site JSON logs to a central collector simplifies alerts and Fail2Ban at scale. Two lightweight options:
- Filebeat prospector (send to Logstash/Elasticsearch):
```yaml
filebeat.inputs:
- type: log
paths:
- /var/www/sites/*/.security/logs/uploads.log
json.keys_under_root: true
json.add_error_key: true
fields:
source: php-upload-logger
output.logstash:
hosts: ["logserver:5044"]
```
- rsyslog `imfile` forwarding to remote syslog (central rsyslog/logstash):
Add to `/etc/rsyslog.d/10-upload-logger.conf`:
```text
module(load="imfile" PollingInterval="10")
input(type="imfile" File="/var/www/sites/*/.security/logs/uploads.log" Tag="uploadlogger" Severity="info" Facility="local7")
*.* @@logserver:514
```
Both options keep JSON intact for downstream parsing and reduce per-host Fail2Ban complexity.
### Testing your Fail2Ban filter
Create a temporary file containing a representative JSON log line emitted by `upload-logger.php` and run `fail2ban-regex` against your filter to validate detection.
```bash
# create test file with a suspicious event
cat > /tmp/test_upload.log <<'JSON'
{"ts":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","event":"suspicious","ip":"1.2.3.4","user":"guest","name":"evil.php.jpg","real_mime":"application/x-php","reasons":["bad_name","php_payload"]}
JSON
# test the filter (adjust path to filter if different)
fail2ban-regex /tmp/test_upload.log /etc/fail2ban/filter.d/php-upload.conf
```
`fail2ban-regex` will report how many matches were found and display sample matched groups (including the captured ``). Use this to iterate on the `failregex` if it doesn't extract the IP as expected.
---
## π Final Architecture
```text
Client β Web Server β PHP (auto_prepend) β Application β Disk
β
Log / Alert / Ban
```
This provides multi-layer upload monitoring and protection.
---
## ποΈ Log rotation & SELinux/AppArmor notes
- Example `logrotate` snippet to rotate per-site logs weekly and keep 8 rotations:
```text
/var/www/sites/*/.security/logs/uploads.log {
weekly
rotate 8
compress
missingok
notifempty
create 0640 root adm
}
```
- If your host enforces SELinux or AppArmor, ensure the `.security` directory and log files have the correct context so PHP-FPM can read the script and write logs. For SELinux (RHEL/CentOS) you may need:
```bash
chcon -R -t httpd_sys_rw_content_t /var/www/sites/example-site/.security/logs
restorecon -R /var/www/sites/example-site/.security
```
Adjust commands to match your platform and policy. AppArmor profiles may require adding paths to the PHP-FPM profile.
## β οΈ Security Notes
- Never use `777` permissions
- Keep `.security` owned by `root`
- Regularly review logs
- Update PHP and extensions
- Combine with OS-level auditing for best results
---
## π Recommended Maintenance
Weekly:
```bash
grep ALERT .security/logs/uploads.log
```