Deploy
ZealPHP is a long-lived OpenSwoole server process. One process per port, N workers per process. Run it behind a reverse proxy (nginx, Caddy, HAProxy, Traefik — or Apache as mod_proxy) for TLS termination, static assets, request logging, and horizontal scaling: point the proxy at an upstream pool of ZealPHP backend addresses and it load-balances across them (round-robin / least-conn), exactly as it would across a pool of PHP-FPM or Node services. The built-in HTTP server replaces the per-node web server; the edge proxy is still your load balancer and TLS terminator.
systemd
A reference unit is shipped at deploy/zealphp.service. Copy it to /etc/systemd/system/zealphp.service, adjust User, WorkingDirectory, and ExecStart, then:
sudo systemctl daemon-reload
sudo systemctl enable zealphp
sudo systemctl start zealphp
sudo systemctl status zealphp
# Logs:
sudo journalctl -u zealphp -f
The unit is Type=simple — do not pass -d in ExecStart, systemd manages the lifecycle.
Environment variables
Boolean variables accept 1/0, true/false, on/off, yes/no.
Server
| Variable | Default | Purpose |
|---|---|---|
ZEALPHP_HOST | 0.0.0.0 | Bind address |
ZEALPHP_PORT | 8080 | Listen port |
ZEALPHP_WORKERS | default_worker_count(4) — min(4, floor(cgroup CPU quota)) when a quota exists; 4 otherwise | HTTP worker processes. OpenSwoole's own default is the host CPU count, which overshoots in CPU-limited containers — ZealPHP uses a cgroup-aware default instead. |
ZEALPHP_TASK_WORKERS | 8 | Async task worker count. Set 0 to disable (saves ~8 processes if you don't use App::getServer()->task()). |
ZEALPHP_MAX_REQUEST | 100000 | Requests per worker before clean recycle. Bounds memory growth in long-running workers; set 0 to disable. |
ZEALPHP_MAX_CONN | OpenSwoole default | max_conn server setting |
ZEALPHP_MAX_COROUTINE | OpenSwoole default | max_coroutine server setting |
ZEALPHP_BACKLOG | OpenSwoole default | TCP listen backlog |
ZEALPHP_REACTOR_NUM | OpenSwoole default | Reactor thread count |
ZEALPHP_DAEMONIZE | 0 | OpenSwoole daemonize. Set by scripts/zealphp.sh; do not set under systemd. |
ZEALPHP_PID_FILE | $LOG_DIR/zealphp_$PORT.pid | PID file location |
Logging
| Variable | Default | Purpose |
|---|---|---|
ZEALPHP_LOG_DIR | /tmp/zealphp | Base directory for all log files |
ZEALPHP_LOG_FILE | (per-stream) | Single-file override for all log streams |
ZEALPHP_ACCESS_LOG_FILE | $LOG_DIR/access.log | Per-request access log |
ZEALPHP_DEBUG_LOG_FILE | $LOG_DIR/debug.log | elog() output |
ZEALPHP_ZLOG_FILE | $LOG_DIR/zlog.log | zlog() output |
ZEALPHP_SERVER_LOG_FILE | $LOG_DIR/server.log (daemon only) | OpenSwoole server log |
ZEALPHP_ACCESS_LOG | 1 | Toggle access logging |
ZEALPHP_DEBUG_LOG | 1 | Toggle elog() debug output. Disable in production. |
ZEALPHP_LOG_ASYNC | 1 | Use coroutine channels for log writes (zero per-request overhead) |
ZEALPHP_RECYCLE_LOG | 1 | Log [recycle] worker N exited after K requests, peak RSS X MB when a worker is recycled. Disable to silence the line. |
ZEALPHP_BENCH_MODE | 0 | Disables all logging for benchmarks |
Middleware & sessions
| Variable | Default | Purpose |
|---|---|---|
ZEALPHP_SESSION_SECURE | auto-detect | Force Secure cookie flag (1/0). Auto-detects HTTPS via HTTPS, HTTP_X_FORWARDED_PROTO, or SERVER_PORT=443. |
ZEALPHP_HTTP_COMPRESSION | 1 | Enable OpenSwoole native HTTP compression. |
ZEALPHP_COMPRESSION_MIDDLEWARE | 0 | Register the reference CompressionMiddleware (only if ZEALPHP_HTTP_COMPRESSION=0). |
ZEALPHP_INI_ISOLATE | 0 | Register IniIsolationMiddleware (a framework PSR-15 middleware — not the ext stage). Snapshots ini_set() changes (timezone, error_reporting, display_errors, memory_limit, etc.) at request start and restores them on exit. Opt-in defense against ini changes leaking between requests on long-running workers. Coroutine-legacy already isolates ini_set() per coroutine at the ext level (the S9g stage), so this middleware is for non-ext setups. |
ZEALPHP_DEMO_MIDDLEWARE | 0 | Enables the demo authentication/validation middleware in app.php. Off in production. |
Site
| Variable | Default | Purpose |
|---|---|---|
ZEALPHP_SITE_URL | falls back to ZEALPHP_SITE_HOST, then https://php.zeal.ninja | Canonical site URL used by site_url() and absolute-URL generation (never derived from the request host) |
ZEALPHP_SITE_HOST | — | Fallback host if ZEALPHP_SITE_URL is not set |
nginx reverse proxy
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket upgrades on /ws/*
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# SSE: disable response buffering and bump timeout
proxy_buffering off;
proxy_read_timeout 86400;
}
}
Caddy reverse proxy
example.com {
reverse_proxy 127.0.0.1:8080 {
flush_interval -1 # streaming SSE / WebSocket
}
}
Docker
The repo ships a Dockerfile and docker-compose.yml. For production:
services:
app:
image: zealphp:0.4.12
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
ZEALPHP_HOST: 0.0.0.0
ZEALPHP_PORT: 8080
ZEALPHP_WORKERS: 8
ZEALPHP_SESSION_SECURE: "1"
volumes:
- sessions:/var/lib/php/sessions
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/"]
interval: 30s
timeout: 5s
retries: 3
volumes:
sessions:
Production checklist
- Run as a non-root user; bind to
8080, not80. - Pin OpenSwoole + ext-zealphp extension versions in your Dockerfile.
- Set
ZEALPHP_WORKERS≈ CPU cores. - Ensure the session save path (
/var/lib/php/sessions) is writable by the service user and mode0700. - Disable debug logging in production (
ZEALPHP_DEBUG_LOG=0or unset). - Set
ZEALPHP_MAX_REQUEST(default100000). Watch for the[recycle]access-log line to confirm the backstop is running. - Tune OPcache for long-running workers:
opcache.validate_timestamps=0+ restart on deploy. Full settings ↗ - Rotate logs in
/tmp/zealphp/withlogrotate. - Behind a TLS-terminating proxy, leave
ZEALPHP_SESSION_SECUREunset — the framework auto-detects HTTPS fromX-Forwarded-Proto. - Use
php app.php restartto cleanly drain in-flight requests and restart; expect a brief listener-down window between stop and re-bind. For seamless in-place recycling rely onZEALPHP_MAX_REQUESTworker rotation (workers recycle one-at-a-time after N requests, with no listener downtime). Front the process with a proxy health-check pool for true zero-downtime deploys.
More
Detailed write-up: docs/deployment.md on GitHub.