Monitor PHP-FPM (FastCGI Process Manager) pool using curl
utility, cgi-fcgi
application or slightly modified Python script that was used to display PHP-FPM pool information.
Enable PHP-FPM ping page for a specific pool
Edit PHP-FPM pool configuration file to define status ping URI and response.
$ sudo vim /etc/php/7.0/fpm/pool.d/www.conf [...] ping.path = /ruok ping.response = imok [...]
By default, the ping page is not enabled as URI is not defined.
You can assume that default challenge and response is simply ping → pong
.
Reload PHP-FPM service to apply the configuration change.
$ sudo systemctl reload php7.0-fpm
Serve PHP-FPM ping page for a specific pool
Use the following NGINX configuration snippet to ensure that the status page is available only on the local machine.
location ~ ^/ruok$ { allow 127.0.0.1/32; deny all; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; }
You can just deny all requests, which is a better solution or just extend it to use basic authentication or other additional measures to restrict access.
Use curl utility to access ping page
Inspect challenge and response mechanism using curl
.
$ curl -4k https://localhost/ruok imok
Use the following command to perform a specific operation that depends on the received response.
$ curl -4sk https://localhost/ruok | awk '{if ($0 == "imok") {exit 0} else {exit 1}}' && (echo "green") || (echo "red")
It will display green
or red
status code, so you can easily modify it.
Use the CGI/1.1 program to monitor FastCGI server using UNIX socket
Install shared FastCGI library that includes a cgi-fcgi
utility.
$ sudo apt-get install -y libfcgi0ldbl Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: libfcgi-bin The following NEW packages will be installed: libfcgi-bin libfcgi0ldbl 0 upgraded, 2 newly installed, 0 to remove and 4 not upgraded. Need to get 162 kB of archives. After this operation, 483 kB of additional disk space will be used. Get:1 http://ftp.icm.edu.pl/pub/Linux/distributions/raspbian/raspbian stretch/main armhf libfcgi0ldbl armhf 2.4.0-8.4 [151 kB] Get:2 http://ftp.icm.edu.pl/pub/Linux/distributions/raspbian/raspbian stretch/main armhf libfcgi-bin armhf 2.4.0-8.4 [11.2 kB] Fetched 162 kB in 0s (266 kB/s) Selecting previously unselected package libfcgi0ldbl:armhf. (Reading database ... 38762 files and directories currently installed.) Preparing to unpack .../libfcgi0ldbl_2.4.0-8.4_armhf.deb ... Unpacking libfcgi0ldbl:armhf (2.4.0-8.4) ... Selecting previously unselected package libfcgi-bin. Preparing to unpack .../libfcgi-bin_2.4.0-8.4_armhf.deb ... Unpacking libfcgi-bin (2.4.0-8.4) ... Setting up libfcgi0ldbl:armhf (2.4.0-8.4) ... Processing triggers for libc-bin (2.24-11+deb9u3) ... Processing triggers for man-db (2.7.6.1-2) ... Setting up libfcgi-bin (2.4.0-8.4) ...
Display PHP-FPM pool information.
$ sudo -u www-data bash -c "export SCRIPT_NAME=/ruok; export SCRIPT_FILENAME=/ruok; export REQUEST_METHOD=GET; cgi-fcgi -bind -connect /var/run/php/php7.0-fpm.sock" Content-type: text/plain;charset=UTF-8 Expires: Thu, 01 Jan 1970 00:00:00 GMT Cache-Control: no-cache, no-store, must-revalidate, max-age=0 imok
Use the following command to perform a specific operation that depends on the received response.
$ sudo -u www-data bash -c "export SCRIPT_NAME=/ruok; export SCRIPT_FILENAME=/ruok; export REQUEST_METHOD=GET; cgi-fcgi -bind -connect /var/run/php/php7.0-fpm.sock" 2>/dev/null | tail -1 | awk '{if ($0 == "imok") {exit 0} else {exit 1}}' && (echo "green") || (echo "red")
It will display green
or red
status code, so you can easily modify it.
Use Python script to monitor FastCGI server using UNIX socket
Use the following Python script to monitor the FastCGI server using the UNIX socket without additional dependencies.
#!/usr/bin/env python import sys import socket import struct class FCGIPingClient: # FCGI protocol version FCGI_VERSION = 1 # FCGI record types FCGI_BEGIN_REQUEST = 1 FCGI_PARAMS =4 # FCGI roles FCGI_RESPONDER = 1 # FCGI header length FCGI_HEADER_LENGTH = 8 def __init__(self, socket_path = "/var/run/php/php7.0-fpm.sock", socket_timeout = 5.0, challenge = "/ping", response = "pong" ): self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.socket_path = socket_path self.set_socket_timeout(socket_timeout) self.challenge = challenge self.response = response self.request_id = 1 self.params = { "SCRIPT_NAME": self.challenge, "SCRIPT_FILENAME": self.challenge, "QUERY_STRING": "", "REQUEST_METHOD": "GET", } def set_socket_timeout(self, timeout): self.socket_timeout = timeout self.socket.settimeout(self.socket_timeout) def connect(self): try: self.socket.connect(self.socket_path) except: print(sys.exc_info()[1]) sys.exit(1) def close(self): self.socket.close() def define_begin_request(self): fcgi_begin_request = struct.pack("!HB5x", self.FCGI_RESPONDER, 0) fcgi_header = struct.pack("!BBHHBx", self.FCGI_VERSION, self.FCGI_BEGIN_REQUEST, self.request_id, len(fcgi_begin_request), 0) self.fcgi_begin_request = fcgi_header + fcgi_begin_request def define_parameters(self): parameters = [] for name, value in self.params.items(): parameters.append(chr(len(name)) + chr(len(value)) + name + value) parameters = ''.join(parameters) parameters_length = len(parameters) parameters_padding_req = parameters_length & 7 parameters_padding = b'\x00'* parameters_padding_req fcgi_header_start = struct.pack("!BBHHBx", self.FCGI_VERSION, self.FCGI_PARAMS, self.request_id, parameters_length , parameters_padding_req) fcgi_header_end = struct.pack("!BBHHBx", self.FCGI_VERSION, self.FCGI_PARAMS, self.request_id, 0, 0) self.fcgi_params = fcgi_header_start + parameters.encode() + parameters_padding + fcgi_header_end def execute(self): try: self.socket.send(self.fcgi_begin_request) self.socket.send(self.fcgi_params) header = self.socket.recv(self.FCGI_HEADER_LENGTH) fcgi_version, request_type, request_id, request_length, request_padding = struct.unpack("!BBHHBx", header) if request_type == 6: self.raw_data=self.socket.recv(request_length) else: self.raw_status_data = "" if request_type == 7: sys.exit(2) else: sys.exit(3) except: sys.exit(4) self.data = self.raw_data.decode().split("\r\n\r\n")[1] def make_request(self): self.define_begin_request() self.define_parameters() self.connect() self.execute() self.close() def return_status(self): if self.data == self.response: sys.exit(0) else: sys.exit(1) fcgi_client = FCGIPingClient( socket_path = "/var/run/php/php7.0-fpm.sock", challenge = "/ruok", response = "imok" ) fcgi_client.make_request() fcgi_client.return_status()
Use the following command to perform a specific operation that depends on the received response.
$ sudo -u www-data python /usr/local/bin/fpm-monitor.py && (echo "green") || (echo "red")
It is using the same mechanism as the previous one.