A web UI for nftables that manages the firewall as its real object model — tables → chains → rules, in any family.
Every option is a typed control that explains what it does, so you can build anything nftables can express without memorising its syntax. Changes are applied as one atomic transaction with an armed auto-revert, so a bad rule can never lock you out of the box you're editing.
It's a single Go binary backed by SQLite. No agent, no cloud, no external
dependencies at runtime beyond nft itself. Install the package, run
nftably init, and go.
Warning
nftably is beta software. Expect bugs. It is a personal project released in the hope it is useful to someone else. Nothing here has been through the kind of testing a piece of firewall infrastructure deserves.
Caution
nftably owns the tables it manages, and replaces their entire contents on every apply. For
each table it manages it renders the whole table from its own database and swaps it in atomically —
any hand-written rule in that table it does not know about is gone after the next apply. Tables it
does not own are never named and never touched, so import an existing table into nftably before you
let it manage that table, or leave it alone. And because the host you are editing is usually reached
through the firewall you are changing, every apply is dry-run through nft --check, review the
diff before you confirm, and keep the armed auto-revert on — it is what stops a bad rule from
locking you out. Run it behind the access list or an SSH tunnel.
The firewall is tables → chains (shown as tabs) → rules, each rendered as the real nft line it becomes:
| The typed, explained rule editor | Best-practice presets |
|---|---|
![]() |
![]() |
| Review the diff, apply with auto-revert | A live overview |
![]() |
![]() |
Jump to any page or action from anywhere with the command palette (Ctrl-K / ⌘K):
All addresses shown are documentation examples (RFC 5737 / RFC 3849).
Managing a firewall by hand-editing /etc/nftables.conf over SSH is error-prone,
and the scariest part is that the mistake which locks you out is the same command
that applies the fix. nftably makes firewall changes on a remote host safe to
make and easy to get right:
- The real model, not a curated subset. You manage tables, chains (base chains
hook into the traffic path; regular chains are jump/goto targets) and rules. A
rule is a set of match conditions and action statements — and every knob nftables
offers is a first-class, explained control. The catalogue reaches into the powerful
corners too: verdict maps (
tcp dport vmap { 22 : accept, … }— one O(1) lookup instead of a stack of rules), named counters you can share across rules for one running total, and flowtable fast-path offload for a router. For the rare construct the catalogue can't yet express, a guarded raw rule lets you type a verbatim nft line (validated so it can't break out of its chain), and freeform tags organise and filter rules. - Explained as you build. Pick a condition and the editor tells you, in plain words, what it matches and gives an example; pick an action and only its relevant fields appear. Interfaces come from the box's real list, sibling chains and the flowtables in the table are offered as you type, and you point a rule at a named set instead of retyping addresses.
- Built for fast editing. Drag rules, chains and tables to reorder them; multi-select rules to enable, disable, move or delete them in one go; duplicate a rule — or move it — into any chain of the table; and jump to any page or action from anywhere with the Ctrl-K / ⌘K command palette. With no JavaScript, up/down buttons stand in for dragging, so reordering still works.
- One model for v4 and v6. netfilter's
inetfamily carries both in a single table, so a rule written once covers both protocols. - Lockout safety. Every apply is an atomic
nft -ftransaction — validated bynft --checkfirst — with an armed auto-revert: if you don't confirm within the window, the previous ruleset is restored, even if your SSH session drops, and even if nftably itself is restarted mid-window (the revert snapshot is persisted). A lint pass warns before an apply that would leave no way back in. - Know before you apply. A built-in packet simulator: describe a packet — protocol, source, port, interface, connection state — and get a step-by-step trace of exactly which rule decides it, ending in accept, drop or reject. It evaluates your model the way netfilter would, touching nothing, so you can answer "will my SSH still get in?" before you commit.
- Named sets, static or living. A named set is a group of IPs/ranges. Point
rules at one (
ip saddr @office) and edit the set later — every rule that references it follows. Fill a set by hand, or have it built from a country (GeoIP), a remote feed (a threat-intel blocklist), or a DNS hostname (resolved to its live A/AAAA addresses and kept tracking the name) and refreshed on a schedule — so you candropan entire country, subscribe to a blocklist, or allow a host that roams, and let it maintain itself. Referenced sets render into the tables that use them. On the Connections view, one click blocks an entire country — it builds the GeoIP set and the early drop rules for you. - Presets to start from. One-click, best-practice starting points that scaffold the tables, chains, rules and editable named sets for you, each explaining what it adds and why: a hardened BGP edge router, a basic secure server, a WireGuard VPN server, a home router / gateway (NAT + masquerade, port-forward-ready), a web server, a database server (scoped to your app tier), and a Docker / container host (hardens the host without touching container networking).
- See, then act. The Connections page shows every flow conntrack knows about — to, from and through the box, with countries when you point nftably at a GeoIP database. The live ruleset viewer shows exactly what the kernel is running; a rule with a Count action shows its live packet/byte total right on the Firewall page; and a rule with a Log action feeds the built-in firewall log viewer — build a rule, apply it, and watch it catch traffic, in numbers and in detail.
- One Posture page that assesses and hardens. It grades your model against what a solid host firewall needs — default-deny, the survivable base, IPv6's ICMP, anti-spoofing, scoped SSH — explaining why each matters; and, on the same page, it scans what's actually listening on the box and runs each service through the simulator against your model, telling you what your firewall really does about it — "PostgreSQL is reachable from the internet" or "sshd is listening but a connection from outside would be dropped". Both halves offer safe one-click fixes that land on the Changes page behind the auto-revert.
- Brute-force auto-ban, in the kernel. One click on the Posture page installs a fail2ban-style guard for SSH — no daemon, no log parsing. A source that opens connections faster than the allowed rate is added to a dynamic timeout set and dropped for the ban window (the set clears itself as bans expire). The same page has a generic form to protect any service — name it, pick tcp/udp and the port(s), set the rate — and it's built from a first-class Rate-ban the source action you can also drop onto any rule by hand.
- Expose an inside service, safely. A port-forward wizard turns "expose external tcp/443 to 192.168.1.10:8443" into the DNAT rule (creating a nat table and prerouting chain if needed) plus the matching forward-accept — model-only, so it lands on Changes for review first.
- A block API for your own tooling. An opt-in, token-gated HTTP API
(
/api/block,/api/unblock,/api/blocked) lets a script or SIEM feed addresses into a blocklist set — wire up your own detection and let nftably do the dropping. - Learn while you harden. A Learn section teaches nftables in plain language: a Concepts page (the packet's journey through the hooks, chains, connection tracking, sets), plus task-oriented lessons — NAT & port-forwarding, a recipe cookbook, Troubleshooting ("why isn't my rule matching?"), and Coming from iptables — each tying the idea to where you act on it. Someone new can go from "what's a chain?" to a hardened box.
- Send traffic to an IDS/IPS. One click sends forwarded traffic to an NFQUEUE
for Suricata or Snort to inspect inline — fail-open, so a stopped inspector
never blackholes transit, and only the forward chain is touched. Built on the
general Send to userspace program (
queue) action. - Graph it in Grafana. An opt-in Prometheus
/metricsendpoint turns every rule with a Count action into a time series (nftably_rule_packets_total/_bytes_total) — watch drops and accepts move — plus table/chain/rule counts and annftably_uphealth gauge. Off by default; enabling it under Settings mints a bearer token the scraper must present. - Alerts when it matters. Get a notification — to a webhook, Slack, Discord, email, Telegram, ntfy or Gotify — when an armed apply auto-reverts (you may have been cut off), a source is auto-banned, a blocklist feed fails to refresh, nft goes unreachable, a new exposure appears, someone burns through failed logins, or the live ruleset drifts from what nftably applied. Configure destinations under Settings → Alerts and filter each to the events you care about.
- Notices when the kernel changes underneath you. nftably fingerprints the tables
it owns and, if someone edits them with
nftdirectly or loads a hand-written config, detects the drift and can alert — so "the firewall no longer matches what I applied" is something you find out, not something that bites you later. - Back it up, move it around. Export your whole configuration — tables, chains, rules, named sets and flowtables — as one portable JSON file (the model, not the database: no credentials), and restore it on any box. Turn on scheduled automatic backups to keep a rolling set of daily snapshots on disk, and roll back to any past config version from its saved snapshot. Every restore is model-only, so it lands on the Changes page for review behind the auto-revert.
Running a BGP router (BIRD/FRR)? The preset builds a control-plane hardening baseline
that nft accepts as-is:
- a policy-drop input chain with loopback, connection-invalid and established/related handling, plus the ICMP/ICMPv6 a router must answer (block the IPv6 neighbour-discovery / PMTU messages and IPv6 stops working);
- SSH and the nftably UI accepted only from
@mgmt— seeded with the address you're connecting from, so applying it can't lock you out; - BGP (TCP 179) and BFD (UDP 3784/3785/4784) accepted only from
@peers— everything else to the box is dropped; - denied inbound tallied into a named
deniedcounter and rate-limited-logged, so scans are both counted and visible without flooding the log; - a forward chain that routes transit but drops invalid.
You then fill in two sets — @peers (your peers, v4 and v6) and @mgmt (widen to your
management network) — then go to Changes to apply.
Kick the tyres without touching your host firewall. This runs nftably in its own network namespace, where it gets a private, fully writable nftables to manage — detect nft, apply real rules, watch live counters — completely isolated from your machine.
docker compose -f docker-compose.demo.yml up --buildThen open http://127.0.0.1:8099 and log in with admin / nftably-demo.
Apply a preset, watch the live ruleset and per-rule counters, enable the
Prometheus /metrics endpoint under Settings. down then up for a clean slate.
(To manage a real host's firewall instead, use docker-compose.yml, which shares
the host network namespace — see the comments in that file.)
sudo apt install ./nftably_*_amd64.deb # pulls in nftables
sudo nftably init # create the admin account
sudo nftably doctor # check nft access + database
sudo systemctl enable --now nftablyThen browse to http://<host>:8099.
go build -o nftably ./cmd/nftably
sudo ./nftably init --db /var/lib/nftably/nftably.db
sudo ./nftably server --db /var/lib/nftably/nftably.dbnftably reads and writes netfilter through nft, which needs CAP_NET_ADMIN — in
practice run it as root, or (as the packaged systemd unit does) as a dedicated account
granted only that one capability.
nftably init create the database and admin account
nftably doctor preflight: nft installed & usable, iptables coexistence, db writable
nftably detect print the detected backend and a ruleset summary
nftably server run the web UI
nftably version print the version
nftably binds every interface by default and serves plain HTTP unless you give it a certificate. On a fresh install its access list is empty (allow-all), and the UI warns you about this until you narrow it. Ways to close it down:
- Access list — Settings → Access control. One IP/CIDR per line. Loopback is always allowed, so an SSH tunnel can never lock you out.
- Native TLS —
--tls-cert cert.pem --tls-key key.pem(TLS 1.2 minimum). - Loopback + SSH tunnel — start with
--listen 127.0.0.1:8099and reach it overssh -L 8099:127.0.0.1:8099 host.
The blocked-client path closes the TCP connection outright, so a scanner can't even tell there's a service on the port. Every response carries hardening headers (a strict CSP with no inline scripts or styles, no framing, no cross-origin reads), cross-origin POSTs are rejected server-side, session tokens are stored only as hashes, failed logins are rate-limited per IP, and operator actions are recorded on the event timeline.
Found a vulnerability? See SECURITY.md.
cmd/nftably/ CLI: init · doctor · detect · server
internal/nft/ shell out to nft (-j JSON for structure, -a text for wording,
-c for dry-run validation); backend detection; iptables preview
internal/store/ SQLite: settings, users, sessions, events; the object model
(tables, chains, rules with match/statement rows, flowtables),
named sets, config versions + snapshots, the persisted pending apply
internal/nftcat/ the knob catalogue: every match and statement as an explained,
typed spec — the single source both the editor and renderer use
internal/render/ model → nft config text (generic over tables/chains/rules/sets);
multi-table apply/revert transactions; lockout lint; unified diff
internal/simulate/ trace a packet through the model (accept/drop/reject) — powers
the simulator page and the advisor's verdicts; pure Go, no kernel
internal/advisor/ scan the box's listeners, run each through the simulator, and
report what the firewall actually does about each exposure
internal/conntrack/ read the kernel's live connection table
internal/klog/ read netfilter LOG lines from the kernel ring buffer (dmesg)
internal/notify/ alert destinations: webhook, Slack, Discord, email, Telegram,
ntfy, Gotify — fan out an event to the channels you configured
internal/web/ server-rendered UI (html/template), auth, access control, presets,
drift detection, the token-gated block API, scheduled backups
The live ruleset is always read fresh from nft — never cached in the database.
The database holds nftably's own state and the model. nftably only ever touches the
tables it owns (recorded in its model); tables it did not create are never modified.
Applying replaces exactly the owned tables in one transaction, and removes tables you
delete from the model. GeoIP lookups (optional, Settings → GeoIP) run against a local
.mmdb file — point at your own MaxMind database, or let nftably fetch the free DB-IP
Lite one (CC-BY 4.0, no account). nftably reaches the network in exactly two places,
both opt-in and operator-triggered: that GeoIP download, and fetching a feed-sourced
named set from a URL you configure. Feed fetches are restricted to public addresses
(the dialer refuses loopback/private/link-local targets, after DNS and across redirects),
so a feed URL can't be turned into a request against the box's own internal services.
go build ./...
go test ./...
go vet ./...
GOOS=linux GOARCH=amd64 go build -o nftably-linux-amd64 ./cmd/nftably # cross-compilenftably compiles and its web UI runs on any OS (handy for development); the firewall-reading and -writing paths simply report "nft not installed" off Linux.
BSD Zero Clause — public-domain-equivalent. Do whatever you like with it; you owe no attribution and get no warranty.
The bundled webfonts are IBM Plex, copyright IBM Corp., used under the
SIL Open Font License 1.1 — see internal/web/static/fonts/LICENSE.txt.
That license covers the fonts only, not nftably.





