NAME

pagi-server - PAGI Reference Server CLI

SYNOPSIS

pagi-server [options] [app] [key=value ...]

# Serve current directory (default)
pagi-server

# Serve a specific directory
pagi-server PAGI::App::Directory root=/var/www

# Run a PAGI app file
pagi-server ./app.pl
pagi-server --app app.pl        # legacy syntax

# With options
pagi-server -p 8080 PAGI::App::Directory root=/var/www
pagi-server -w 4 ./myapp.pl

DESCRIPTION

pagi-server is the command-line interface for running PAGI applications. It can load apps from files or from installed PAGI::App::* modules.

OPTIONS

--lib, -I PATH

Add PATH to @INC before loading the app. Can be specified multiple times to add multiple paths, similar to perl -I.

pagi-server -I ./lib -I ./vendor/lib ./app.pl
--app, -a FILE

Path to .pl/.psgi file returning PAGI app coderef. Legacy option for backward compatibility; you can also just pass the file as an argument.

--host, -h HOST

Bind address (default: 127.0.0.1, localhost only)

The default only accepts connections from localhost, which is secure by default for development. For headless servers or production deployments where remote clients need to connect:

# Bind to all IPv4 interfaces
pagi-server --host 0.0.0.0 ./app.pl

# Bind to specific interface
pagi-server --host 192.168.1.100 ./app.pl
--port, -p PORT

Bind port (default: 5000)

--workers, -w NUM

Number of worker processes (default: 1)

--max-requests NUM

Maximum number of requests a worker process will handle before restarting. After serving this many requests, the worker gracefully shuts down and the parent spawns a replacement.

Default: 0 (disabled - workers run indefinitely)

When to use: Long-running deployments where gradual memory growth is a concern, or applications with known memory leaks.

Note: Only applies in multi-worker mode (--workers > 1). In single-worker mode, this setting is ignored.

--max-connections NUM

Maximum number of concurrent connections per worker before returning HTTP 503 Service Unavailable. This prevents "Too many open files" crashes.

Default: 0 (auto-detect from ulimit - 50)

Behavior: When at capacity, new connections receive a 503 response with a Retry-After: 5 header. The TCP backlog queues additional connections.

When to adjust:

  • Getting 503s under load? Increase ulimit or set higher limit

  • Want predictable capacity? Set explicit limit matching your ulimit

  • Memory constrained? Lower the limit to reduce concurrent request memory

Note: In multi-worker mode, this limit applies per-worker. With --workers 4 --max-connections 100, total capacity is 400 connections.

--max-body-size NUM

Maximum request body size in bytes. Requests exceeding this limit receive HTTP 413 (Payload Too Large).

Default: 10,000,000 (10MB)

Special value: 0 means unlimited (not recommended for public servers).

When to adjust:

  • File uploads? Increase to 50-100MB as needed

  • API with large JSON? Increase to match expected payload sizes

  • Behind reverse proxy? Can set to 0 if proxy enforces its own limit

--log-level LEVEL

Set the logging verbosity level. Messages below this level are suppressed.

Levels: debug, info, warn, error

Default: info

Production Options

-D, --daemonize

Fork to background and run as a daemon. Errors during startup (including binding to port) are reported before daemonizing.

--pid FILE

Write the process ID to FILE. The file is removed on clean shutdown. Useful for init scripts: kill $(cat /var/run/pagi.pid)

--user USER

After binding to the port, drop privileges to run as USER. Requires starting the server as root. Commonly used with privileged ports (80, 443).

--group GROUP

After binding to the port, drop privileges to run as GROUP. If not specified but --user is, uses the user's primary group.

--listener_backlog -b

Listener queue size. No default, if left blank then the server sets a default that is rational for itself.

--timeout

Seconds before we timeout the request. If left blank will default to whatever is default for the server.

--loop, -l BACKEND

Event loop backend (Poll, EV, Epoll, UV). Default is auto-detect.

--ssl-cert FILE

Path to SSL certificate (enables HTTPS)

--ssl-key FILE

Path to SSL private key

--access-log FILE

Path to access log file (default: STDERR)

--no-access-log

Disable access logging entirely. Eliminates per-request I/O overhead, improving throughput by 5-15% depending on workload. Useful for benchmarking or when access logs are handled by a reverse proxy.

--reuseport

Enable SO_REUSEPORT mode for multi-worker servers. Each worker creates its own listening socket with SO_REUSEPORT.

Linux 3.9+: The kernel load-balances incoming connections across workers, reducing accept() contention and improving p99 latency under high concurrency.

macOS/BSD: SO_REUSEPORT allows binding but does NOT provide kernel load balancing. May actually decrease performance - benchmark before using.

--max-receive-queue NUM

Maximum number of messages in the WebSocket receive queue before the connection is closed with code 1008 (Policy Violation). This is a DoS protection mechanism to prevent memory exhaustion from slow consumers.

Default: 1000 messages

Unit: Message count, not bytes. Each WebSocket frame counts as one message.

Tuning: Lower to 100-500 for slow message processing. Raise to 5000-10000 for trusted high-throughput clients. Memory risk per connection is approximately max_receive_queue × average_message_size.

--max-ws-frame-size NUM

Maximum size in bytes for a single WebSocket frame payload. When a client sends a frame larger than this limit, the connection is closed.

Default: 65536 bytes (64KB)

Unit: Bytes (not messages). This controls individual frame size.

Tuning: For chat/control messages, default 64KB is sufficient. For file uploads via WebSocket, increase to 1-16MB. Memory impact: each connection can buffer up to this amount during frame parsing.

--quiet, -q

Suppress startup banner

--help

Show this help

Signal Handling

TERM, INT

Graceful shutdown. Stops accepting new connections, waits for existing requests to complete, then exits.

HUP

Graceful restart (multi-worker mode only). Terminates all current workers and spawns fresh replacements. Useful for config/code changes.

TTIN

Increase worker count by 1 (multi-worker mode only).

TTOU

Decrease worker count by 1 (multi-worker mode only). Maintains at least 1 worker.

APP SPECIFICATION

The app can be specified in three ways:

Module Name

If the argument contains ::, it's treated as a Perl module name:

pagi-server PAGI::App::Directory root=/var/www

The module must have new() and to_app() methods. Constructor arguments are passed as key=value pairs.

File Path

If the argument contains / or ends with .pl/.psgi:

pagi-server ./app.pl
pagi-server /path/to/myapp.psgi

The file must return a PAGI app coderef.

Default

If no app is specified, serves the current directory:

pagi-server
# equivalent to: pagi-server PAGI::App::Directory root=.

EXAMPLES

# Serve current directory on default port
pagi-server

# Serve /var/www on port 8080
pagi-server -p 8080 PAGI::App::Directory root=/var/www

# Run an app file with HTTPS
pagi-server -a myapp.pl -p 3000 --ssl-cert cert.pem --ssl-key key.pem

# Run with multiple workers
pagi-server -w 4 ./myapp.pl

# Headless server / remote access (bind to all interfaces)
pagi-server --host 0.0.0.0 -p 8080 ./myapp.pl

# Production: all interfaces, workers, daemonized
pagi-server --host 0.0.0.0 -p 80 -w 4 -D --pid /var/run/pagi.pid ./myapp.pl

# Add library paths (like perl -I)
pagi-server -I ./lib -I ./local/lib ./myapp.pl

# Use EV event loop for better performance
pagi-server --loop EV ./myapp.pl

# Maximum performance (no access logging)
pagi-server --no-access-log -w 8 ./myapp.pl

# Optimal macOS performance (EV + kqueue)
LIBEV_FLAGS=8 pagi-server --loop EV --no-access-log ./myapp.pl

# High concurrency with SO_REUSEPORT (reduces accept contention)
pagi-server --reuseport -w 8 ./myapp.pl

# Workers restart after 10k requests (prevents memory growth)
pagi-server -w 4 --max-requests 10000 ./myapp.pl

# Limit max connections (prevents FD exhaustion)
pagi-server --max-connections 200 ./myapp.pl

# Enable debug logging
pagi-server --log-level debug ./myapp.pl

# Or via environment variable
IO_ASYNC_LOOP=EV pagi-server ./myapp.pl

# Run a proxy app
pagi-server PAGI::App::Proxy target=http://backend:3000

# Directory listing with hidden files
pagi-server PAGI::App::Directory root=/home show_hidden=1

BUNDLED APPS

The following PAGI::App::* modules are available:

PAGI::App::Directory

Serve static files with directory listings.

pagi-server PAGI::App::Directory root=/var/www
PAGI::App::File

Serve static files (no directory listings).

pagi-server PAGI::App::File root=/var/www
PAGI::App::Proxy

HTTP reverse proxy.

pagi-server PAGI::App::Proxy target=http://backend:3000
PAGI::App::Redirect

URL redirector.

pagi-server PAGI::App::Redirect location=https://example.com
PAGI::App::URLMap

Mount multiple apps at different paths.

PAGI::App::Cascade

Try multiple apps in sequence.

See individual module documentation for available options.

ENVIRONMENT

IO_ASYNC_LOOP

Specifies which IO::Async::Loop backend to use. The default is auto-detection. For better performance, especially with many concurrent connections:

IO_ASYNC_LOOP=EV pagi-server ./myapp.pl      # Cross-platform (libev)
IO_ASYNC_LOOP=Epoll pagi-server ./myapp.pl   # Linux-specific

The --loop option takes precedence over this environment variable.

LIBEV_FLAGS

When using the EV backend, this controls which libev backend is used internally. By default, EV may choose a suboptimal backend (select instead of kqueue on macOS).

# Force kqueue on macOS for optimal performance (~6% faster)
LIBEV_FLAGS=8 pagi-server --loop EV ./myapp.pl

Values: 1=select, 2=poll, 4=epoll (Linux), 8=kqueue (macOS/BSD)

EVENT LOOP BACKENDS

Poll

Default fallback. Uses poll() system call. Works everywhere but slower with many connections.

EV

High-performance backend using libev. Recommended for production. Install: cpanm IO::Async::Loop::EV EV

Epoll

Linux-specific backend using epoll. O(1) complexity. Install: cpanm IO::Async::Loop::Epoll

UV

Backend using libuv (Node.js's event library). Cross-platform. Install: cpanm IO::Async::Loop::UV UV

SEE ALSO

PAGI::Runner, PAGI::Server, PAGI::App::Directory

AUTHOR

John Napiorkowski <jjnapiork@cpan.org>

LICENSE

This software is licensed under the same terms as Perl itself.