gem version

Sandboxing apps with Unix user accounts (user switching)

Relevant selection for this article:

Nginx

When running web applications with Passenger, we recommend that you make use of the user account sandboxing feature, also known as user switching. This means (if you run multiple applications on a server) running each application under its own operating system user account, instead of running them all as the same user. Doing so improves the overall security of the system.

To better understand the problem, let us consider the situation with PHP. There is a problem that plagues most PHP web hosts, namely the fact that all PHP applications are run in the same user context as the web server. So for example, Joe's PHP application will be able to read Jane's PHP application's passwords. This is obviously undesirable on many servers.

Passenger's user account sandboxing feature solves this problem. This feature makes it very easy to run each application as its own operating system user. Assuming that you have correctly secured your files with the right filesystem permissions, user account sandboxing stops or limits the impact of certain classes of vulnerabilities. Going back to our example, if Joe's application has been hijacked through a vulnerability (or is intentionally malicious), then it won't be able to access Jane's passwords, assuming that Jane has secured her password files with the right permissions.

Table of contents

  1. Loading...

How it works

If the user account sandboxing feature is enabled, Passenger will need to know what user / group to run the application as. This is determined as follows:

  1. Passenger tries to set the user and group name that you specify in passenger_user and passenger_group.

  2. If the options above are not specified, Passenger tries to autodetect by looking at the owner of the application's "startup file".

  3. If autodetection fails, or if the owner of the startup file is the root user, Passenger will set the user and group name specified in passenger_default_user and passenger_default_group instead (default: nobody).

Don't rely on autodetection if users are untrusted (e.g. shared hosting). If a user somehow manages to specify a startup file that is owned by another user, Passenger will launch the startup file as that other user (it cannot check whether this is an allowed user or not).

When is user account sandboxing enabled?

Not using Flying Passenger

In most installations user account sandboxing is already enabled. Assuming you are not using the Flying Passenger mode (this is probably the case), the following table shows the exceptions:

passenger_user_switching on (default)
passenger_user_switching off

Web server has root privileges User account sandboxing enabled. User account sandboxing disabled. Apps are run as passenger_default_user and passenger_default_group.
Web server has no root privileges User account sandboxing disabled. Apps are run as Nginx's user. User account sandboxing disabled. Apps are run as Nginx's user.

Using Flying Passenger

When using Flying Passenger, the effect is as follows:

Daemon run with root privileges User account sandboxing enabled.
Daemon run without root privileges User account sandboxing disabled. Apps are run as the daemon's user.

Caveats & troubleshooting

If your application regularly encounters permission errors or fails to find certain files, then this is an indication that your application is started as a user that you did not intent it to be run as. Other symptoms include:

  • The application fails to start because Bundler complains that it cannot find gems. This probably indicates that Bundler does not have read access to the directory that contains Bundler-installed gems.
  • The application fails to start and its error message mentions the path /nonexistent. This probably indicates that your application is started as the nobody user. This is because on many systems, the nobody user's home directory is /nonexistent.

To check whether it is indeed the case that your application is started as a different user than you intended to, see Finding out what user an application is running as.

The most likely reason why your application is started as nobody is probably because your startup file is owned by root, by nobody or by an unknown user. To fix this, change the owner of the startup file to the owner that you want to run the application as.

Whatever user your application runs as, it must have read access to the application root, and read/write access to the application's logs directory.

If you are setting up shared hosting, please carefully consider the security and privacy implications of allowing the user switching to be user controlled.

Enterprise Linux (Red Hat, CentOS, Rocky, Alma) caveats

This information only applies if you installed Passenger through the RPM packages provided by Phusion. If you did not installed Passenger through the RPM packages provided by Phusion, then you can ignore this section.

If you installed Passenger through Phusion's YUM repository, and you want to disable user account sandboxing, then you must also change the location of the instance registry directory.

This is because our RPMs configure the default instance registry directory to /var/run/passenger-instreg, which is only writable by root. If you disable user switching, then the Passenger processes will run as passenger_default_user which (as long as it's not root) won't be able to write to that directory.

Note that any alternative instance registry directory must have the proper SELinux context, allowing the web server to read and write to it. We recommend that you create a directory /var/lib/passenger-instreg and give it the label var_run_t:

$ sudo mkdir /var/lib/passenger-instreg
$ sudo chcon -t var_run_t /var/lib/passenger-instreg

Then, in your Nginx config file:

passenger_instance_registry_dir /var/lib/passenger-instreg;

Finding out what user an application is running as

To find our what user an application is started as, first access its URL in your browser so that Passenger starts the application. For example:

$ curl http://www.example.local/

The application will now either successfully start or fail to start. If it fails to start then you will see an error page that tells you what user the application was being started as. If you do not see the error page in the browser then set passenger_friendly_error_pages on.

If the application successfully started, then run passenger-status to find the process's PID:

---------- General information -----------
Max pool size : 6
Processes     : 1
Requests in top-level queue : 0

---------- Application groups -----------
/webapps/example.local#default:
  App root: /webapps/example.local
  Requests in queue: 0
  * PID: 16915   Sessions: 0       Processed: 1       Uptime: 2s
    CPU: 0%      Memory  : 9M      Last used: 2s ago

In the above example we see that the PID is 16915. Next, use 'ps' to find out the user that it is running as:

$ ps -o pid,user,comm -p 16915
  PID USER    COMM
16915 phusion Passenger RackApp: /webapps/example.local

As you can see, the application in this example is being run as user phusion.

light mode dark mode
Passenger 6 Passenger 6