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:
| 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 an upstream URL | missus, 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.