Expose PHP‑FPM metrics to Prometheus with an exporter in Docker Compose (tutorial)
Docker & Containers · By Kevin Marcondes · Published on · 7 min read
Step-by-step guide to expose PHP‑FPM metrics to Prometheus using an exporter in Docker Compose. You will enable the PHP‑FPM status endpoint, run a dedicated exporter, and configure Prometheus to scrape the metrics. At the end we validate with curl and the Prometheus UI, plus tips for multiple pools and security.
Architecture: how scraping works
PHP‑FPM exposes stats via pm.status_path. An exporter translates those stats into Prometheus format and serves them at /metrics. Prometheus then scrapes that endpoint periodically. Everything runs in separate containers orchestrated with Docker Compose.
This separation makes it easier to isolate responsibilities, apply resource limits, and evolve each component independently. See the roles and default ports:
| Component | Port | Role |
|---|---|---|
| php-fpm | 9000/TCP | Runs the PHP pool and exposes /status via FastCGI |
| php-fpm-exporter | 9253/TCP | Connects to /status and serves /metrics |
| prometheus | 9090/TCP | Scrapes the exporter and stores time series |
Prerequisites and important warnings
Before starting, you need Docker and Docker Compose installed and terminal access. This walkthrough uses official images and minimal configs suitable for development or a lab environment.
Warning: PHP‑FPM's status endpoint has no authentication. Keep access restricted to Docker's internal network or localhost. Do not expose /status to the public internet.
Warning: publishing ports exposes services. Prefer binding to 127.0.0.1 during tests.
- Docker and Docker Compose installed
- Permission to run docker compose
- Ports 9090 and 9253 free on the host (or adjust)
- Basic knowledge of YAML and Linux
Project folder structure
We'll create two folders to separate the PHP‑FPM and Prometheus configuration. Files will be populated in the following sections.
Suggested structure:
- php-fpm/www.conf
- prometheus/prometheus.yml
- docker-compose.yml
mkdir -p php-fpm prometheus
ls -la
# Depois, criaremos os arquivos nas seções seguintes com o conteúdo correto.
Enable PHP‑FPM status (pm.status_path)
The exporter relies on PHP‑FPM's status endpoint. Enable pm.status_path and ensure PHP‑FPM listens on TCP (port 9000) to simplify inter-container communication.
Save the file below as php-fpm/www.conf.
[www]
user = www-data
group = www-data
; Ouça em TCP para facilitar a conexão do exporter
listen = 9000
; Gerenciador de processos
pm = dynamic
pm.max_children = 20
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
; Endpoints internos do FPM
pm.status_path = /status
ping.path = /ping
; Logs para stdout/stderr do container
access.log = /proc/self/fd/2
clear_env = no
Docker Compose: php-fpm and php-fpm-exporter
Now let's orchestrate PHP‑FPM and the exporter. The exporter hipages/php-fpm_exporter connects to /status via FastCGI and serves HTTP metrics on port 9253.
Note: you can bind ports to 127.0.0.1:PORT if you want to restrict access to the local host.
version: "3.8"
services:
php-fpm:
image: php:8.2-fpm-alpine
container_name: php-fpm
volumes:
- ./php-fpm/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro
expose:
- "9000"
php-fpm-exporter:
image: hipages/php-fpm_exporter:latest
container_name: php-fpm-exporter
command:
- --phpfpm.scrape-uri=tcp://php-fpm:9000/status
- --web.listen-address=:9253
ports:
- "9253:9253" # use 127.0.0.1:9253 para restringir ao host local
depends_on:
- php-fpm
Prometheus: scrape the exporter
With the exporter serving /metrics, configure Prometheus to scrape it periodically. We'll use a dedicated job with the target pointing to the exporter service in Compose.
Save the file below as prometheus/prometheus.yml and add the prometheus service to your Compose file.
global:
scrape_interval: 15s
scrape_configs:
- job_name: "phpfpm"
static_configs:
- targets: ["php-fpm-exporter:9253"]
labels:
instance: "app-php-fpm"
Bring up the containers and validate metrics
Time to start everything and check it. We'll add/update docker-compose.yml with Prometheus, start the services, and verify that /metrics returns data. Also check the targets in the Prometheus UI.
Tip: if you want dashboards, see the Prometheus+Grafana guide in how to set up Prometheus and Grafana.
# Acrescente o serviço Prometheus no docker-compose (separado ou no mesmo arquivo)
cat >> docker-compose.yml <<'EOF'
prometheus:
image: prom/prometheus:latest
container_name: prometheus
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports:
- "9090:9090" # use 127.0.0.1:9090 para restringir ao host local
depends_on:
- php-fpm-exporter
EOF
# Subir o ambiente
docker compose up -d
# Verificar containers
docker compose ps
# Validar métricas do exporter
curl -s http://localhost:9253/metrics | head -n 20
# Conferir algumas séries esperadas
curl -s http://localhost:9253/metrics | grep -E 'phpfpm_(up|listen_queue|processes)'
# UI do Prometheus (navegador): http://localhost:9090/targets
# Status deve aparecer como UP para o job "phpfpm".
Best practices, multiple pools and next steps
For each PHP‑FPM instance/pool, the simplest pattern is to run one exporter per target and add them all to Prometheus (multiple targets in the same job). Avoid exposing /status publicly — keep communication on Docker's internal network.
Practical recommendations:
- Restrict ports: bind to 127.0.0.1:9253 and 127.0.0.1:9090 when possible.
- Keep pm.status_path enabled only where the exporter needs it. In production, do not publish it via external HTTP.
- Use consistent labels (instance, service, env) in Prometheus to simplify dashboards.
- Create alerts for high listen_queue and persistent busy processes.
- Tune FPM pm.* settings according to load; metrics help validate tuning. See PHP‑FPM memory leak for related guidance.
- To visualize metrics, connect Prometheus to Grafana. Compare stacks in Prometheus+Grafana vs Loki+Tempo and complement with centralized logs (Fluentd + Elasticsearch).
Common errors and quick diagnostics
Some problems commonly appear during initial setup. Check the typical causes and direct fixes below.
- Connection refused on exporter: confirm php-fpm is listening on 9000 and the service name in the scrape URI (tcp://php-fpm:9000/status).
- 404 on /status: verify pm.status_path in www.conf and restart FPM (recreate the container).
- Exporter UP but metrics empty: check permissions and whether the active pool is [www].
- Target DOWN in Prometheus: curl the target from the Prometheus host; if binding to 127.0.0.1 fixes it, adjust your network/ports.
- High persistent listen_queue: a sign of saturation; tune pm.max_children and investigate CPU/IO bottlenecks.
Frequently asked questions
Can I use a UNIX socket instead of TCP for PHP‑FPM?
Yes. The exporter can speak FastCGI over a socket. Point --phpfpm.scrape-uri to the socket path, for example unix:///usr/local/var/run/php-fpm.sock, and mount the socket into the exporter container. In Compose, use shared volumes between php-fpm and the exporter.
Is it safe to expose the exporter's /metrics to the internet?
Avoid it. Expose /metrics only internally (Docker network or 127.0.0.1) and let Prometheus scrape locally. If you need remote access, put a proxy with authentication and IP restrictions in front of the exporter or access it via VPN.
How do I monitor multiple pools or PHP‑FPM services?
Run one exporter per instance/pool and add them all as targets in Prometheus under the same job. You can also create multiple jobs per environment (e.g., staging, production) and use labels to filter in dashboards and alerts.
Do I need Nginx to enable PHP‑FPM status?
No. The exporter speaks FastCGI directly to PHP‑FPM. Just enable pm.status_path and ensure FPM is listening (TCP or socket). Nginx is only needed if you want to expose the status over HTTP for humans, which Prometheus doesn't require.
Which PHP‑FPM metrics are most useful for alerts?
Listen queue (phpfpm_listen_queue), busy/idle processes (phpfpm_processes by state), max children reached, and the target up metric. Correlate these with app latency and 5xx errors from your proxy to get full context.
Want a review of your observability stack in Docker?
If you need help configuring exporters securely and consistently, or creating useful dashboards and alerts for your team, let's talk.
Discuss observability in DockerSee also
- Tutorial: Implement Canary releases with Traefik in Docker Compose using headers and weighted routing for gradual PHP deployments
- Tutorial: Configure HAProxy with Let's Encrypt and health checks to load balance traffic between Docker containers on a VPS (step-by-step)
- Hands‑On Tutorial: Implement mTLS Between Nginx and Microservices with smallstep CA in Docker Compose
- Tutorial: configure cgroup v2 and systemd ResourceControl to limit CPU, memory and I/O for Docker containers on Debian