This entry describes installation and configuration of nginx in conjunction with php-fpm on linux.
You can install it using a package manager or, like me, compile it from source - because OpenSuSE does not offer nginx in their standard repo. The source can be found on their download page. Apart from gcc, you will also need libxslt, gd, openssl and geoip ( +dependencies, and maybe more).
zypper in gcc libxslt-devel gd-devel libGeoIP-devel libopenssl-devel
After unpacking the nginx source code, enter the directory and configure according to your wishes, e.g.
./configure --prefix=/usr \ --conf-path=/etc/nginx/nginx.conf \ --error-log-path=/var/log/nginx/error-log \ --http-log-path=/var/log/nginx/access-log \ --pid-path=/var/run/nginx/nginx.pid \ --lock-path=/var/lock/nginx/nginx.pid \ --user=wwwrun \ --group=www \ --builddir=build1 \ --with-threads \ --with-ipv6 \ --with-file-aio \ --with-http_ssl_module \ --with-http_spdy_module \ --with-http_realip_module \ --with-http_addition_module \ --with-http_xslt_module \ --with-http_image_filter_module \ --with-http_geoip_module \ --with-http_sub_module \ --with-http_flv_module \ --with-http_mp4_module \ --with-http_gunzip_module \ --with-http_auth_request_module \ --with-http_random_index_module \ --with-http_secure_link_module \ --with-http_stub_status_module
The default settings for configure assume some pretty "interesting" defaults, so it is worth setting all the paths to match the conventions of your distribution. Then just
make make install
A list of configuration file directives can be found here. An old, but still valid and good introduction to nginx can be found here. It is a good introduction for people coming from apache. Also, if you come from apache and have started using rewrites quite extensively, know that nginx has a totally different approach to the whole web-serving business and you should not use nginx' rewrite functionality until you are totally sure you need it.
For my needs, the following is a very working configuration.
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name example.net; location / { fastcgi_param SCRIPT_FILENAME /index.php; 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 REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 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; # PHP only, required if PHP was built with --enable-force-cgi-redirect fastcgi_param REDIRECT_STATUS 200; fastcgi_pass 127.0.0.1:9000; } } }
You might want to adjust worker_processes to your needs. This configuration file basically just directs any request to example.net to a cgi server listening on 127.0.0.1:9000, at the file index.php. That means that whatever is entered into the url bar after http://example.net/ does not matter, everything goes to index.php. If the application is written properly, it will parse the $_SERVER[REQUEST_URI] and act accordingly. For example, if the request is http://example.net/a/b/c?x=y, then index.php will be run and $_SERVER[REQUEST_URI]="/a/b/c?x=y".
This way, it is of course totally impossible to access any other files that the cgi server might serve. If you need to access other files directly (i.e. not going via index.php), maybe you need images or a css file, there are simple solutions for this, e.g.
location / { root /srv/www/pools/example.net try_files $uri $uri/ /index.php; }
This redirects everything that does not exist to /index.php. However, any attacker that manages to place a php file into your cgi directory can then execute it. In my original solution, everything gets redirected to /index.php, so it is totally impossible to directly call any other scripts or executables. Also, since everything goes via php-fpm, no root directive is needed.
Another secure method would be to put all static content into separate directories from executable (read: scripts) content and filter those directories via a separate location directive. The location directive is really important, the documentation can be found here.
Installation was done via the package manager in this case (thankfully, php-fpm is included in the standard OpenSuSE repos). A config file template is stored at /etc/php5/fpm/php-fpm.conf.default. You can copy this to /etc/php5/fpm/php-fpm.conf or use the one below:
[global] error_log = /var/log/php-fpm/errors.log log_level = notice process.max = 128 daemonize = yes systemd_interval = 10 include=/etc/php5/fpm/pool.d/*.conf
This is very simple. More configuration directives (although not all) can be found in the official documentation. Some extra ones are found in php-fpm.conf.default. Note that this configuration also uses some other *inis to set php behaviour. In my case (checked via phpinfo()) these were
Loaded Configuration File /etc/php5/fpm/php.ini Additional .ini files parsed: /etc/php5/conf.d/ctype.ini, /etc/php5/conf.d/dom.ini, /etc/php5/conf.d/iconv.ini, /etc/php5/conf.d/json.ini, /etc/php5/conf.d/pdo.ini, /etc/php5/conf.d/pdo_sqlite.ini, /etc/php5/conf.d/sqlite3.ini, /etc/php5/conf.d/tokenizer.ini, /etc/php5/conf.d/xmlreader.ini, /etc/php5/conf.d/xmlwriter.ini
To load sensible defaults it is probably a good idea to copy the /etc/php5/cli/php.ini to /etc/php5/fpm/php.ini (as I did). Otherwise, no php.ini is loaded except for those in the conf.d directory. You might also want to tweak the php.ini to your needs (e.g. regarding file uploads, max post size etc.). However, any more specific settings should be done per-pool.
An example of a pool configuration file (/etc/php5/fpm/pool.d/example.net.conf) is shown below:
[example.net] listen = 127.0.0.1:9000 listen.allowed_clients = 127.0.0.1 prefix = /srv/www/pools/$pool chroot = $prefix user = phpfpm group = phpfpm pm = dynamic pm.max_children = 5 pm.start_servers = 1 pm.min_spare_servers = 1 pm.max_spare_servers = 2 pm.max_requests = 500 ; pm.status_path = /status ; ping.path = /ping ; ping.response = pong request_terminate_timeout = 120s request_slowlog_timeout = 5s slowlog = /var/log/php-fpm/$pool.log.slow access.log = /var/log/php-fpm/$pool.access.log access.format = "%R - %u %t \"%m %l %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" clear_env = Yes security.limit_extensions = .php
You can add per-pool php settings for example like this:
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
More about this can be found in the documentation (search for "update PHP settings of a certain pool" on the page).
OpenSuSE provides a (crappy) php-fpm systemd service script at /usr/lib/systemd/system/php-fpm.service. The original one is pretty "bad" because it runs php-fpm in foreground and also does not check the config file for errors before starting the service. I have corrected this, my version can be found below:
[Unit] Description=The PHP FastCGI Process Manager After=syslog.target network.target Before=apache2.service nginx.service lighttpd.service [Service] Type=forking PIDFile=/var/run/php-fpm/php-fpm.pid ExecStartPre=/usr/sbin/php-fpm --fpm-config /etc/php5/fpm/php-fpm.conf -t ExecStart=/usr/sbin/php-fpm --fpm-config /etc/php5/fpm/php-fpm.conf --pid /var/run/php-fpm/php-fpm.pid ExecReload=/bin/kill -USR2 $MAINPID ExecStop=/bin/kill -QUIT $MAINPID PrivateTmp=true [Install] WantedBy=multi-user.target
Since I did not install nginx via the package manager, there is no systemd service file (it was also not installed as part of the installation process). You should add one yourself at /usr/lib/systemd/system/nginx.service:
[Unit] Description=The nginx HTTP and reverse proxy server After=syslog.target network.target remote-fs.target nss-lookup.target [Service] Type=forking PIDFile=/var/run/nginx/nginx.pid ExecStartPre=/usr/sbin/nginx -t ExecStart=/usr/sbin/nginx ExecReload=/usr/sbin/nginx -s reload ExecStop=/usr/sbin/nginx -s quit PrivateTmp=true [Install] WantedBy=multi-user.target
All of this creates quite a few log files, and none of them are automagically rotated. For this purpose, add two new files in /etc/logrotate.d/: nginx and php-fpm, shown below:
/var/log/nginx/*-log { delaycompress compress dateext maxage 365 rotate 99 size=+4096k notifempty missingok create 644 root root sharedscripts postrotate /usr/sbin/nginx -s reopen endscript }
/var/log/php-fpm/*.log /var/log/php-fpm/*.slow { delaycompress compress dateext maxage 365 rotate 99 size=+4096k notifempty missingok create 644 root root sharedscripts postrotate [ ! -f /var/run/php-fpm/php-fpm.pid ] || kill -USR1 `cat /var/run/php-fpm/php-fpm.pid` endscript }
You can test them using logrotate -d -f *configfile*. If this gives a ton of output, copy the /etc/logrotate.conf to a temporary directory and change it to only include /etc/logrotate.d/php-fpm and /etc/logrotate.d/nginx.
You should check if all required directories were created and create them if necessary. They can all be owned by root. Both nginx and php-fpm are started as root and have their master processes run as root, while the workers run with the configured user. Therefore, nginx and php-fpm can create logfiles in locations where their workers can not. Some directories to check:
Furthermore, the user and group we configured php-fpm to use needs to be created. wwwrun and www for nginx should already exist.
useradd -U -s /bin/false phpfpm
Please do *not* change the ownership of anything to the phpfpm user. php does not need write permissions on a script to be able to execute it, nor does it need ownership of it or of the parent directory. Both possibly enable attackers to modify server contents. If an application needs write permissions (e.g. for file uploads), they mostly need it on a particular directory only. If an application requires write permissions to everything, do not use it, it is crap. You might as well give your server to spammers for free.
This concludes this setup. If you find any bugs or problems with this configuration, please send me an email.