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, BIND9, Squid, nftables, WireGuard, GoAccess, and the rest of the stack arx manages.

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 an upstream URLmissus, nomina, Node/Go apps, anything with its own listener

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 ships a two-layer outbound filter you can flip on per site:

  • BIND RPZ — site's PHP-FPM pool resolves against 127.0.0.1; the local BIND has an RPZ that returns NXDOMAIN for any name not on the site's whitelist.
  • 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 same whitelist.

One whitelist file per site at /srv/arx/whitelist/<domain>.txt. Edit, then click Apply — arx regenerates the RPZ zone and 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 whitelist. Way less guesswork.

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) shows nginx active connections, per-pool worker counts, request rates, and recent BIND/RPZ activity, 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.