One typo in an nft rule can lock you out over SSH, so this checks a JSON ruleset before it goes live. It builds a small one from scratch and validates it against the kernel step by step. The example creates a table, an input chain and an SSH accept rule. Allow about 15 minutes, plus time to decide whether the example policy belongs on your host.
This guide uses nft from nftables 1.0.9, package version 1.0.9-1ubuntu0.1, on the reference machine. The installed libnftables-json(5) page describes the JSON schema used by the library and by the nft frontend. The schema is an alternative frontend to ordinary nft syntax, not a separate firewall engine.
JSON parsing and file inspection are ordinary-user operations. Reading or changing the kernel ruleset normally needs root, so use sudo only for those nft commands when your account is not already privileged.
$ nft --version
nftables v1.0.9 (Old Doc Yak #3)
$ command -v jq
/usr/bin/jq
jq is not part of nftables; it is a convenient independent check that the file is valid JSON. If it is unavailable, use another JSON parser or run nft in check mode later.
Warning: do not begin with flush ruleset. A flush removes the current rules, which can cut off remote access and discard policy you never saved. This guide creates a uniquely named table instead.
Save the following as example-filter.json. Replace example_filter only if that name is already used. The outer object has one property, nftables, whose array holds commands in execution order. Each command has one key such as add; its value is the object being added.
{
"nftables": [
{ "add": { "table": { "family": "inet", "name": "example_filter" } } },
{ "add": { "chain": {
"family": "inet",
"table": "example_filter",
"name": "input",
"type": "filter",
"hook": "input",
"prio": 0,
"policy": "accept"
} } },
{ "add": { "rule": {
"family": "inet",
"table": "example_filter",
"chain": "input",
"expr": [
{ "match": {
"op": "==",
"left": { "payload": { "protocol": "tcp", "field": "dport" } },
"right": 22
} },
{ "accept": null }
]
} } }
]
}
inet can cover both IPv4 and IPv6.hook and prio attach it to input processing.expr array holds a mandatory comparison followed by an accept verdict. In JSON, op is required; do not omit it and expect the parser to infer equality.This policy accepts TCP traffic on port 22, but the chain policy is also accept. It is a learning example, not a complete restrictive firewall. Do not copy it as a claim that every other packet gets blocked.
First ask jq to parse the document:
$ jq empty example-filter.json
$ printf '%s\n' "$?"
0
No output from jq empty means it accepted the JSON. A non-zero status means the file is malformed, usually a missing comma, brace or quote. Fix that before investigating nftables at all.
Next ask nft to check the file. -c requests check mode, -j selects JSON output, and -f reads commands from a file:
$ sudo nft -c -j -f example-filter.json
A successful check returns status 0 and should not install the table or rule. On a restricted machine this command can fail before parsing because nft cannot initialise its netlink cache; that is an access or environment failure, not proof the JSON is wrong. Keep the actual error text when diagnosing it.
Checkpoint: inspect both the JSON parser status and nft's status before applying anything. Do not treat an empty output stream as success; the exit status is the result that matters.
Take a JSON snapshot if you have permission to read the ruleset:
$ sudo nft -j list ruleset > ruleset-before.json
$ jq empty ruleset-before.json
The output shares the same outer nftables array. When produced through libnftables, its first object may be metainfo, holding the library version, release name and JSON schema version. Do not copy that metadata blindly into input: the parser rejects a supplied schema version higher than the one it understands, and a lower value is reserved for possible compatibility handling.
If the snapshot is empty or the command fails, stop and resolve the access problem. A missing snapshot is not a reason to flush or overwrite the live configuration.
Applying the file changes kernel firewall state and may affect network traffic. Have console access or a tested recovery route ready before continuing, especially over SSH. Once the check has passed, apply the file with elevated privileges:
$ sudo nft -j -f example-filter.json
Commands in the array run in order. If the table already exists, add reports an error; use create when an existing object should be an error by design. For a rule, insert places it first by default, while add appends it. replace needs a rule handle, and delete normally needs enough family, table and name or handle information to identify the object.
Verify the example table now exists:
$ sudo nft -j list table inet example_filter | jq .
{
"nftables": [
{
"table": {
"family": "inet",
"name": "example_filter"
}
}
]
}
Exact formatting and extra fields can vary. The useful check is that the command succeeds and the returned object names family inet and table example_filter. List the chain or ruleset too if you need to confirm the rule itself.
Do not reach for a broad flush to undo a test. Delete the named table instead, which also removes the chain and rule inside it:
$ sudo nft delete table inet example_filter
$ sudo nft -j list table inet example_filter
table inet example_filter does not exist
The exact diagnostic text can differ by nftables release; the result that matters is a non-zero status because the table is gone. If you need to restore an earlier complete ruleset, review ruleset-before.json first and use it only after confirming it is a trusted snapshot for this host. Restoration can itself change connectivity, so keep an out-of-band recovery path ready.
nftables array, but output may carry handles and metainfo that input never needs.expr, and every rule needs at least one. A verdict such as { "accept": null } is a statement, not a string.ipv4_addr or an array for concatenated types; do not confuse it with the anonymous set expression used inside a rule.xt statement means an iptables-nft compatibility fallback. The manpage warns that nftables will not restore these statements, so manage such rules with the tool that created them.jq empty accepts it, and it has the single top-level nftables array.nft -c -j -f returned success in an environment with the required netlink access.nft -j list.