Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.

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:

Enable and start the service
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

VariableDefaultPurpose
ZEALPHP_HOST0.0.0.0Bind address
ZEALPHP_PORT8080Listen port
ZEALPHP_WORKERSdefault_worker_count(4) — min(4, floor(cgroup CPU quota)) when a quota exists; 4 otherwiseHTTP 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_WORKERS8Async task worker count. Set 0 to disable (saves ~8 processes if you don't use App::getServer()->task()).
ZEALPHP_MAX_REQUEST100000Requests per worker before clean recycle. Bounds memory growth in long-running workers; set 0 to disable.
ZEALPHP_MAX_CONNOpenSwoole defaultmax_conn server setting
ZEALPHP_MAX_COROUTINEOpenSwoole defaultmax_coroutine server setting
ZEALPHP_BACKLOGOpenSwoole defaultTCP listen backlog
ZEALPHP_REACTOR_NUMOpenSwoole defaultReactor thread count
ZEALPHP_DAEMONIZE0OpenSwoole daemonize. Set by scripts/zealphp.sh; do not set under systemd.
ZEALPHP_PID_FILE$LOG_DIR/zealphp_$PORT.pidPID file location

Logging

VariableDefaultPurpose
ZEALPHP_LOG_DIR/tmp/zealphpBase directory for all log files
ZEALPHP_LOG_FILE(per-stream)Single-file override for all log streams
ZEALPHP_ACCESS_LOG_FILE$LOG_DIR/access.logPer-request access log
ZEALPHP_DEBUG_LOG_FILE$LOG_DIR/debug.logelog() output
ZEALPHP_ZLOG_FILE$LOG_DIR/zlog.logzlog() output
ZEALPHP_SERVER_LOG_FILE$LOG_DIR/server.log (daemon only)OpenSwoole server log
ZEALPHP_ACCESS_LOG1Toggle access logging
ZEALPHP_DEBUG_LOG1Toggle elog() debug output. Disable in production.
ZEALPHP_LOG_ASYNC1Use coroutine channels for log writes (zero per-request overhead)
ZEALPHP_RECYCLE_LOG1Log [recycle] worker N exited after K requests, peak RSS X MB when a worker is recycled. Disable to silence the line.
ZEALPHP_BENCH_MODE0Disables all logging for benchmarks

Middleware & sessions

VariableDefaultPurpose
ZEALPHP_SESSION_SECUREauto-detectForce Secure cookie flag (1/0). Auto-detects HTTPS via HTTPS, HTTP_X_FORWARDED_PROTO, or SERVER_PORT=443.
ZEALPHP_HTTP_COMPRESSION1Enable OpenSwoole native HTTP compression.
ZEALPHP_COMPRESSION_MIDDLEWARE0Register the reference CompressionMiddleware (only if ZEALPHP_HTTP_COMPRESSION=0).
ZEALPHP_INI_ISOLATE0Register 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_MIDDLEWARE0Enables the demo authentication/validation middleware in app.php. Off in production.

Site

VariableDefaultPurpose
ZEALPHP_SITE_URLfalls back to ZEALPHP_SITE_HOST, then https://php.zeal.ninjaCanonical site URL used by site_url() and absolute-URL generation (never derived from the request host)
ZEALPHP_SITE_HOSTFallback host if ZEALPHP_SITE_URL is not set

nginx reverse proxy

/etc/nginx/sites-available/zealphp
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

Caddyfile
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:

docker-compose.yml (production sketch)
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, not 80.
  • 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 mode 0700.
  • Disable debug logging in production (ZEALPHP_DEBUG_LOG=0 or unset).
  • Set ZEALPHP_MAX_REQUEST (default 100000). 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/ with logrotate.
  • Behind a TLS-terminating proxy, leave ZEALPHP_SESSION_SECURE unset — the framework auto-detects HTTPS from X-Forwarded-Proto.
  • Use php app.php restart to cleanly drain in-flight requests and restart; expect a brief listener-down window between stop and re-bind. For seamless in-place recycling rely on ZEALPHP_MAX_REQUEST worker 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.