ngx_php7 is a high-performance, non-blocking Nginx module that embeds PHP 7 directly into the web server process—similar in architecture and intent to ngx_lua. It enables inline PHP execution at various Nginx request-processing phases without relying on external processes like PHP-FPM, mod_php, or CGI gateways.
Developed as a open-source extension (GitHub: rryqszq4/ngx_php7), it targets developers seeking low-latency, stateless, and I/O-efficient PHP-driven routing, header manipulation, request inspection, and lightweight service orchestration—all within Nginx’s event loop.
Key Design Principles
- Non-blocking by default: Asynchronous socket operations (
ngx_socket_connect,ngx_socket_recv) useyieldto suspend/resume coroutines without blocking workers. - No shared state per request: Global variables and static class properties are unsafe across requests; each invocation runs in an isolated PHP context.
- Zero-process overhead: Eliminates inter-process communication, serialization, and process spawning inherent in traditional PHP SAPIs.
- Minimalist integration: Does not replace PHP-FPM—it complements it. Use it for edge logic (authz, redirects, logging) and delegate heavy workloads to backend services.
System Requirements
- Linux only (no macOS or Windows support)
- PHP 7.0–7.4, compiled with
--enable-embed - Nginx 1.4.7–1.17.8
- Requires ngx_devel_kit as a dependency
Installation Options
Source Compilation
# Build PHP with embed support
wget https://www.php.net/distributions/php-7.4.33.tar.gz
tar xzf php-7.4.33.tar.gz
cd php-7.4.33
./configure --prefix=/opt/php-embed --enable-embed=shared
make -j$(nproc) && sudo make install
# Build Nginx with ngx_php7
git clone https://github.com/rryqszq4/ngx_php7.git
wget https://nginx.org/download/nginx-1.20.2.tar.gz
tar xzf nginx-1.20.2.tar.gz
cd nginx-1.20.2
export PHP_CONFIG=/opt/php-embed/bin/php-config
export PHP_INC=/opt/php-embed/include/php
export PHP_LIB=/opt/php-embed/lib
./configure \
--prefix=/opt/nginx \
--with-ld-opt="-Wl,-rpath,$PHP_LIB" \
--add-module=../ngx_php7/third_party/ngx_devel_kit \
--add-module=../ngx_php7 \
--user=www-data --group=www-data
make -j$(nproc) && sudo make install
RPM-Based Systems (CentOS/RHEL 7+)
yum install -y https://extras.getpagespeed.com/release-el7-latest.rpm
yum install -y epel-release
yum install -y nginx-module-php7
# In /etc/nginx/nginx.conf, add before 'events {':
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_php_module.so;
Docker Quickstart
git clone https://github.com/rryqszq4/ngx_php7.git
cd ngx_php7/docker
docker build -t nginx-php7 .
# Run with custom config mounted
docker run -d -p 8080:80 \
-v $(pwd)/examples/default.conf:/etc/nginx/conf.d/default.conf \
--name php7-server nginx-php7
Configuration Overview
The following minimal nginx.conf demonstrates core capabilities:
worker_processes auto;
events {
worker_connections 10240;
}
http {
include mime.types;
default_type application/json;
# Load PHP configuration
php_ini_path /opt/php-embed/etc/php.ini;
server {
listen 80;
server_name _;
# Log request URI using embedded PHP
location = /uri {
content_by_php_block {
$uri = \$_SERVER['REQUEST_URI'] ?? '';
echo json_encode(['uri' => $uri, 'timestamp' => time()]);
}
}
# Parse query string safely (replaces $_GET)
location = /query {
content_by_php_block {
$params = ngx_query_args();
http_response_code(200);
header('Content-Type: application/json');
echo json_encode($params);
}
}
# Handle POST data (replaces $_POST)
location = /post {
client_max_body_size 16k;
content_by_php_block {
$body = ngx_post_args();
echo json_encode([
'received' => !empty($body),
'keys' => array_keys($body)
]);
}
}
# Non-blocking HTTP client call
location = /stock {
content_by_php_block {
$sock = ngx_socket_create();
if (yield ngx_socket_connect($sock, 'hq.sinajs.cn', 80)) {
$req = "GET /list=s_sh000001 HTTP/1.1\r\n" .
"Host: hq.sinajs.cn\r\n" .
"Connection: close\r\n\r\n";
yield ngx_socket_send($sock, $req, strlen($req));
$resp = '';
yield ngx_socket_recv($sock, $resp, 4096);
echo $resp;
yield ngx_socket_close($sock);
} else {
echo "Connection failed";
}
}
}
# Set response headers dynamically
location = /headers {
content_by_php_block {
ngx_header_set('X-Powered-By', 'ngx_php7/0.0.21');
ngx_header_set('Cache-Control', 'no-cache');
echo '{"status":"ok"}';
}
}
}
}
Request Lifecycle Hooks
PHP code can be injected at every major Nginx phase:
| Directive | Phase | Description |
|---|---|---|
init_worker_by_php_block |
Worker startup | Runs once per worker process (e.g., init connection pools) |
rewrite_by_php_block |
Rewrite | Modify URI or internal redirect before routing |
access_by_php_block |
Access | Implement auth, rate limiting, IP filtering |
content_by_php_block |
Content | Generate full response body (most common) |
header_filter_by_php_block |
Header filter | Modify outgoing response headers |
log_by_php_block |
Log | Custom logging after request completion |
Core PHP APIs
Request Inspection
ngx_request_method()→ e.g.,"GET"ngx_request_uri()→ raw URI pathngx_request_headers()→ associative array of all request headersngx_request_remote_addr()→ client IP (respectingX-Forwarded-Forrequires menual parsing)
Input Handling
ngx_query_args()→ parsed query string (URL-decoded, no superglobals)ngx_post_args()→ form-encoded or multipart POST bodyngx_var_get('host')→ fetch Nginx variable values (e.g.,$host,$request_time)
Response Control
ngx_header_set('Content-Type', 'text/plain')ngx_redirect('/login', 302)ngx_exit(403)→ terminate immediately with statusngx_log_error(NGX_LOG_WARN, 'Custom warning')
Async I/O Primitives
yield ngx_sleep(2)— suspend for 2 seconds$sock = ngx_socket_create(); yield ngx_socket_connect($sock, $host, $port);yield ngx_socket_send($sock, $data, $len);yield ngx_socket_recv($sock, $buffer, 8192);
Important Behavioral Notes
- No persistent memory: Avoid static properties or global caches—they persist across requests and cause race conditions.
- Blocking calls are discouraged: Native
fopen(),file_get_contents(), orcurl_exec()will stall the entire worker. Prefer async socket APIs or offload to upstreams. - PHP extensions must be thread-safe: Only extensions built with ZTS (Zend Thread Safety) are safe in this embedded context.
- INI directives apply globally:
php_ini_pathloads one config per Nginx instance—not per request.
Testing & Validation
The module includes a comprehensive Test::Nginx-based test suite covering:
- Basic script execution and output capture
- Variable injection and scope isolation
- Socket lifecycle (connect/send/recv/close)
- Header, cookie, and redirect behavior
- Error handling and logging fidelity
All tests pass under CI environments targeting Ubuntu 14.04+, GCC 4.8+, and Nginx 1.12–1.20.
Licensing
Released under the 2-clause BSD License. Copyright (c) 2016–2020 rryqszq4.