Running PHP scripts in uWSGI

You can safely run PHP scripts using uWSGI’s CGI support. The downside of this approach is the latency caused by the spawn of a new PHP interpreter at each request.

To get far superior performance you will want to embed the PHP interpreter in the uWSGI core and use the PHP plugin.

Building

A bunch of distros (such as Fedora, Red Hat and CentOS) include a php-embedded package.Install it, along with php-devel and you should be able to build the php plugin:

  1. python uwsgiconfig.py --plugin plugins/php
  2. # You can set the path of the php-config script with UWSGICONFIG_PHPPATH.
  3. UWSGICONFIG_PHPPATH=/opt/php53/bin/php-config python uwsgiconfig.py --plugin plugins/php
  4. # or directly specify the directory in which you have installed your php environment
  5. UWSGICONFIG_PHPDIR=/opt/php53 python uwsgiconfig.py --plugin plugins/php

If you get linkage problems (such as libraries not found), install those missing packages (ncurses-devel, gmp-devel, pcre-devel…) but be warned that if you add development packages modifying the uWSGI core behaviour (pcre is one of these) you need to recompile the uWSGI server too, or strange problems will arise.

For distros that do not supply a libphp package (all Debian-based distros, for instance), you have to rebuild PHP with the —enable-embed flag to ./configure:

  1. ./configure --prefix=/usr/local --with-mysql --with-mysqli --with-pdo-mysql --with-gd --enable-mbstring --enable-embed
  2. # That's a good starting point

Ubuntu 10.04 (newer versions include official libphp-embed sapi)

  1. # Add ppa with libphp5-embed package
  2. sudo add-apt-repository ppa:l-mierzwa/lucid-php5
  3. # Update to use package from ppa
  4. sudo apt-get update
  5. # Install needed dependencies
  6. sudo apt-get install php5-dev libphp5-embed libonig-dev libqdbm-dev
  7. # Compile uWSGI PHP plugin
  8. python uwsgiconfig --plugin plugins/php

Multiple PHP versions

Sometimes (always, if you are an ISP) you might have multiple versions of PHP installed in the system. In such a case, you will need one uWSGI plugin for each version of PHP:

  1. UWSGICONFIG_PHPDIR=/opt/php51 python uwsgiconfig.py --plugin plugins/php default php51
  2. UWSGICONFIG_PHPDIR=/opt/php52 python uwsgiconfig.py --plugin plugins/php default php52
  3. UWSGICONFIG_PHPDIR=/opt/php53 python uwsgiconfig.py --plugin plugins/php default php53

‘default’ is the build profile of your server core. If you build uWSGI without a specific profile, it will be ‘default’.

You can then load a specific plugin with plugins php51, etc. You cannot load multiple PHP versions in the same uWSGI process.

Running PHP apps with nginx

If you have simple apps (based on file extensions) you can use something like this:

  1. location ~ \.php$ {
  2. root /your_document_root;
  3. include uwsgi_params;
  4. uwsgi_modifier1 14;
  5. uwsgi_pass 127.0.0.1:3030;
  6. }

You might want to check for all of URIs containing the string .php:

  1. location ~ \.php {
  2. root /your_document_root;
  3. include uwsgi_params;
  4. uwsgi_modifier1 14;
  5. uwsgi_pass 127.0.0.1:3030;
  6. }

Now simply run the uWSGI server with a bunch of processes:

  1. uwsgi -s :3030 --plugin php -M -p 4
  2. # Or abuse the adaptive process spawning with the --cheaper option
  3. uwsgi -s :3030 --plugin php -M -p 40 --cheaper 4

This will allow up to 40 concurrent php requests but will try to spawn (or destroy) workers only when needed, maintaining a minimal pool of 4 processes.

Advanced configuration

By default, the PHP plugin will happily execute whatever script you pass to it. You may want to limit it to only a subset of extensions with the php-allowed-ext option.

  1. uwsgi --plugin php --master --socket :3030 --processes 4 --php-allowed-ext .php --php-allowed-ext .inc

Run PHP apps without a frontend server

This is an example configuration with a “public” uWSGI instance running a PHP app and serving static files. It is somewhat complex for an example, but should be a good starting point for trickier configurations.

  1. [uwsgi]
  2. ; load the required plugins, php is loaded as the default (0) modifier
  3. plugins = http,0:php
  4.  
  5. ; bind the http router to port 80
  6. http = :80
  7. ; leave the master running as root (to allows bind on port 80)
  8. master = true
  9. master-as-root = true
  10.  
  11. ; drop privileges
  12. uid = serena
  13. gid = serena
  14.  
  15. ; our working dir
  16. project_dir = /var/www
  17.  
  18. ; chdir to it (just for fun)
  19. chdir = %(project_dir)
  20. ; check for static files in it
  21. check-static = %(project_dir)
  22. ; ...but skip .php and .inc extensions
  23. static-skip-ext = .php
  24. static-skip-ext = .inc
  25. ; search for index.html when a dir is requested
  26. static-index = index.html
  27.  
  28. ; jail our php environment to project_dir
  29. php-docroot = %(project_dir)
  30. ; ... and to the .php and .inc extensions
  31. php-allowed-ext = .php
  32. php-allowed-ext = .inc
  33. ; and search for index.php and index.inc if required
  34. php-index = index.php
  35. php-index = index.inc
  36. ; set php timezone
  37. php-set = date.timezone=Europe/Rome
  38.  
  39. ; disable uWSGI request logging
  40. disable-logging = true
  41. ; use a max of 17 processes
  42. processes = 17
  43. ; ...but start with only 2 and spawn the others on demand
  44. cheaper = 2

A more extreme example that mixes CGI with PHP using internal routing and a dash of configuration logic.

  1. [uwsgi]
  2. ; load plugins
  3. plugins-dir = /proc/unbit/uwsgi
  4. plugins = cgi,php,router_uwsgi
  5.  
  6. ; set the docroot as a config placeholder
  7. docroot = /accounts/unbit/www/unbit.it
  8.  
  9. ; reload whenever this config file changes
  10. ; %p is the full path of the current config file
  11. touch-reload = %p
  12.  
  13. ; set process names to something meaningful
  14. auto-procname = true
  15. procname-prefix-spaced = [unbit.it]
  16.  
  17. ; run with at least 2 processes but increase up to 8 when needed
  18. master = true
  19. processes = 8
  20. cheaper = 2
  21.  
  22. ; check for static files in the docroot
  23. check-static = %(docroot)
  24. ; check for cgi scripts in the docroot
  25. cgi = %(docroot)
  26.  
  27. logto = /proc/unbit/unbit.log
  28. ;rotate logs when filesize is higher than 20 megs
  29. log-maxsize = 20971520
  30.  
  31. ; a funny cycle using 1.1 config file logic
  32. for = .pl .py .cgi
  33. static-skip-ext = %(_)
  34. static-index = index%(_)
  35. cgi-allowed-ext = %(_)
  36. endfor =
  37.  
  38. ; map cgi modifier and helpers
  39. ; with this trick we do not need to give specific permissions to cgi scripts
  40. cgi-helper = .pl=perl
  41. route = \.pl$ uwsgi:,9,0
  42. cgi-helper = .cgi=perl
  43. route = \.cgi$ uwsgi:,9,0
  44. cgi-helper = .py=python
  45. route = \.py$ uwsgi:,9,0
  46.  
  47. ; map php modifier as the default
  48. route = .* uwsgi:,14,0
  49. static-skip-ext = .php
  50. php-allowed-ext = .php
  51. php-allowed-ext = .inc
  52. php-index = index.php
  53.  
  54. ; show config tree on startup, just to see
  55. ; how cool is 1.1 config logic
  56. show-config = true

uWSGI API support

Preliminary support for some of the uWSGI API has been added in 1.1. This is the list of supported functions:

  • uwsgi_version()
  • uwsgi_setprocname($name)
  • uwsgi_worker_id()
  • uwsgi_masterpid()
  • uwsgi_signal($signum)
  • uwsgi_rpc($node, $func, …)
  • uwsgi_cache_get($key)
  • uwsgi_cache_set($key, $value)
  • uwsgi_cache_update($key, $value)
  • uwsgi_cache_del($key)

Yes, this means you can call Python functions from PHP using RPC.

  1. from uwsgidecorators import *
  2.  
  3. # define a python function exported via uwsgi rpc api
  4. @rpc('hello')
  5. def hello(arg1, arg2, arg3):
  6. return "%s-%s-%s" (arg3, arg2, arg1)
  1. Python says the value is <? echo uwsgi_rpc("", "hello", "foo", "bar", "test"); ?>

Setting the first argument of uwsgi_rpc to empty, will trigger local rpc.

Or you can share the uWSGI cache

  1. uwsgi.cache_set("foo", "bar")
  1. <? echo uwsgi_cache_get("foo"); ?>

Sessions over uWSGI caches (uWSGI >=2.0.4)

Starting from uWSGI 2.0.4, you can store PHP sessions in uWSGI caches.

  1. [uwsgi]
  2. plugins = php
  3. http-socket = :9090
  4. http-socket-modifier1 = 14
  5. ; create a cache with 1000 items named 'mysessions'
  6. cache2 = name=mysessions,items=1000
  7. ; set the 'uwsgi' session handler
  8. php-set = session.save_handler=uwsgi
  9. ; use the 'mysessions' cache for storing sessions
  10. php-set = session.save_path=mysessions
  11.  
  12. ; or to store sessions in remote caches...
  13. ; use the 'foobar@192.168.173.22:3030' cache for storing sessions
  14. php-set = session.save_path=foobar@192.168.173.22:3030

Zend Opcode Cache (uWSGI >= 2.0.6)

For some mysterious reason, the opcode cache is disabled in the embed SAPI.

You can bypass the problem by telling the PHP engine that is running under the apache SAPI (using the php-sapi-name option):

  1. [uwsgi]
  2. plugins = php
  3. php-sapi-name = apache
  4. http-socket = :9090
  5. http-socket-modifier1 = 14

ForkServer (uWSGI >= 2.1)

The Fork Server (sponsored by Intellisurvey) is one of the main features of the 2.1 branch. It allows you to inherit your vassals from specific parents instead of the Emperor.

The PHP plugin has been extended to support a fork-server so you can have a pool of php base instances from which vassals can fork(). This means you can share the opcode cache and do other tricks.

Thanks to the vassal attributes in uWSGI 2.1 we can choose from which parent a vassal will call fork().

Note

You need Linux kernel >= 3.4 (the feature requires PR_SET_CHILD_SUBREAPER) for “solid” use. Otherwise your Emperor will not be able to correctly wait() on children (and this will slow-down your vassal’s respawns, and could lead to various form of race conditions).

In the following example we will spawn 3 vassals, one (called base.ini) will initialize a PHP engine, while the others two will fork() from it.

  1. [uwsgi]
  2. ; base.ini
  3.  
  4. ; force the sapi name to 'apache', this will enable the opcode cache
  5. early-php-sapi-name = apache
  6. ; load a php engine as soon as possible
  7. early-php = true
  8.  
  9. ; ... and wait for fork() requests on /run/php_fork.socket
  10. fork-server = /run/php_fork.socket

then the 2 vassals

  1. [emperor]
  2. ; tell the emperor the address of the fork server
  3. fork-server = /run/php_fork.socket
  4.  
  5. [uwsgi]
  6. ; bind to port :4001
  7. socket = 127.0.0.1:4001
  8. ; force all requests to be mapped to php
  9. socket-modifier1 = 14
  10. ; enforce a DOCUMENT_ROOT
  11. php-docroot = /var/www/one
  12. ; drop privileges
  13. uid = one
  14. gid = one
  1. [emperor]
  2. ; tell the emperor the address of the fork server
  3. fork-server = /run/php_fork.socket
  4.  
  5. [uwsgi]
  6. ; bind to port :4002
  7. socket = 127.0.0.1:4002
  8. ; force all requests to be mapped to php
  9. socket-modifier1 = 14
  10. ; enforce a DOCUMENT_ROOT
  11. php-docroot = /var/www/two
  12. ; drop privileges
  13. uid = two
  14. gid = two

The two vassals are completely unrelated (even if they fork from the same parent), so you can drop privileges, have different process policies and so on.

Now spawn the Emperor:

  1. uwsgi emperor phpvassals/ emperor-collect-attr fork-server emperor-fork-server-attr fork-server

The —emperor-collect-attr forces the Emperor to search for the ‘fork-server’ attribute in the [emperor] section of the vassal file, while —emperor-fork-server-attr tells it to use this parameter as the address of the fork server.

Obviously if a vassal does not expose such an attribute, it will normally fork() from the Emperor.