Additionally, when viewing a topic, all forums are loaded into memory:
SELECT * FROM `forums_forums` LEFT JOIN `core_permission_index` ON core_permission_index.app='forums' AND core_permission_index.perm_type='forum' AND core_permission_index.perm_type_id=forums_forums.id LEFT JOIN `core_members` ON core_members.member_id=forums_forums.last_poster_id WHERE (sub_can_post=0 OR min_posts_view<=22) AND (( FIND_IN_SET(3,perm_view) OR FIND_IN_SET(4,perm_view) OR FIND_IN_SET('m1',perm_view) OR FIND_IN_SET('ca',perm_view) OR FIND_IN_SET('cm',perm_view) OR FIND_IN_SET('c1',perm_view) OR FIND_IN_SET('cm1',perm_view) ) OR perm_view='*' ) ORDER BY position
Additionally, when viewing a topic, all forums are loaded into memory:
SELECT * FROM `forums_forums` LEFT JOIN `core_permission_index` ON core_permission_index.app='forums' AND core_permission_index.perm_type='forum' AND core_permission_index.perm_type_id=forums_forums.id LEFT JOIN `core_members` ON core_members.member_id=forums_forums.last_poster_id WHERE (sub_can_post=0 OR min_posts_view<=22) AND (( FIND_IN_SET(3,perm_view) OR FIND_IN_SET(4,perm_view) OR FIND_IN_SET('m1',perm_view) OR FIND_IN_SET('ca',perm_view) OR FIND_IN_SET('cm',perm_view) OR FIND_IN_SET('c1',perm_view) OR FIND_IN_SET('cm1',perm_view) ) OR perm_view='*' ) ORDER BY position
Even with this enabled, the queries still pull ALL forums into memory ('club_id IS NULL' is needed). Can this be considered a bug now?
SELECT * FROM `forums_forums` LEFT JOIN `core_permission_index` ON core_permission_index.app='forums' AND core_permission_index.perm_type='forum' AND core_permission_index.perm_type_id=forums_forums.id LEFT JOIN `core_members` ON core_members.member_id=forums_forums.last_poster_id WHERE (sub_can_post=0 OR min_posts_view<=21) AND (( FIND_IN_SET(3,perm_view) OR FIND_IN_SET(4,perm_view) OR FIND_IN_SET('m1',perm_view) OR FIND_IN_SET('ca',perm_view) OR FIND_IN_SET('cm',perm_view) OR FIND_IN_SET('c1',perm_view) OR FIND_IN_SET('cm1',perm_view) ) OR perm_view='*' ) ORDER BY position
All permissions are loaded:
SELECT perm_type_id FROM `core_permission_index` STRAIGHT_JOIN `forums_forums` ON forums_forums.min_posts_view<=22 AND core_permission_index.perm_type_id=forums_forums.id WHERE core_permission_index.app='forums' AND core_permission_index.perm_type='forum' AND (( FIND_IN_SET(3,perm_2) OR FIND_IN_SET(4,perm_2) OR FIND_IN_SET('m1',perm_2) OR FIND_IN_SET('ca',perm_2) OR FIND_IN_SET('cm',perm_2) OR FIND_IN_SET('c1',perm_2) OR FIND_IN_SET('cm1',perm_2) ) OR perm_2='*' )
Continuing from this topic:
The board index (and I assume any forum listing) grabs every row from forums_forums, this could be 20k sub-forums. The board index should only grab nodes/categories with a parent ID of -1 and then any forums with those results as a parent, and sub-forums of that parent. The only reason I can think of that it's not done this way is to provide the correct "last post" info for the user's permissions. It would be nice to have an option to just limit the "last post" depth to 1 sub-forum deep and have a more performant forum.
Even with this enabled, the queries still pull ALL forums into memory ('club_id IS NULL' is needed). Can this be considered a bug now?
SELECT * FROM `forums_forums` LEFT JOIN `core_permission_index` ON core_permission_index.app='forums' AND core_permission_index.perm_type='forum' AND core_permission_index.perm_type_id=forums_forums.id LEFT JOIN `core_members` ON core_members.member_id=forums_forums.last_poster_id WHERE (sub_can_post=0 OR min_posts_view<=21) AND (( FIND_IN_SET(3,perm_view) OR FIND_IN_SET(4,perm_view) OR FIND_IN_SET('m1',perm_view) OR FIND_IN_SET('ca',perm_view) OR FIND_IN_SET('cm',perm_view) OR FIND_IN_SET('c1',perm_view) OR FIND_IN_SET('cm1',perm_view) ) OR perm_view='*' ) ORDER BY position
All permissions are loaded:
SELECT perm_type_id FROM `core_permission_index` STRAIGHT_JOIN `forums_forums` ON forums_forums.min_posts_view<=22 AND core_permission_index.perm_type_id=forums_forums.id WHERE core_permission_index.app='forums' AND core_permission_index.perm_type='forum' AND (( FIND_IN_SET(3,perm_2) OR FIND_IN_SET(4,perm_2) OR FIND_IN_SET('m1',perm_2) OR FIND_IN_SET('ca',perm_2) OR FIND_IN_SET('cm',perm_2) OR FIND_IN_SET('c1',perm_2) OR FIND_IN_SET('cm1',perm_2) ) OR perm_2='*' )
Additionally, when viewing a topic, all forums are loaded into memory:
SELECT * FROM `forums_forums` LEFT JOIN `core_permission_index` ON core_permission_index.app='forums' AND core_permission_index.perm_type='forum' AND core_permission_index.perm_type_id=forums_forums.id LEFT JOIN `core_members` ON core_members.member_id=forums_forums.last_poster_id WHERE (sub_can_post=0 OR min_posts_view<=22) AND (( FIND_IN_SET(3,perm_view) OR FIND_IN_SET(4,perm_view) OR FIND_IN_SET('m1',perm_view) OR FIND_IN_SET('ca',perm_view) OR FIND_IN_SET('cm',perm_view) OR FIND_IN_SET('c1',perm_view) OR FIND_IN_SET('cm1',perm_view) ) OR perm_view='*' ) ORDER BY position
Clubs allow forums to be added by users and as I posted earlier, they are also loaded on forum index. If clubs were enabled here, I could slowdown the entire forum.
See below
We don’t allow the listing of the sub-forums due to the performance.
https://forum.psnprofiles.com/
Nginx Server Installation Guide
This is a guide for those interested in learning how to set up a secure, high performance Invision Power Suite installation using Nginx and PHP-FPM. Everything written here was done so with a focus on security, maintainability, and adherence to best practices. The information provided is the result of years of personal experience, research and testing.
The steps listed have been tested to work on CentOS 7 and Debian Wheezy. If you run into any problems with your distribution, please submit as detailed of a report as you can below.
Warning / Requirements
This guide is intended for intermediate users and assumes you have at least a decent understanding of the Linux operating system. Please do not blindly attempt to run these commands on an active server. If you are replacing an existing Nginx installation, the easiest thing to do is make a backup of your configuration files, then purge/uninstall your existing Nginx installation before re-installing using the Nginx repository listed below.
Be responsible. Test locally before trying to set anything up in production. The information and resources below are provided "as is", with no guarantee of support, and without warranty of any kind.
1. Installing Nginx
When installing Nginx on CentOS or Debian based systems, I recommend utilizing the official Nginx repository so you can keep up to date with the latest releases straight from the developers.
Please follow the instructions on the official Nginx website to set this repository up on your system: http://nginx.org/en/linux_packages.html
After importing the repository, go ahead and install Nginx.
CentOS 7
yum install nginx
Debian / Ubuntu
apt-get update
apt-get install nginx
2. Configuring Nginx
Once you have Nginx up and running, you can move on to the configuration. Open /etc/nginx/nginx.conf and make sure it matches the following configuration,
user nginx nginx;
pid /var/run/nginx.pid;
worker_processes auto;
worker_priority -10;
events {
worker_connections 1024;
}
http {
##
# Basic Settings
##
default_type application/octet-stream;
keepalive_timeout 30;
sendfile on;
tcp_nodelay on;
tcp_nopush on;
##
# Logging Settings
##
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#access_log /var/log/nginx/access.log main;
access_log off;
error_log /var/log/nginx/error.log error;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
##
# Configuration Includes
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Notice
The "auto" worker_processes value is only supported in Nginx versions 1.2.5 (stable) / 1.3.8 (mainline) and up. If you are using an older version, you will need to manually specify a value instead.
There are just a few things to note in detail here.
First, we have changed our worker_processes from the default of 1 to auto. This will automatically assign workers equal to the number of cores your server has.
Second, we have set the worker_priority of Nginx to -10. This is a small change that simply gives your web server higher CPU scheduling priority over other processes on your server.
Finally, we have enabled the sendfile, tcp_nodelay and tcp_nopush directives to improve performance.
3. Structuring your Nginx configuration files
If you're going to be managing your configuration files manually, it's particularly important to have a good, solid configuration structure in place.
The default layout of your /etc/nginx directory may vary a bit depending on your distribution. I personally recommend setting up and using a basic structure like this:
/etc/nginx/
? conf.d - Global configuration directives to be loaded automatically when Nginx is started.
? mime_types.conf - Universal mime type associations for file extensions.
? includes - Common configuration snippets to be included in server or location blocks as needed.
? ips - Common configuration snippets designed for use on IPS installations.
? protect_upload_directories - Configuration include used to prevent execution of PHP scripts in writable upload directories.
? allow_only_admins - Configuration include used to restrict location block access to server administrators.
? cache_static_files - Contains a location block used to automatically apply cache headers to static resources.
? deny_dotfiles - Contains a location block used to deny requests to dotfiles (eg; .htpasswd).
? php_fastcgi_params - Basic FastCGI configuration for PHP-FPM.
? php_fpm_status - Contains a location block used for enabling admin only access to the php_fpm_status URI.
? stub_status - Contains a location block used for allowing admins to access basic information about the web server.
? ... - May also contain various other includes provided by Nginx and/or your distribution.
? scripts - Contains various helper scripts for common Nginx related tasks.
? create - Creates configuration and domain directories for a new virtual host.
? disable - Removes configuration symlinks for a single domain or a domain and all its subdomains.
? enable - Creates a configuration symlink for a specified domain.
? fixperms - Automatically sets/fixes file ACL permissions for a specified domain.
? provision - Run once after installing Nginx to rebuild the Nginx configuration directory.
? sites-available - Contains all available virtual host configurations. (Structure: sites-available/{domain}/{subdomain-or-root}.conf)
? default - Default/"localhost" configurations.
? root.conf - The default configuration used for unconfigured virtual hosts.
? sites-enabled - Contains symlinks to enabled virtual hosts in /etc/nginx/sites-available, (Structure: sites-available/{domain})
? default -> /etc/nginx/sites-available/default/root.conf - Default virtual host.
? ssl - Contains SSL keys and certificates for virtual hosts. Keys should be owned by root:nginx with no public access (chmod 0640).
? nginx.conf - The primary Nginx configuration file.
Info
You won't need to reconfigure everything above manually. A helper script will be provided at the end which can automatically provision everything for you.
If you've worked with Apache before, you should feel pretty at home with this setup. You store configurations for virtual hosts in sites-available, then create symlinks to those configuration files in sites-enabled to actually enable them.
To provide a quick example, here is how you would enable a virtual host manually (you won't need to do this manually if you use the helper scripts, but you should still know how),
$ ln -vs /etc/nginx/sites-available/example.org/root.conf /etc/nginx/sites-enabled/example.org
$ ln -vs /etc/nginx/sites-available/example.org/foo.conf /etc/nginx/sites-enabled/foo.example.org
In sites-available, every domain has its own directory. Inside those directories are the virtual host configurations relevant to that domain, which are named by their subdomains (or "root" for www/no subdomain).
When symlinking configurations in sites-enabled, you just use the raw domain name for the symlinks filename.
$ ls -l /etc/nginx/sites-enabled/
lrwxrwxrwx. 1 root nginx 44 Dec 20 11:23 example.org -> /etc/nginx/sites-available/example.org/root.conf
lrwxrwxrwx. 1 root nginx 44 Dec 20 11:23 foo.example.org -> /etc/nginx/sites-available/example.org/foo.conf
$ rm -i /etc/nginx/sites-enabled/foo.example.org
rm: remove symbolic link `/etc/nginx/sites-enabled/foo.example.org'? y
This way, you get the best of both worlds. When you want to see which domains are enabled, you can just list the /etc/nginx/sites-enabled directory and everything is displayed in one place. When you need to review the virtual hosts for a domain, your configuration files remain efficiently categorized for sorting.
I've written a script that will automatically provision your Nginx directory using the above structure, so you shouldn't need to do any of this manually. To get things rolling, just run the following commands,
cd /etc/nginx/
wget https://github.com/FujiMakoto/nginx-helpers/releases/download/v0.1/scripts.tar.gz
tar -xzvf scripts.tar.gz
rm -f scripts.tar.gz
/etc/nginx/scripts/provision
After running the provisioner, you should be left with a setup roughly identical to the one above.
If you have vim installed prior to running the provisioner, you will also be prompted to automatically download and install syntax highlighting for Nginx configuration files. This is a very nice feature to have if you're a vim user.
Once you have everything provisioned, go ahead and restart Nginx and try and access your servers IP address through your web browser. You should see a generic Nginx welcome page. If you don't / if you get an error when restarting nginx, make sure you've updated the nginx.conf configuration properly and try again.
4. Creating your first virtual host
Now that you have a good configuration structure to work with, we can move on to creating our first IPS virtual host. To start, go ahead and set up a new directory for your domain name (naturally replacing yourdomain.com with your actual domain name),
mkdir /etc/nginx/sites-available/yourdomain.com
Next, go ahead and create a configuration file in the directory you made above. This should be named root.conf if your IPS installation is located on yourdomain.com / www.yourdomain.com, otherwise use the subdomain as the filename (for example, use community.conf for community.yourdomain.com).
Inside of this file, insert the following configuration template,
upstream php_backend {
server unix:/var/run/php-fpm_webapp.sock;
}
server {
# Basic server configuration
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /srv/http/yourdomain.com/root/html;
index index.php;
# Maximum allowed upload size
client_max_body_size 100M;
# Logging
#access_log /srv/http/yourdomain.com/root/logs/access.log.gz combined gzip flush=5m;
error_log /srv/http/yourdomain.com/root/logs/error.log error;
# Friendly URL "rewrite" rules
location / {
try_files $uri $uri/ @ips;
}
# Configuration includes
include /etc/nginx/includes/ips/protect_upload_directories;
include /etc/nginx/includes/deny_dotfiles;
include /etc/nginx/includes/stub_status;
include /etc/nginx/includes/php_fpm_status;
# Assign cache headers to static files
location ~* ^.+\.(?:jpg|jpeg|gif|css|png|js|ico|xml|htm|swf|cur)$ {
# If the static resource doesn't exist, pass off to IPS' 404 handler
try_files $uri @ips404;
access_log off;
expires 2w;
}
# Lock down access to the AdminCP
location ~ ^/admin/.+\.php$ {
#allow 127.0.0.1;
#deny all;
#auth_basic "This page is restricted to administrators";
#auth_basic_user_file $document_root/admin/.htpasswd;
try_files $uri @ips404;
include /etc/nginx/includes/php_fastcgi_params;
fastcgi_pass php_backend;
}
# Execute the requested PHP script if it exists, otherwise pass off to IPS
location ~ \.php$ {
try_files $uri @ips;
include /etc/nginx/includes/php_fastcgi_params;
fastcgi_pass php_backend;
fastcgi_buffers 38 4k;
fastcgi_buffer_size 16k;
}
# Pass off not found errors to IPS' 404 handler
location @ips404 {
include /etc/nginx/includes/php_fastcgi_params;
fastcgi_pass php_backend;
fastcgi_param SCRIPT_FILENAME $document_root/404error.php;
fastcgi_param SCRIPT_NAME 404error.php;
}
# Send rewritten requests directly to IPS
location @ips {
include /etc/nginx/includes/php_fastcgi_params;
fastcgi_pass php_backend;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_buffers 38 4k;
fastcgi_buffer_size 16k;
}
}
Virtual host configuration
There are naturally some things you will need adjust here. We'll go through these in the order of which they appear. Once that's out of the way, we'll explain a bit how everything you see works.
Server Name
This one is obvious. Replace yourdomain.com with your actual domain name. If you are using a root level domain, be sure you add both the www/non-www variants.
Root
This, as you've probably already figured out, is the root path to your IPS installation. I recommend storing all of your applications in /srv/http and keeping to a/srv/http/{domain}/{subdomain} directory structure (similar to how our Nginx configuration files are managed).
Client Max Body Size
Self-explanatory. You should adjust this value to at least the maximum upload size you allow in IPS. You will need to remember to keep this value in sync with PHP's configured max_upload_size as well, otherwise your uploads will end up restricted to whichever setting is the lowest.
AdminCP Location
If you have renamed your AdminCP by following the instructions in the IPS security center, you will need to replace "admin" in the admin location block with the actual name of your admin directory. You can also enable IP based restrictions in this location block by uncommenting the allow and deny lines. You'll probably want to go ahead and add your local IP address as an allow line now if you do this. You can also set up .htpasswd based restrictions with the other two lines. For more information on how to do that, follow the instructions listed here.
"Rewrite" rules and named location blocks
Quite often, when looking up guides or examples for Nginx PHP configurations online, you'll see examples that simply pass every request ending with .php to FastCGI/PHP-FPM and be done with it.
Don't do this, it is really a bad way to configure your server. Most prominently because it opens the potential for a serious security vulnerability, and while modern releases of PHP-FPM protect against this vulnerability by default, it only exists as a protection for misconfigured web servers. You shouldn't rely on it.
By taking full advantage of Nginx's named location blocks and try_files processing, we are able to provide a clean and elegant configuration layout that is both secure and fast. In order to really understand how everything here works in detail, you'll need to dig into Nginx's documentation and familiarize yourself with how the location and try_files configuration directives work.
Configuration Includes
When running the provisioner script provided above, you'll automatically download a few simple but useful include files for Nginx. You can also also access them directly online here, each include file provides a bit of documentation explaining what it's for.
In summary, the four includes we use here provide IPS' "Protect Writeable Folders From Dangerous Files" security recommendation for Nginx, block browser access to dotfiles (which is very important if you store a .htpasswd file in your AdminCP directory for HTTP authentication), and finally enable access to the ngx_server_status and php_fpm_status pages for registered server administrators.
5. Setting up your web directory
Now that we have a working virtual host for our IPS installation, it's time to set it up. As I mentioned above, I recommend storing your web files in /srv/http. Naturally, you don't have to do it that way. You are free to store your installations wherever you want, it will just require some additional work on your part to set everything up if you do use a different location.
Why not /var/www?
/var/www is a common place for http data to be stored, mostly because it is usually the default configured path for Nginx and Apache. However, while this is often the usual place for web data, it's not actually a standardized configuration. /var is best reserved (as its name implies) for variable files which change over time, such as log files.
Why not /home/user/public_html?
This setup is actually fine for shared hosting environments and may make the most sense in such a situation. However, there's no reason to use it outside of that. From a security perspective, I would also advise against hosting your IPS installation under your primary user account. In the absolute worst case scenario that your SSH credentials are compromised and you've followed this guides recommendations (also assuming you have a properly configured server), your installation files and data would still be protected.
Why /srv/http?
I personally use and recommend /srv/http as it is FHS compliant. The /srv directory is the only standardized directory specifically made for hosting service files like this. If you are already used to another way of doing things and don't want to change, you are again free to use whatever kind of configuration you like.
We can use another helper script to take care of constructing the web directories for us, but if you decide not to use the default /srv/http path, you will need to edit the script below and modify HTTP_BASEDIR accordingly. Once that's done, go ahead and copy your IPS installation files into the html directory.
mkdir -p /srv/http
/etc/nginx/scripts/create yourdomain.com root
6. Locking down access to your web directory
Optional
While strongly recommended, it is not required that you follow the steps listed here. If you prefer not to (or are for some reason unable to) take advantage of ACL's, you will simply be responsible for setting up and managing permissions on your own. While outdated, you can still use the old Nginx+PHP-FPM server guide here as a reference for setting up decently secure UNIX permissions.
In this step, I will be explaining how to set up and configure ACL (Access Control List) permissions for your web directories. If this is new to you, it may be a bit difficult to grasp at first, but I recommend sticking with it. ACL's are an incredibly powerful tool to have and make managing secure file permissions a lot easier in the long run.
Go ahead and make sure the ACL utilities are installed now, they should be available through the acl package on most distributions. Next, test to make sure acl is enabled on your filesystem,
touch acl_test
setfacl -m "u:nginx:r--" acl_test
getfacl acl_test
rm acl_test
If all is well, the above commands should run without error and you should see "user:nginx:r--" in the output returned by getfacl. If you do get an error, you likely need to enable acl on your filesystem now. To do this, replace /dev/sdXY in the command below with your actual filesystem device (e.g. /dev/sda1) and then try repeating the previous commands after,
mount -o remount,acl /
tune2fs -o acl /dev/sdXY
Once you've confirmed acl's are working, you can go ahead and set the permissions for your IPS installation. Again, as long as you've configured Nginx using the provisioner script above, you will have access to a helper script to assist in doing this,
/etc/nginx/scripts/fixperms yourdomain.com root
After running the above command, you will be asked to provide a user for file ownership in your web directory. You can use whatever makes most sense to you here, a shortened version of your IPS communities name is a good example choice.
Unless you plan on setting up and running multiple web applications under different PHP-FPM pools, it's reasonable to leave the default for the PHP-FPM user.
When prompted for files to grant PHP modification access to, you should input conf_global.php only if this is a new IPS installation. Otherwise, leave this blank.
When prompted for directories to grant PHP recursive write access to, input applications/ datastore/ plugins/ uploads/
$ /etc/nginx/scripts/fixperms yourdomain.com root
What user do you want to grant file ownership to? webapp
What user does PHP-FPM run under for this virtual host? www-data
Which individual files/directories should PHP be granted write access to? conf_global.php
Which directories should PHP be granted recursive write access to? applications/ datastore/ plugins/ uploads/
Making sure all files and directories are owned by webapp
Setting directory permissions to 0750
Setting file permissions to 0640
Setting default permissions for new files
Granting Nginx user read-only access to all files and directories
Granting PHP-FPM user read-only access to all files and directories
Granting PHP-FPM user write access to conf_global.php
Granting PHP-FPM user recursive write access in applications/
Granting PHP-FPM user recursive write access in datastore/
Granting PHP-FPM user recursive write access in plugins/
Granting PHP-FPM user recursive write access in uploads/
All site permissions updated successfully
In the example above, first, we've set it so that everything in our web root is owned by the webapp user. Furthermore, permissions on all files and directories have been set in a way that completely prevents public access. This means that, at this point, no user other than webapp can see or read anything inside the web root.
This is where we start using acl permissions. First, we run the following commands to grant the Nginx and PHP-FPM users read-only access to everything in our web directory,
# Nginx
setfacl -R -m "u:nginx:r-X" /srv/http/yourdomain.com/root
setfacl -R -d -m "u:nginx:r-X" /srv/http/yourdomain.com/root
# PHP-FPM
setfacl -R -m "u:www-data:r-X" /srv/http/yourdomain.com/root
setfacl -R -d -m "u:www-data:r-X" /srv/http/yourdomain.com/root
The first commands (without the -d/--default flag) set the permissions on all existing files and directories. The second commands set the default permissions on all directories. This means any new files created inside your web directory will now automatically inherit the permissions you've configured.
After that, we repeat the above commands for PHP-FPM (but with rwX permissions) in order to grant PHP write access to only the required files and directories. Note also that we use a capital X here when applying permissions recursively, that way only directories are made executable.
With the following setup, you should never have a reason to use chmod 777 for anything. Furthermore, chmod 777 will also no longer work, as the ACL permissions for PHP-FPM will override it. You should instead use setfacl to set/remove permissions for PHP,
# Make a file writable
setfacl -m "u:www-data:rw-" conf_global.php
# Make a file read-only
setfacl -m "u:www-data:r--" conf_global.php
# Recusrively make a directory and its contents writable
setfacl -R -m "u:www-data:rwX" uploads/
setfacl -R -d -m "u:www-data:rwX" uploads/
# Recursively make a directory and its contents read-only
setfacl -R -m "u:www-data:r-X" uploads/
setfacl -R -d -m "u:www-data:r-X" uploads/
For a good general overview of the acl utility commands and how they work, check out the Arch Wiki and Debian / CentOS documentation pages. For a more in-depth understanding of how setfacl works, read the manpages.
7. Installing and configuring PHP-FPM
Finally, it's time to install and configure PHP-FPM. Go ahead and install the required packages now,
CentOS 7
yum install php-fpm php-cli php-mysqlnd php-pdo php-gd php-mbstring php-xml
# Only required if you use Imagick (which I recommend using over GD)
yum install php-devel php-pear ImageMagick*
pecl install Imagick
echo "extension=imagick.so" > /etc/php.d/imagick.ini
Debian / Ubuntu
apt-get update
apt-get install php5-fpm php5-cli php5-mysqlnd php5-curl php5-gd php5-imagick php5-json php5-mcrypt
Afterwards, go ahead and delete the default PHP-FPM pool, as we will be replacing it with our own,
CentOS 7
rm /etc/php-fpm.d/www.conf
nano /etc/php-fpm.d/webapp.conf
Debian / Ubuntu
rm /etc/php5/fpm/pool.d/www.conf
nano /etc/php5/fpm/pool.d/webapp.conf
Inside of this file, insert the following configuration template,
[webapp]
;;;;;;;;;;;;;;;;;;;;;
; FPM Configuration ;
;;;;;;;;;;;;;;;;;;;;;
; Basic settings
prefix = /var/run
user = www-data
group = www-data
listen = php-fpm_webapp.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Process manager configuration
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
pm.status_path = /php_fpm_status
; Log slow requests (used for performance tuning, 0 = Off)
request_slowlog_timeout = 0
slowlog = /srv/http/yourdomain.com/root/logs/php_slow.log
; Terminate unresponsive requests, this should match PHP's max_execution_timeout at a minimum
request_terminate_timeout = 30s
; Don't allow executing arbitrary files as PHP scripts. YOU SHOULD NEVER DISABLE THIS.
security.limit_extensions = .php
;;;;;;;;;;;;;;;;;;;;;
; PHP Configuration ;
;;;;;;;;;;;;;;;;;;;;;
; Make sure errors are not displayed publicly, error logging can be enabled as needed
php_flag[display_errors] = off
php_admin_flag[log_errors] = off
php_admin_value[error_log] = /srv/http/yourdomain.com/root/logs/php_error.log
; Set the maximum upload size, memory limit and execution timeout
php_admin_value[upload_max_filesize] = 100M
php_admin_value[post_max_size] = 100M
php_admin_value[memory_limit] = 128M
php_admin_value[max_execution_time] = 30
; PHP security directives, adjust open_basedir appropriately
php_admin_value[cgi.fix_pathinfo] = 0
php_admin_value[disable_functions] = exec,system,popen,proc_open,shell_exec
php_admin_value[open_basedir] = /tmp/:/usr/bin/:/srv/http/yourdomain.com/root/html/
As usual, go ahead and replace all references to yourdomain.com accordingly. If you allow uploads larger than 100M, you'll also want to tweak the upload_max_filesize and post_max_size PHP values.
The main thing we want to look at here is the process manager configuration. There are three ways you can configure the process manager; static, dynamic, and ondemand. You can use whichever makes the most sense to you, but you'll likely be needing to watch and tune these settings a bit no matter which you choose, so there's no harm in experimenting with different configurations.
Regardless of which you use, it's very important you don't try and set pm.max_children to a higher value than your server can handle. In the brief amount of profiling I could perform on IPS4 prior to writing this guide, my tests indicated that the average pool memory consumption for a mostly stock IPS install was around 32M per process. With the above configuration, this means that with all 8 children in use, PHP would be utilizing approximately 256M of memory.
So, when tuning this value, you need to factor in how much memory you have know you can dedicate entirely to PHP. In the above example, you would need to dedicate at least 256MB to PHP with a bit of margin for error. This is what you should be doing for everything on your server. Also consider that 32M is only an example, the actual average memory requirement will vary from setup to setup, so it would be a good idea to do a bit of profiling yourself.
As a final note, even if you have massive amounts of memory on hand, you still shouldn't set this value absurdly high. It can do more harm than good. Memory restrictions aside, a good starting value would probably be the number of cores your server has * 2. So, for a quad core server, 8 children would probably be a good starting point. If your community hosts a lot of attachments and handles a lot of PHP file transfers, you'll probably want to increase this value. Finding an optimal value here will take a bit of trial and error.
So, what about Static?
Good for very high traffic websites
Static is the PM configuration I used for a long time. It's dead simple. With static, you don't need to specify start_servers, min_spare_servers or max_spare_servers. You just provide a number of child processes to spawn and they are all immediately spawned and kept alive and ready to handle requests at any moments notice.
This sounds nice considering what I mentioned above. It offers better FPM performance, and since you're already dedicating that memory to PHP anyways, who cares if that memory is always in use?
While there is a bit of truth to that, I probably would not recommend static for most installations anymore. I consider static particularly good for high traffic websites, or websites that receive a lot of very heavy intermittent traffic spikes. For most low to moderate traffic communities, I am more likely to recommend dynamic, and I explain why in more detail below.
So, what about Dynamic?
Good for moderate and low traffic websites
Dynamic is probably the most common configuration. It's the middle ground between static and ondemand. With this setup, PHP always keeps a set number of child processes spawned and ready to handle incoming traffic. New child processes are spawned as traffic increases, old processes are destroyed as traffic lowers again. This is great for most web applications, as it avoids wasting memory.
While you still should always tune assuming all processes are in use, keeping the unused processes cleaned up gives more free memory to the server, which can in return be used for disk caching to improve overall server performance.
Dynamic also helps better protect against memory leaks. As unused processes are killed off, there's naturally less risk of memory leaks eating away at your memory pool. pm.max_requests normally helps protect against this, but it becomes less effective the more processes you keep spawned.
So, what about Ondemand?
Good for low traffic websites
As its name implies, ondemand only spawns child processes on demand. This is similar to dynamic, however unlike with dynamic, ondemand does not keep any unused child processes active. When a request is made that spawns a new child process, a timeout is set. If no further requests are sent to that process within the timeout window, the process is destroyed.
This could be beneficial for low traffic websites (particularly those on more memory constrained VPS'), but outside of that, there's probably not much of a reason to use it. Even for low traffic websites, keeping a small number of child processes spawned to handle new requests is usually good for performance.
Wrapping up
Once you have PHP-FPM configured, it's finally time to test everything out. Go ahead and enable your new virtualhost now,
/etc/nginx/scripts/enable yourdomain.com root
systemctl restart {nginx,php-fpm}
Assuming no errors are returned, try and visit your community in your web browser. If all has gone well, you're done! If not, check your error logs, review your configuration, make sure permissions are properly applied, and make sure you have all the required PHP packages installed. If you still have problems, don't be afraid to ask for help, just be sure to provide a detailed description of your problem and include any relevant error log entries when doing so!
Did you find this guide useful? You can show your support by sending a small tip, or by buying and reviewing one of my marketplace submissions! If that's not an option for you, you can also just tell me I'm awesome or something.