User guide

From a clean Debian box to a working site with HTTPS in about ten minutes. Then the daily-use bits — backups, multiple PHP versions, outbound filtering, troubleshooting.

1. Prerequisites

  • A clean Debian 12 (bookworm) or Debian 13 (trixie) machine: VPS, Raspberry Pi 4/5 with an SSD, or a Proxmox LXC. 2 GB RAM is the sensible floor for one or two PHP sites; a Pi 4 with 4 GB handles a homelab cluster fine.
  • Root access. arx itself runs as root because it edits /etc/nginx/, runs useradd, nft, and certbot.
  • A public IP with ports 80 and 443 reachable from the internet (for Let's Encrypt issuance and serving sites).
  • At least one hostname with DNS pointing at the box. Conventionally panel.example.com for arx itself plus one per site you'll host.

2. Install arx

Add the NetForge apt repository, then install. apt pulls in nginx, certbot, Squid, nftables, WireGuard, GoAccess, and the rest of the stack arx manages. (Not BIND9 nor Postfix — those belong to nomina and missus.)

curl -fsSL https://apt.netforge.it/free/key.gpg \
    | sudo gpg --dearmor -o /usr/share/keyrings/netforge.gpg
echo "deb [signed-by=/usr/share/keyrings/netforge.gpg] https://apt.netforge.it/free stable main" \
    | sudo tee /etc/apt/sources.list.d/netforge.list
sudo apt update
sudo apt install arx

Create the first admin and start the service:

sudo arx admin create yourname
sudo systemctl start arx
sudo systemctl status arx --no-pager

The postinst lays out /srv/arx/ (sites, state, backups, certs), generates a 256-bit session secret in /etc/arx/env, and enables three timers: arx-renew (Let's Encrypt twice a day), arx-refresh-cf-ips (Cloudflare IP ranges weekly), and arx-stats-refresh (GoAccess daily).

3. First login

arx binds to 127.0.0.1:9443 on purpose — the panel is not exposed to the world. Reach it from your laptop with an SSH tunnel:

ssh -L 9443:127.0.0.1:9443 root@your-server

Open http://127.0.0.1:9443/ in your browser. Log in with the admin you created, enrol 2FA from /account (recommended).

Promote to public HTTPS as soon as possible: create a reverse_proxy site (see next section) pointing at 127.0.0.1:9443 with hostname panel.example.com. arx requests a Let's Encrypt cert and serves itself over HTTPS — at that point you can drop the SSH tunnel and use the panel URL directly.

4. Create your first site

arx supports three site types. Pick one when you click + New site on /sites:

TypeWhat nginx doesUse for
PHPServes files from htdocs/, hands .php to a per-site PHP-FPM poolWordPress, Laravel, custom PHP
StaticServes files from htdocs/, returns 404 for anything missingHugo/Astro/Jekyll output, static SPAs
Reverse proxyForwards every request to one upstream — or load-balances across manymissus, nomina, Node/Go apps, anything with its own listener
Load balancing: a reverse-proxy site can fan out to multiple backends with any nginx open-source method — round-robin (default), least_conn, ip_hash (sticky by client IP), hash (sticky by a custom key, optionally consistent), or random. Each backend takes per-server weight, max_fails, fail_timeout, max_conns, plus backup and down (drain a node for maintenance), and the pool can keep idle keepalive connections open. Edit the pool from the site detail page at any time. Failure detection is passive (max_fails/fail_timeout) — active health checks need NGINX Plus and are not offered here.

For each site arx creates a dedicated Unix user and an isolated webroot at /srv/arx/sites/<domain>/htdocs/. The user owns its files; nginx reads via group membership. Other sites' users cannot read each other's webroots.

SFTP only: you can flag a site as "SFTP-only" — the Unix user gets a shell of /usr/sbin/nologin and the home directory is chrooted. Useful when you give a developer access to one site without granting any shell on the box.

5. SSL with Let's Encrypt

Each site has an SSL panel. Click Issue certificate; arx runs certbot --webroot against /.well-known/acme-challenge/ on the live nginx (no service downtime), drops the cert under /etc/letsencrypt/live/<domain>/, and reloads nginx.

The arx-renew.timer systemd unit runs arx ssl renew twice a day with a 1 h jitter (matches the stock certbot.timer). Renewals deploy automatically and reload nginx with no intervention.

You can also upload a manually-issued certificate (intermediate + key + chain) through the same panel — useful for wildcards from a different CA.

6. Multiple PHP versions

arx pre-configures Ondřej Surý's repo (packages.sury.org/php) at install time, so all PHP versions from 7.x to 8.4 are one apt away:

sudo apt install php8.4-fpm php8.4-mysql php8.4-curl php8.4-mbstring \
                 php8.4-xml php8.4-zip php8.4-gd php8.4-intl

The panel detects every installed version. When you create a PHP site you pick the version from a dropdown; arx generates a per-site FPM pool under /srv/arx/php/pools/<version>/<domain>.conf and symlinks it into /etc/php/<version>/fpm/pool.d/.

Each pool gets open_basedir, a dedicated tmp directory, and FPM tuning that you can edit per site (presets minimal, light, wordpress, heavy, or custom) — or leave alone, the defaults are sane for the workload type you picked.

7. Outbound filtering per site

By default a hosted site can connect anywhere. WordPress plugins, Composer packages, external APIs — sometimes useful, sometimes a foothold. arx filters outbound at the HTTP/HTTPS layer, per site:

  • Squid SNI peek + nftables NAT per-UID — the site's Unix UID's outbound TCP gets redirected through Squid; Squid peeks the TLS SNI and accepts only hostnames on the site's allowlist. No app config, no proxy env vars — it's enforced at the network layer by UID.

One allowlist file per site at /srv/arx/whitelist/<domain>.txt. Edit, then click Apply — arx regenerates the Squid ACL atomically.

Profiles: wordpress (api.wordpress.org, plugins repo, gravatar, ...), laravel, static, custom. Start from a profile and add what your site actually needs.

Learning mode: with the toggle in passthru + verbose log, you let the site run for a day, then read which hostnames it actually called and turn the list into a tight allowlist. Way less guesswork.
DNS-layer filtering is not arx's job. arx never touches BIND — no RPZ, no zones. If you also want DNS-level blocking (NXDOMAIN for names off the list), run nomina alongside arx and configure RPZ there. The two coexist cleanly on the same box.

8. Firewall & banning

arx owns one nftables table — inet arx — rendered to /srv/arx/nftables/arx.nft and applied via nft -f. INPUT defaults to drop; loopback, established connections, WireGuard, and ports 80/443 are accepted; SSH and the panel are gated to a list of admin source CIDRs.

The Firewall page shows the live ruleset and a blocklist nftables set with flags timeout. Click Ban IP, enter an address and a duration (1 h, 24 h, 30 d, …) and arx adds it to the set. The kernel drops it without bouncing to a userspace daemon.

WireGuard admin plane: arx ships a one-click WireGuard generator. Setup wg0 + a client config for your laptop, then close public :22 and panel access — both stay reachable over the WG mesh only. arx waits for a heartbeat before committing the close, so a misconfigured rule rolls back automatically before locking you out.

9. Backups & migration

Everything arx owns lives under /srv/arx/: sites' webroots, state.db, certs cache, FPM pool sources, per-site whitelists, WireGuard keys. So the backup story is plain rsync.

# on the source box
sudo rsync -aHAX --delete /srv/arx/ backup-host:/srv/arx-snapshot/

# on the new box, after `apt install arx`
sudo rsync -aHAX backup-host:/srv/arx-snapshot/ /srv/arx/
sudo arx rehydrate

arx rehydrate reads /srv/arx/ and re-creates the side-effects that don't live there: per-site Unix users (useradd), the FPM pool symlinks under /etc/php/<ver>/fpm/pool.d/, the nginx include stub, the nftables ruleset, and reloads the daemons.

For per-site tarballs the Backup page on each site bundles webroot + DB dump (if you've configured a DB host) into a single <domain>_<timestamp>.tar.gz under /srv/arx/backups/.

10. Troubleshooting

First stop:

sudo arx check

Walks the filesystem, daemons, listening ports, ssl certs, and reports each bullet as ✓ / ! / ✗. Most "why isn't this working" questions have a clear answer in arx check's output.

Service logs:

sudo journalctl -u arx -n 100 --no-pager
sudo journalctl -u nginx -n 100 --no-pager
sudo journalctl -u php8.4-fpm -n 100 --no-pager

Per-site logs (raw nginx access + error):

tail -f /srv/arx/logs/nginx/<domain>/access.log
tail -f /srv/arx/logs/nginx/<domain>/error.log

The panel's Live dashboard (/system/live) aggregates nginx active connections and request rates with per-site PHP-FPM pool worker counts, refreshed every few seconds.

If you ever need to nuke arx and rebuild: apt purge arx intentionally does not delete /srv/arx/. Even cascade purges from unrelated package removals leave your data alone. To actually wipe, remove the directories by hand — sudo rm -rf /srv/arx /etc/arx — that's an explicit, deliberate action you have to take.