PAGI
view release on metacpan or search on metacpan
lib/PAGI/Server.pm view on Meta::CPAN
# Forward client info (since PAGI can't see it over Unix socket):
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;
}
}
B<Important:> The C<keepalive> directive is critical for performance. Without
it, nginx opens a new Unix socket connection for every request.
C<proxy_http_version 1.1> and C<proxy_set_header Connection ""> are required
for keepalive to work.
B<With TLS termination at nginx:>
server {
listen 443 ssl http2;
ssl_certificate /etc/ssl/myapp.crt;
ssl_certificate_key /etc/ssl/myapp.key;
location / {
proxy_pass http://pagi_backend;
# ... same proxy headers as above
}
}
nginx handles TLS and HTTP/2 with clients, then speaks plain HTTP/1.1 to
PAGI over the Unix socket. This is the recommended production pattern.
=head2 Socket Permissions and Security
By default, the socket file inherits permissions from the process umask.
Use C<socket_mode> to set explicit permissions:
# CLI
pagi-server --socket /var/run/myapp/pagi.sock --socket-mode 0660 ./app.pl
# Programmatic
PAGI::Server->new(
app => $app,
socket => '/var/run/myapp/pagi.sock',
socket_mode => 0660,
);
B<Production recommendations:>
=over 4
=item * B<Use a dedicated directory>, not C</tmp/>. Directories like
C</var/run/myapp/> or C</run/myapp/> prevent symlink attacks and provide
an additional permission layer.
=item * B<Use C<0660> with a shared group.> Create a group (e.g., C<myapp>)
that both the application user and the nginx user belong to:
sudo groupadd myapp
sudo usermod -aG myapp www-data # nginx user
sudo usermod -aG myapp myappuser # app user
sudo mkdir -p /var/run/myapp
sudo chown myappuser:myapp /var/run/myapp
sudo chmod 0750 /var/run/myapp
=item * B<Use systemd C<RuntimeDirectory>> for automatic directory management:
# /etc/systemd/system/myapp.service
[Service]
User=myappuser
Group=myapp
RuntimeDirectory=myapp
RuntimeDirectoryMode=0750
ExecStart=/usr/local/bin/pagi-server \
--socket /run/myapp/pagi.sock \
--socket-mode 0660 \
--workers 4 \
/opt/myapp/app.pl
systemd creates C</run/myapp/> on service start and cleans it up on stop.
=back
=head2 TLS Over Unix Sockets
TLS can be used over Unix sockets, though this is unusual â normally the
reverse proxy handles TLS termination. When TLS is configured on a Unix
socket listener, the server logs an info-level note suggesting reverse proxy
TLS termination instead.
The combination is allowed because it has legitimate uses (encrypted
inter-container communication, compliance requirements). All major ASGI
servers (Uvicorn, Hypercorn, Granian) also allow it.
=head2 HTTP/2 Over Unix Sockets
h2c (HTTP/2 cleartext) works over Unix sockets. This is useful for gRPC
backends or reverse proxies that support HTTP/2 to upstreams (e.g., Envoy).
Note that nginx does B<not> currently support HTTP/2 to upstream backends
(except for gRPC via C<grpc_pass>).
=head2 Scope Differences
For Unix socket connections, the PAGI scope differs from TCP connections:
=over 4
=item * B<C<client> is absent> â Unix sockets have no peer IP address or
port. The C<client> key is omitted entirely from the scope hashref (not set
to C<undef>). This is spec-compliant: the PAGI specification marks C<client>
as optional.
=item * B<C<server> is C<[$socket_path, undef]>>> â instead of C<[$host, $port]>.
=back
B<Middleware implications:> Any middleware that accesses C<< $scope->{client} >>
must check C<< exists $scope->{client} >> first. For client IP identification
behind a reverse proxy, use C<X-Forwarded-For> or C<X-Real-IP> headers
instead of C<< $scope->{client} >>. The C<PAGI::Middleware::XForwardedFor>
middleware (if available) handles this automatically.
B<Access log:> Unix socket connections log C<unix> as the client IP in the
( run in 1.812 second using v1.01-cache-2.11-cpan-71847e10f99 )