# Least Privilege: Permissions of the Web Server User

> Give the web server user minimal rights: no shell, no sudo, no cron, narrow database privileges and systemd protection for PHP-FPM, with check commands.

Source: https://phpsos.de/en/blog/rechte-des-webserver-benutzers/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** The web server user that PHP runs as should, following the principle of least privilege, read only what the website needs and write only where it stores data. It has no login shell, no sudo rights, no cron jobs of its own and no SSH keys, and its database account may only read and change data. systemd options such as `ProtectSystem` and `PrivateTmp` confine PHP-FPM further.

## The Question After the Break-In: What May This Process Do?

PHP2Shell is about executing malicious code remotely without logging in. If that succeeds, the code does not run in a vacuum but as one specific user: the user of the PHP process. Everything this user may do, the attacker may do from that moment on.

On many servers that is surprisingly a lot. The user owns the entire program code, has a shell, can create its own cron jobs, and the application's database account has all rights on all databases. For an attacker, that is the ideal starting position for establishing a permanent foothold.

The guide [File Permissions on the Web Server](https://phpsos.de/en/ratgeber/dateirechte-webserver/) describes how to assign files and folders correctly. This article looks at the account itself: login, special rights, scheduled tasks, database and the limits that systemd sets.

## Taking Stock: Who Is the Web Server User?

First, clarify which users are involved at all. With PHP-FPM, each pool defines with `user` and `group` who its workers run as. With mod_php, PHP runs as the Apache user. How master and workers interact in PHP-FPM is explained in [PHP-FPM Explained: Process Manager, Pools and Workers](https://phpsos.de/en/php/php-fpm-erklaert/). The following commands only read:

```bash
# Which users do the web server and PHP run as?
ps -eo user,group,cmd | grep -E "[p]hp-fpm|[n]ginx|[a]pache2|[h]ttpd" | sort | uniq -c
# Account details: home directory and shell (adjust the user)
getent passwd www-data
# Does the user have sudo rights?
sudo -l -U www-data
# Own cron jobs?
sudo crontab -l -u www-data
# SSH keys in the home directory (on Debian/Ubuntu this is /var/www)
sudo ls -la /var/www/.ssh 2>/dev/null
```

Note the results for each pool user. If all websites run under the same user, that is the first item on the list; how to separate them is described in [Isolating PHP-FPM Pools per Website](https://phpsos.de/en/blog/php-fpm-pools-isolieren/).

## Web Server User Rights: What the Account Does Not Need

The following overview shows what a web server user typically does not need and how to withdraw it.

| Right | Why it is dangerous | Recommendation |
|---|---|---|
| Login shell | Login and interactive use possible | Shell `/usr/sbin/nologin` |
| sudo entries | Path to root rights | no entries, not even for single commands |
| own crontab | permanent backdoor that survives every reboot | none, restrict via `/etc/cron.allow` |
| SSH keys | login from outside | no `authorized_keys` |
| Ownership of the program code | code can be modified | owner is a deploy user |
| Write rights outside data folders | placement of malicious code | only uploads, cache, temp |
| full database rights | databases of other applications, file access via the database | only its own database, only data rights |

You set the shell with `usermod -s /usr/sbin/nologin www-data`. For cron: if `/etc/cron.allow` exists, only the users listed there may create their own cron jobs. Newly created cron jobs or unknown SSH keys are a typical sign that someone has gained a foothold. A particularly clear warning sign is described in the article [Warning Sign: The Web Server Starts a Shell](https://phpsos.de/en/blog/webserver-startet-shell/).

## The Database: Limit Rights to What Is Necessary

A website's database account is effectively a second web server user. Its credentials sit in the application's configuration file and are readable by injected code. It should therefore be allowed to do only what the application needs in operation.

```sql
-- Example MySQL/MariaDB: separate account per website, data rights only
CREATE USER 'seite1'@'localhost' IDENTIFIED BY 'hier-ein-langes-Zufallspasswort';
GRANT SELECT, INSERT, UPDATE, DELETE ON seite1.* TO 'seite1'@'localhost';
-- Check the rights
SHOW GRANTS FOR 'seite1'@'localhost';
```

An application account does not need rights such as `FILE`, `SUPER` or `GRANT OPTION`, or access to `*.*`. Many CMSs additionally need `CREATE`, `ALTER`, `INDEX` and `DROP` for updates. Cleaner is a separate account for updates and migrations that is used only during maintenance. Check the requirements in the vendor's documentation.

## Confining PHP-FPM Further with systemd

systemd can restrict a service in its view of the system. For PHP-FPM, a few options from the systemd documentation are particularly suitable. They also apply to the master process, which starts as root.

```ini
# sudo systemctl edit php8.3-fpm
[Service]
# /usr, /boot and /etc read-only
ProtectSystem=full
# /home, /root and /run/user invisible
ProtectHome=true
# own /tmp and /var/tmp for this service only
PrivateTmp=true
# no privilege escalation via setuid or file capabilities
NoNewPrivileges=true
```

Test these options before production use. If websites are located under `/home`, `ProtectHome=true` blocks access. `NoNewPrivileges=true` can disturb programs that PHP calls and that change their group rights at startup, such as some local mail programs for the PHP function `mail()`. So proceed in steps:

1. Add one option, restart the service, check `systemctl status php8.3-fpm`.
2. Test the website's core functions: login, forms, mail delivery, uploads, the application's cron jobs.
3. Evaluate the error log of PHP-FPM and the application.
4. Only then add the next option.

Finally, the socket: according to the PHP manual, it receives rights `0660` for the user and group of the pool by default. Set `listen.owner` and `listen.group` so that only the web server can connect, not every local user.

> **Tip:**
> Minimal rights are an attitude, not a one-off project. Repeat the inventory from this article after every rebuild and every new website, because new services often come with generous default rights.

## Conclusion: The Attacker Inherits What You Allow

Least privilege sounds like theory, but it is a very practical question: what can someone do who executes code as the web server user? On a server with a shell, a sudo entry, its own crontab and a database account with all rights, the answer is: almost everything. On a confined server, it is more likely to remain an incident in a single website that can be detected and cleaned up.

The changeover needs no new software, only a careful inventory and step-by-step tests. Start with the points that can hardly break anything, namely shell, sudo and SSH keys, and then work your way up to database rights and systemd options.

## How does PHP SOS help?

- **Review accounts:** For each pool and web server, we record user, shell, sudo entries, cron jobs, SSH keys and database rights.
- **Reduce rights:** We withdraw what is not needed and separate deploy, runtime and maintenance accounts.
- **Confine services:** We apply systemd protection options step by step and test the website after each step.
- **Keep it that way:** As part of [server hardening](https://phpsos.de/en/leistungen/server-haertung/), we document the permissions concept for future changes.


## Frequently asked questions

**Which user does PHP run as on my server?**  
That depends on the operating mode. With mod_php, PHP runs as the Apache user, such as www-data or apache. With PHP-FPM, each pool defines its user with user and group; the master process usually runs as root, the workers as the pool user. The ps command shows this quickly.

**Does the web server user need a login shell?**  
No. Neither Nginx nor Apache nor PHP-FPM needs an interactive login for this user. A shell such as /usr/sbin/nologin prevents anyone from logging in with this account. Deployments run through a separate user.

**Why no sudo rights for www-data, not even for a single command?**  
Because injected code inherits exactly these rights. Even a seemingly harmless command, if released incorrectly, can lead to root rights. Tasks that need elevated rights belong in separate services or processes outside the web server.

**Does this help against PHP2Shell?**  
It does not close the vulnerability; that takes the PHP update and a restart of all workers. Minimal rights do, however, limit what an attacker can do after a successful exploit: change files, gain persistence, reach other websites.

## Sources

- [PHP manual: FPM configuration (user, listen.mode)](https://www.php.net/manual/en/install.fpm.configuration.php)
- [systemd.exec (ProtectSystem, PrivateTmp, NoNewPrivileges)](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html)
- [BSI IT-Grundschutz (German IT baseline protection)](https://www.bsi.bund.de/DE/Themen/Unternehmen-und-Organisationen/Standards-und-Zertifizierung/IT-Grundschutz/it-grundschutz_node.html)
- [OWASP Cheat Sheets](https://cheatsheetseries.owasp.org/)
