Linux websites

Websites on Linux.

PXM manages static websites, PHP applications, Node.js applications and HTTP reverse proxies on Ubuntu 22.04, 24.04 and 26.04. Use an unprivileged account dedicated to your applications. PHP, Node.js, sites and HTTPS are free features.

Enable web routing

Terminal
pxm php install 8.5
pxm web enable --email you@example.com
pxm web status

The enable command requests administrator access to install the router's systemd unit and enable startup after logout. The router runs as your account, with only the capability needed to bind ports 80 and 443. Application code runs in separate unprivileged user units. Existing database services are not stopped or reconfigured by this command.

PXM downloads its signed Caddy package. Customers do not need Caddy, Node.js, PHP, Homebrew, Docker or a compiler preinstalled. PXM leaves foreign services alone. If Apache, nginx or another web server owns ports 80 or 443, schedule its shutdown or move the website deliberately before starting PXM routing.

Public PHP sites

Terminal
pxm site add app.example.com /home/deploy/releases/current-release/public --type php --php 8.5
pxm site info app.example.com
pxm site logs app.example.com

Point the domain's A record to this server. Add an AAAA record only when its IPv6 address is reachable. Allow incoming TCP ports 80 and 443 and outgoing DNS and HTTPS. PXM does not edit public DNS or firewall rules. Caddy obtains and renews public certificates automatically and redirects HTTP to HTTPS. HTTPS becomes ready after the certificate authority can validate the hostname.

Use the application's actual public directory, such as Laravel's public directory. Site roots must be owned by the PXM account, must not be writable by other accounts, and cannot expose PXM's private state directory or any ancestor of it. Resolve deployment symlinks to an immutable release directory before adding or updating a site. Configuration and source-control dotfiles, database dump files and common source manifests are refused by the web router.

Each PHP site has its own PHP-FPM worker. Defaults are a 256 MB memory limit, a 64 MB upload/post limit, a 60-second execution timeout and up to eight workers. Override them per site:

Terminal
pxm site update app.example.com --memory 512M --upload 128M --timeout 120 --workers 12
pxm site update app.example.com --php 8.5

Changing the global CLI PHP version does not silently restart production sites. A site records its exact authenticated runtime package. Install the new runtime first, then update the selected site when ready.

Node.js

Terminal
pxm node available
pxm node install 24
pxm node use 24
pxm node --version
pxm npm --version

cd /home/deploy/my-app
pxm node isolate 24
pxm npm ci
pxm npm run build
pxm site add node.example.com /home/deploy/my-app --type node --node 24 --entry server.js

The app must listen on 127.0.0.1 using process.env.PORT. PXM sets HOST=127.0.0.1, PORT and NODE_ENV=production. It rejects a new worker that binds a public listener or fails to become ready. The entry runs directly through the selected Node executable, without a shell. Implement a SIGTERM handler that stops accepting requests and finishes current work.

Run pxm node install 24 again to install a published update in that major. The selected registry entry is replaced instead of creating a second selectable patch version. Running sites keep their exact old binary until pxm site update node.example.com --node 24. Immutable package files remain while a production site may still reference them. Do not remove package directories manually.

pxm node isolate writes a major-version .node-version pin. An unavailable manually pinned exact version produces an error instead of silently selecting another version. pxm node unisolate removes the current directory's pin. npm, npx and optional bundled Corepack tools run through PXM; global npm files and Corepack downloads are stored separately from authenticated runtime files. Node 26 does not bundle Corepack.

Static sites and reverse proxies

Terminal
pxm site add docs.example.com /home/deploy/docs/dist --type static
pxm site add api.example.com --type proxy --upstream http://127.0.0.1:8080
pxm site update docs.example.com --aliases www.example.com

A proxy upstream must be an explicit HTTP loopback address and port. Its lifecycle remains the responsibility of its own service manager. Public upstream forwarding and arbitrary shell launch commands are not accepted.

Local development HTTPS

Terminal
pxm site add demo.test /home/deploy/demo/public --type php
pxm web trust

Local .test, .localhost and other recognized local names use Caddy's private certificate authority. Resolve the name to this machine through your client DNS or hosts file. web trust explicitly installs this workspace's public CA certificate in the Ubuntu trust store. Creating a site does not change system trust automatically. Browsers with separate trust stores and other client computers may require importing the public root certificate separately. Never share the CA private key.

Use pxm web untrust to remove this workspace's system trust entry. --tls off is available for intentionally HTTP-only sites. Public domains default to public HTTPS; local names cannot request public ACME certificates.

Deployments, recovery and operations

Terminal
pxm sites
pxm site update app.example.com --root /home/deploy/releases/2026-10-22/public
pxm site restart app.example.com
pxm site stop app.example.com
pxm site start app.example.com
pxm site remove app.example.com
pxm web reload
pxm web update-check
pxm web update

Enabled sites start at boot and after logout. Site updates start a replacement worker and verify its owned loopback listener or PHP-FPM socket before switching routes. This verifies process readiness, not application-specific HTTP health. Old workers are disabled for future startup and given at least two minutes to drain. They are never force-killed. Removing a site keeps its application files. Stopping a site removes its route and stops its worker after the drain period.

The desired site state and router configuration are stored together in an immutable generation. A failed reload reports that the saved generation is pending. The last confirmed workers remain available, including for crash recovery, until the replacement configuration is confirmed. During an interrupted transition both generations may start after reboot. Fix the reported issue and run pxm web reload to apply the desired configuration and retire obsolete workers. PXM does not silently undo a deployment after it may have accepted traffic. web status distinguishes a running router from a confirmed applied configuration. A router binary update performs a graceful restart and should be scheduled as maintenance.

To pass private environment values, use --env-file /absolute/private/file. The file must be mode 0600, owned by the account, and contain literal NAME=value lines. PXM does not evaluate shell expressions. Runtime loader overrides, Node options, HOME, PATH, HOST and PORT are reserved. PHP environment values containing $ or ; should instead be loaded by the application's own secret/configuration mechanism. Each deployment snapshots its environment privately; edit the file and update the site to apply a change.

Access logs rotate at 10 MiB with five retained files. Worker logs are available through pxm site logs and the systemd user journal. Router logs and the private certificate store live under PXM_HOME/web. Include application releases, configuration and the private certificate store in encrypted off-machine backups. Database backup export is a separate operation.

This is a single-account application manager, not a hostile multi-tenant sandbox. Only run code trusted by that account. Separate Unix accounts or separate machines are needed for mutually untrusted customers. PXM does not provision DNS records, firewalls, load balancers, queues, cluster failover or off-site backup storage.

Advanced operators may specify --acme-ca https://.../directory and --acme-root /absolute/ca.pem when enabling a new workspace. An explicit custom CA can also set --renew-interval 10s for certificate lifecycle testing. Public ACME uses Caddy's production renewal defaults. No global signing-key or TLS-verification bypass is provided.