Edit: I just noticed your "needs to scale to ~2000 users" requirement... this might not be your best option, but could probably be easily automated with a bit of scripting.
You could use php-fpm to do something like this (fpm is part of the PHP since PHP 5.3.3. I host a couple of sites on my VPS, and use something similar.
My main php-fpm.conf looks like:
;;;;;;;;;;;;;;;;;;;;;
; FPM Configuration ;
;;;;;;;;;;;;;;;;;;;;;
include=/usr/local/etc/fpm.d/*.conf
;;;;;;;;;;;;;;;;;;
; Global Options ;
;;;;;;;;;;;;;;;;;;
[global]
; Pid file
; Default Value: none
pid = /var/run/php-fpm.pid
; Error log file
; Default Value: /var/log/php-fpm.log
error_log = /var/log/php-fpm.log
; Log level
; Possible Values: alert, error, warning, notice, debug
; Default Value: notice
;log_level = notice
; If this number of child processes exit with SIGSEGV or SIGBUS within the time
; interval set by emergency_restart_interval then FPM will restart. A value
; of '0' means 'Off'.
; Default Value: 0
;emergency_restart_threshold = 0
; Interval of time used by emergency_restart_interval to determine when
; a graceful restart will be initiated. This can be useful to work around
; accidental corruptions in an accelerator's shared memory.
; Available Units: s(econds), m(inutes), h(ours), or d(ays)
; Default Unit: seconds
; Default Value: 0
;emergency_restart_interval = 0
; Time limit for child processes to wait for a reaction on signals from master.
; Available units: s(econds), m(inutes), h(ours), or d(ays)
; Default Unit: seconds
; Default Value: 0
;process_control_timeout = 0
; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging.
; Default Value: yes
;daemonize = yes
And then, in the fpm.d folder, I have configuration files for each site like this:
[myuser]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1
user = myuser
group = myuser
pm = dynamic
pm.max_children = 15
pm.start_servers = 3
pm.min_spare_servers = 1
pm.max_spare_servers = 5
pm.max_requests = 2000
request_slowlog_timeout = 5
slowlog = /home/myuser/tmp/logs/myuser.slow.log
php_admin_value[error_log] = /home/myuser/tmp/logs/myuser.error.log
php_admin_flag[log_errors] = on
Then, for each site, you change the user and the port in their own file, and in the nginx config, you'd have something like:
location ~ .*.php$ {
include /usr/local/etc/nginx/fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Changing the port in the fastcgi_pass directive.
On the specific topic of using exec in php and how to be safe, The only thing you need to take care of is to escape arguments.
i.e. don't do this:
exec('myscript ' . $_POST['arg']);
if it's not obvious - just think about what happens if $_POST['arg']
contains ; rm xyz
- or worse, if you are sending the output of that command to the screen and it contains ; more /etc/passwd; more db-config.php
etc.
therefore - escape your arguments:
$foo = escapeshellarg($whatever);
exec("myscript $foo");
However, if your bash script requires more privileges than you would ordinarily permit your nginx user to have - You are better of heeding the advice you have found which disassociates the user running your bash script from the nginx user.
Similar to your php daemon suggestion in the question, the php app sends a request for the bash script to run, synchronously or asynchronously, using a job queue system. The job queue just sends the request to your bash script, optionally returning the result. In this way you can start the "create new user" bash script as a user with appropriate permissions, without giving your nginx user any extra privileges and risking these permissions being exploited somewhere else if your php application code. Gearman in particular, is very easy to setup.
Best Answer
With bare nginx, you cannot do it based on file ownership only.
PHP-FPM is the script execution engine that is used with nginx.
In PHP-FPM, there are PHP worker pools, which can be set up in different ports. Each PHP-FPM worker can have a user that is used to execute scripts for that pool.
Then, in nginx side, one configures rules which PHP-FPM backend is used for which request. The normal configuration is that each virtual host on nginx uses its own PHP-FPM worker pool.
With bare nginx, it is not possible to check file ownership so that backend location could be selected with that information. nginx
lua
module could be used for this purpose though.