For the purpose of this write up, I am demonstrating the configuration as they are on a FreeBSD 9.0 server. The ‘www’ user and group are often referred to as ‘www-data’ on most linux hosts. For saftey Nginx and PHP-FPM are run as www:www, with ownership of the web files belonging to an unprivileged user and permissions set accordingly.
On most of my servers my web files are owned by an unprivileged user that belongs to the www group, and I tend to run nginx and php as www:www. Running PHP as said user:www can be quite risky since most files have read/write/execute permission set on the owner bit. (in octal permissions it’s UGO – User/Owner-Group-Other, such as 750 = owner(read/write/excute) + group (read/execute) + other (none))
In this write up I am simply going to show you the configuration of second a second smaller PHP-FPM pool to act as an ‘elevated’ process running as the folder’s owner, thus allowing far more control over the files and folders such as upgrading wordpress or it’s plugins, but not allow any other files not matching the pattern to do the same.
First thing we need to do is add a second pool to the php-fpm.conf, on FreeBSD installed from ports it will be located at /usr/local/etc/php-fpm.conf, on other systems such as debian or ubuntu, it may be located as /etc/php5/fpm/pool.d/default or similar (look for the config with the [www] pool in it).
This configuration has been stripped down quite a bit with some commented options shown for example purposes.
[global] pid = run/php-fpm.pid [www] user = www group = www listen = /tmp/php.sock listen.owner = www listen.group = www listen.mode = 0666 ;listen is not required with sockets ;listen.allowed_clients = 127.0.0.1 pm = static pm.max_children = 4 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 pm.max_requests = 1024 ;chroot = ;chdir = /var/www ;catch_workers_output = yes ;security.limit_extensions = .php .php3 .php4 .php5 ;env[HOSTNAME] = $HOSTNAME ;env[PATH] = /usr/local/bin:/usr/bin:/bin ;env[TMP] = /tmp ;env[TMPDIR] = /tmp ;env[TEMP] = /tmp ;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com ;php_flag[display_errors] = off ;php_admin_value[error_log] = /var/log/fpm-php.www.log ;php_admin_flag[log_errors] = on ;php_admin_value[memory_limit] = 32M [elevated] user = kbeezie group = www listen = /tmp/php-elevated.sock listen.owner = www listen.group = www listen.mode = 0666 ;listen.allowed_clients = 127.0.0.1 pm = static pm.max_children = 2 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 pm.max_requests = 1024
In the above I have two pools set up, the first one being a typical 4-process (with static, min/max_*servers values are ignored) running as www:www, followed by a second pool named elevated running with 2 processes with the user/group of kbeezie:www (the web files are owned by kbeezie:www).
You’ll notice from some of the commented values above, that you can tweak specific configurations for each pools as needed, such as needing more memory or being locked into a specific folder.
Now in a very simple server block on Nginx for wordpress we need to add a couple location blocks using a regular expression location (this way it overrides the usual php location block when placed ahead of it).
server { listen 80; server_name mywebsite.com; root /home/kbeezie/www/mywebsite.com; location / { try_files $uri $uri/ /index.php; } # matches for update.php or update-core.php in /wp-admin/ location ~ ^/wp-admin/(?:update|update-core)\.php$ { include php-elevated.conf; } include php.conf; include drop.conf; }
I tend to use config files for my PHP inclusion. For example here are both the php.conf and php-elevated.conf located in the same folder as nginx.conf:
location ~ \.php$ { try_files $uri =404; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_pass unix:/tmp/php.sock; }
location ~ \.php$ { try_files $uri =404; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_pass unix:/tmp/php-elevated.sock; }
The only difference you’ll notice is that php-elevated.conf connects to the elevated socket rather than the usual one.
Now once we’ve restarted Nginx and PHP-FPM with the new configuration, when you attempt to update wordpress or one of it’s plugins, the script will have an elevated permission due to running as the files/folder’s owner, while any other scripts (as called by the browser) will run with unprivileged www:www status.
The drop.conf is simply a number of locations that should normally be dropped from public requests, I paste it here for reference:
# the first line can normally be omitted for wordpress, as robots.txt is normally dynamic location = /robots.txt { access_log off; log_not_found off; } location = /favicon.ico { access_log off; log_not_found off; } location ~ /\. { access_log off; log_not_found off; deny all; } location ~ ~$ { access_log off; log_not_found off; deny all; }
Cavets
Bear in mind the above configuration are for example purposes, there are quite a few smaller things to keep in mind such as hardening your configuration for more security concerns or optimizing it for performance. For some examples of typical Nginx configurations see : Nginx Configuration Examples