HAProxy now supports the FastCGI protocol, enabling fast, secure, and observable load balancing to PHP, Python, and other dynamic scripting languages. In this post, you will learn how to load balance PHP-FPM applications using HAProxy and FastCGI.
HAProxy version 2.1 introduced support for proxying the FastCGI protocol. For the first time, this means that HAProxy can route requests directly to applications written in dynamic scripting languages like PHP and Python without an intermediary web server to translate the protocol. You can seamlessly integrate dynamic backend applications with regular static pages by using HAProxy’s routing rules. On top of this, HAProxy is designed for the utmost performance, observability and security, which means a better user experience, detailed metrics, and safeguards around your FastCGI app.
FastCGI is a protocol that was developed during the mid-1990s to be a standard scheme for exchanging data between a web server and a backend application. That backend application didn’t have to be a scripted program, although it often was. FastCGI can be used to connect to any compatible program that speaks the protocol, including applications written in C. Today, it’s often used to connect servers like Apache to external PHP programs. Separating PHP into its own process apart from the web server or proxy allows for greater scalability.
FastCGI and Its Precursor, CGI
FastCGI is an application-layer protocol that was built to solve problems people were experiencing when using the older Common Gateway Interface (CGI) protocol. CGI was created during the early 1990s as a standard way for a web server to communicate with scripts, typically Perl scripts. At the time, web servers used various, non-standard ways of communicating with external scripts, making it difficult to write a program that was compatible with different server software. CGI made the communication uniform and consistent.
With CGI, whenever a request was received by the web server, a new CGI process was spawned to run the script. The web server passed parameters to the external process via environment variables and got responses via stdout. Although CGI didn’t become an official specification until much later, RFC 3875 gives us a clue about the CGI rationale:
The server acts as an application gateway. It receives the request from the client, selects a CGI script to handle the request, converts the client request to a CGI request, executes the script and converts the CGI response into a response for the client.
The problem with CGI was that it fired up a new process every time a client requested a script. This led to a large amount of memory being used when there were many, concurrent requests. Due to this limitation, its reign as king of dynamic content was soon usurped by FastCGI. Lincoln Stein’s 1999 article, Is CGI Dead?, tells how other technologies competed to steal CGI’s crown:
…a host of CGI alternatives and spin-offs has appeared in recent years. There are persistent CGI variants such as FastCGI, embedded CGI emulators such as mod_perl and Velocigen, server APIs such as ISAPI, and template-driven solutions such as ASP and PHP.
FastCGI had something good going for it. Like CGI, it could invoke a Perl, Python or PHP script and return the output to the server. However, it did not need to create a new process every time to do so. Instead, it spawned the process, but kept it running, which allowed it to reuse that same process again and again.
A lot of time has passed since then. What makes FastCGI a viable choice in today’s technology landscape? Primarily, it comes down to the fact that the problem of communicating with a PHP, Python, or Perl script has not disappeared. Raw HTTP requests can not be directly translated into the task of executing a script on the server.
You could, of course, write a Python script that receives HTTP messages directly, but then you would have embedded HTTP server logic into your script and muddled low-level protocol details with your business logic. Such an approach would likely not cover all of the aspects of a true web server, either. It would also mean duplicating this effort across each of your scripts.
Another solution is to install language parsing modules, such as mod_php, into the web server itself. This gives the web server the ability to execute scripts on its own. However, that limits your ability to scale the number of processes running the script independently from the web server. In many cases, it also means that the PHP module is loaded even for HTTP requests that don’t require PHP processing.
The better alternative is to run the script in its own process and leave the task of receiving HTTP requests to the web server. FastCGI allows you to separate the web server (or proxy) and the running script by defining the communication protocol between the two. Performance benchmarks indicate that separating scripts into their own process equates to a boost in performance.
FastCGI Process Manager (PHP-FPM) is a popular FastCGI implementation for PHP. It handles running a PHP script and receiving FastCGI communication to interact with the script. Traditionally, you would configure Apache to communicate with PHP-FPM via the mod_fastcgi or mod_proxy_fcgi module. However, with HAProxy version 2.1, you can relay requests directly from HAProxy to a running PHP-FPM service.
You can connect HAProxy to a PHP-FPM process either by Unix socket or by TCP/IP. Communicating via a Unix socket avoids any network overhead, but it requires that the PHP-FPM process run on the same server as HAProxy. TCP/IP, on the other hand, lets you run the PHP-FPM process on a different server, but it introduces the potential for network overhead.
First, let’s see how to install PHP-FPM. On an Ubuntu 18.04 server, run the following commands, which install PHP-FPM and a compatible version of PHP. At the time of this writing, it installs version 7.2 of both PHP and the PHP-FPM service.
Afterwards, you can check the version of PHP by passing the
--version flag to the
You can also check that the PHP-FPM service is installed and running:
As you can see, we did not install a web server such as Apache or NGINX. That’s because we are going to convert HTTP requests to FastCGI directly in HAProxy. This simplifies your PHP application’s server dependencies and reduces latency by removing a hop in the request path.
First, create a new folder to host your PHP application’s files. For example, you could create a folder at /var/www/myapp.
Then browse to that folder and add a file named index.php with the following contents:
When this PHP file is rendered as an HTML page, it displays various PHP settings configured on your system. This is useful as a test page, but should not be used in production.
Next, navigate into the /etc/php/7.2/fpm/pool.d directory and add a file named myapp.conf. Every configuration file in this directory instructs PHP-FPM to start a new pool. A pool runs worker processes for your application only. So, by adding more pool configuration files, you can host multiple PHP applications. The directives in each file control how many worker processes to create, for example, and the method for communicating with your application. It’s easiest to make a copy of the existing www.conf file:
Edit myapp.conf. At the top, you will find the pool name, www, defined between square brackets. Change this to [myapp].
Next, set listen to be a Unix socket. This socket file will be created automatically when the application starts:
At this point, if you wanted to have PHP-FPM listen on an IP address and port instead of a Unix socket, you would set
listen like so:
Save the file and then restart the PHP-FPM service:
Each configuration file that you add to the pool.d directory creates a new PHP-FPM application. They should each be assigned a different Unix socket or IP address and port combination. When the PHP-FPM service starts, it reads all of the files within that directory and fires up all of the requested applications.
Now that you have the PHP-FPM service running and have configured it to host a PHP application, the next step is to connect it to HAProxy. Install HAProxy version 2.1 or later and edit the file /etc/haproxy/haproxy.cfg. Here is a barebones example that uses FastCGI to communicate with PHP-FPM over a Unix socket:
global section, it’s necessary to set user and group directives so that HAProxy will have permission to access the Unix socket that was created by PHP-FPM. For example, on Ubuntu 18.04 the Unix socket at /run/php/myapp.sock is assigned a user and group named www-data. By setting HAProxy’s
group to this, it will have access.
Next, in the
backend phpservers, the line
use-fcgi-app php-fpm enables FastCGI and uses the FastCGI settings from the section named php-fpm.
Note that the
server line specifies the path to the Unix socket and a
proto argument set to fcgi. If you had configured PHP-FPM to listen on an IP address and port, then you would use that here, such as:
fcgi-app section tunes how HAProxy communicates with the PHP-FPM application.
Here is a summary of these settings:
||Sends FastCGI log messages to STDERR.|
||Sets the directory of your PHP script on the remote server.|
||The name of the PHP script to use if no other file name is given in the requested URL.|
||The regular expression HAProxy uses to extract the PHP file name from the rest of the requested URL.|
Restart HAProxy for these changes to take effect. Then you should see the PHP web page displayed when you navigate to the proxied URL. Add as many PHP applications as you would like, defining a new
fcgi-app section for each.
Or employ the HAProxy Enterprise WAF with the following ModSecurity rule:
Isolate Your Application Inside a Chroot
PHP-FPM supports isolating different PHP applications within their own chroot, or jailed directory, on the system. This is good for security, but it also gives you an added perk. It lets you decouple the location of your PHP files from your proxy. HAProxy won’t need to know where your application’s files are stored on the PHP-FPM server. That will make sense in a moment.
First, configure your PHP-FPM pool to use a chroot. Go to the directory /etc/php/7.2/fpm/pool.d and edit the myapp.conf file. Find the
chroot setting and change its value to be the path to your PHP application’s directory.
Now, when your application starts, PHP will see /var/www/myapp as the root directory of the system. It won’t have access to files outside of this folder. In your HAProxy configuration file, you will set
docroot to a forward slash.
When PHP-FPM receives the request, it will perceive the root directory to be the /var/www/myapp folder. As you can see, HAProxy no longer needs to know where your PHP files live on the local or remote PHP-FPM server. You can set
docroot to a forward slash for all chrooted applications. The pool configuration file’s
chroot directive will determine the application’s true directory.
FastCGI provides a way for HAProxy to communicate directly with a scripted program that’s written in a programming language like PHP. By separating static content from scripts using PHP-FPM, you are able to scale them independently. By placing HAProxy in front, you gain high performance, observability and security.
HAProxy Enterprise combines HAProxy Community, the world’s fastest and most widely used, open-source load balancer and application delivery controller, with enterprise-class features, services and premium support. It is a powerful product tailored to the goals, requirements and infrastructure of modern IT. Contact us to learn more and sign up for a free trial!