Connect once before you change a rule and note whether the client hangs or is refused immediately. That single difference tells you which decision dropped the packet and saves an hour of reading policy files. The rest of the walk is short: the packet crosses a hook, matches a rule, and either reaches a listener or it does not. An allowlist is bookkeeping around those three facts.

Follow one SSH attempt to the input hook

A client sends a SYN to the host's address on port 22, and the kernel decides whether that packet is for a local service. If it is, the input path runs and the rule set either accepts the packet or discards it. Silence means the SYN died before the listener ever saw it, while a fast refusal means something answered with a reset. Those are different verdicts from different rules, and both are legitimate outcomes.

Confirm the listener next, because no rule can rescue a service that is not there. Run ss -ltnp and check that something is bound to the port and address family you expect, then read ssh -v on the client to see where it stops.

Forwarding is a separate decision

On a VPN server the more interesting hook is forward, because client traffic is routed through the machine rather than delivered to it. A host that only runs its own VPN client needs a far smaller rule set than one relaying other people's packets. Mixing the two designs on a single box produces rules that look correct and pass nothing.

Write the inner policy as its own question: which client addresses may reach which destinations through the tunnel. Keep the administrative path, such as SSH, on the input path with its own source restriction. A permissive forward rule added during testing is the classic defect that outlives the test, so review the whole ruleset after a change instead of the one line you edited. Opening the tunnel listener is not permission for every connected client to administer the host.

Drop and reject are actions, not policies

A base chain policy accepts or drops, and that is the entire list of choices, as the nftables reference states. Rejection exists as a rule action with a chosen response, which is why writing it as a policy fails on both nftables and iptables. In practice a drop gives the client no information while a reject hands it an error it can act on.

table inet admin {
    chain input {
        type filter hook input priority filter; policy drop;
        ct state established,related accept
        ip saddr 203.0.113.0/28 tcp dport 22 accept
        udp dport 51820 accept
        icmp type { destination-unreachable, time-exceeded } accept
    }
}

Read that sketch as the shape of a decision rather than a file to paste, since the address ranges are examples. An explicit reject on the SSH port makes an unauthorized client fail in a second instead of waiting out a timeout, which shortens your own debugging. Silence discourages casual scanning and tells an authorized user nothing at all. Pick per service and write down why, because the next person will otherwise change it back.

Keep the control traffic you actually need

Blocking all ICMP is a popular default that breaks path MTU discovery, and the symptom is a connection that establishes and then stalls on larger packets. Neighbour discovery on IPv6 depends on its own ICMPv6 types, and dropping those breaks the link before any TCP rule is consulted. Both belong in the allowlist as named dependencies rather than as blanket permissions. A policy that silently breaks discovery is worse than one that admits a little diagnostic traffic.

Allow the established and related state for connections you initiated, and let the stateful engine handle the replies. Then confirm the reply path is not blocked further out, because an outbound-only restriction upstream produces the same timeout you were about to blame on the host.

Prove recovery before you retire the old path

Keep a console session open, apply the change, and open a brand new management connection from a different terminal. An existing session proves nothing, because it may survive a rule that blocks every new login, which makes it the most convincing false positive in firewall work. If a monitoring system checks the host, add its source address before the change rather than after the first alert. Then test one large packet and one small one to prove discovery still works.

For a VPN service, repeat from a client that has never connected, since a resumed session can reuse state that a fresh one must negotiate. Record the exact rule set that passed, with a checksum or a copy, so a later edit can be compared rather than guessed at. Coordinate with whoever owns the rule order, because an earlier rule silently wins over the one you added.

Artaigilean co-cheangailte