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

Least Privilege: Permissions of the Web Server User

Injected code runs with the rights of the web server user. What this account may do decides how far an attacker gets. Here is how to limit shell, sudo, cron, database and file system to what is necessary.

Last updated 6 min readHardening and configuration

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 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. The following commands only read:

# 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.

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.

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.

-- 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.

# 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, 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

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