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.
Clean boxes only. arx takes over nginx,
PHP-FPM and Squid: it points their system configuration at
files it generates under /srv/arx
(/etc/nginx/conf.d/zz-arx.conf, one pool per site
in /etc/php/<ver>/fpm/pool.d/, a systemd
drop-in that makes Squid read /srv/arx/squid/squid.conf).
Vhosts, pools or Squid rules already on the box are not
imported, and removing arx does not put them back.
2. Install arx
Add the NetForge apt repository, then install. apt pulls in
nginx, certbot, Squid (squid-openssl), nftables, GoAccess, and
the rest of the stack arx manages. (Not BIND9 nor Postfix —
those belong to nomina and missus.) Upgrading a box that has
the plain squid package: use
apt full-upgrade (or apt install arx),
plain apt upgrade holds arx back.
curl -fsSL https://apt.netforge.it/netforge.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/netforge.gpg
echo "deb [signed-by=/usr/share/keyrings/netforge.gpg] https://apt.netforge.it/free $(. /etc/os-release; echo $VERSION_CODENAME) 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:
| Type | What nginx does | Use for |
| PHP | Serves files from htdocs/, hands .php to a per-site PHP-FPM pool | WordPress, Laravel, custom PHP |
| Static | Serves files from htdocs/, returns 404 for anything missing | Hugo/Astro/Jekyll output, static SPAs |
| Reverse proxy | Forwards every request to one upstream — or load-balances across many | missus, 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
arx's firewall is outbound only: one nftables
table, inet arx, rendered to
/srv/arx/nftables/arx.nft. Each site's Unix user
goes through its own chain and reaches only the destinations
you allow on the site's page; with Squid on, its web traffic is
checked by domain. System traffic (root, apt, SSH, nginx) is
never filtered. Loaded at every boot, and reloaded within a
minute if something flushes the ruleset.
The inbound firewall of the box is yours. NetForge
has a ready template for every combination of arx, nomina and
missus (SSH open with a per-IP limit, 80/443, 53, mail ports).
Load it so that it replaces only its own table — never with
flush ruleset, which also removes arx's. The
System rules page shows every table on the box,
read-only.
Turning it off: the panel's Turn off
now button, or nft delete table inet arx,
removes arx's rules. It can't lock you out: arx has no inbound
rules.
9. Backups & migration
Everything arx owns lives under /srv/arx/:
sites' webroots, state.db, certs cache, FPM pool sources,
per-site whitelists. 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.