Custom Rules
nftables statements, written by hand, appended to easywall’s input chain. This is the escape hatch for anything the typed pages cannot say: a port open to one source only, a rate limit on a specific service, a log line you want.
nft that will apply it.The shape of the file
One statement per line. # comments and the blank lines between groups are kept
exactly as you wrote them — they are part of the file, not stripped on save.
# allow Prometheus node-exporter only from the monitoring host
ip saddr 192.0.2.50 tcp dport 9100 accept
# rate-limit inbound DNS queries to mitigate amplification
udp dport 53 limit rate 50/second accept
# log and drop traffic to a legacy admin port
tcp dport 10000 log prefix "legacy-admin: " drop
Each line is written into the input chain as add rule inet easywall input <your
line>, so write the statement only — no add rule, no table, no chain.
No newlines, no semicolons
nft reads both as the end of one command and the start of the next. A statement
carrying either is therefore not a rule but a second command, run by the root
daemon, able to reach tables easywall does not own. Both are refused — on save,
on import, and again inside the core.
Demonstrated, not theoretical. An imported rule containing a newline wrote into a neighbouring table, which is precisely what “easywall owns one table and never looks at another” says cannot happen. The check is on the shape of the input rather than on nft’s grammar, so it does not depend on a subprocess being available or on a wrapper happening to be unbalanced. The reasoning is in Security.
There is also a ceiling of 256 statements per check, which is far above any real rule set and far below what a form body can carry.
Where they are evaluated
Last — after the ports, before the chain policy drops what is left.
Two consequences worth knowing:
- A packet an earlier rule already accepted never reaches your statement. To
restrict something the port rules opened, remove the port rule — a custom
dropafter it will not be consulted. - A packet your statement accepts is one the policy would otherwise have dropped, which is the usual reason to write one.
Checked as you type
The editor sends the text to the core, which runs it past nft --check in a
throwaway table and reports the line number of anything it rejects. A valid set
costs one nft run; only a rejected one is re-checked line by line, to say which
line it was.
| State | What it means |
|---|---|
| No errors | nft parses every statement. It does not mean the rule does what you meant |
| A line number | That statement is not valid nftables syntax; the message is nft’s own |
| “Validation unavailable” | The core is not reachable, so nothing was checked — not “everything is fine” |
In demo mode there is no nft
behind the interface, so it reports the checker as unavailable rather than
claiming your rules are valid.
Nothing happens until you apply
Saving stages, like every other page. Worth knowing here: a statement that passes the syntax check can still be refused by the kernel at apply time — a chain that does not exist, a set that is not defined — and a failed apply rolls back to the previous rules.
When it does not work
| Symptom | Cause |
|---|---|
| “contains a newline, which nft reads as the end of the command” | Put each statement on its own line; it is not a formatting preference |
| Valid syntax, no effect | An earlier rule accepted the packet first — see Where they are evaluated |
| The apply failed and rolled back | The kernel refused the statement. journalctl -u easywall-core has nft’s message |
| Comments disappeared | They no longer do. Saving keeps # lines and blank lines |
| The syntax check says nothing at all | The core is unreachable, or you are in demo mode |