How can i do this in a single line?

tcp dport 53 counter accept comment "accept DNS"
udp dport 53 counter accept comment "accept DNS"

2 Answers 2


With a recent enough nftables, you can just write:

meta l4proto {tcp, udp} th dport 53 counter accept comment "accept DNS"

Actually, you can do even better:

set okports {
  type inet_proto . inet_service
  elements = {
    tcp . 22,  # SSH
    tcp . 53,  # DNS (TCP)
    udp . 53   # DNS (UDP)

And then:

meta l4proto . th dport @okports accept

You can also write domain instead of 53 if you prefer using port/service names (from /etc/services).

  • 1
    Hello. What does "th" mean in this context?
    – Lethargos
    Commented Jul 27, 2022 at 17:20
  • 5
    @Lethargos th means "Transport Header". th dport is a "human readable" shortcut for @th,16,16 which means: read 16 bits at offset 16 in the transport header. This is where the destination port is set when either TCP or UDP is used. See this documentation for further details.
    – Totor
    Commented Jul 28, 2022 at 12:37
  • So it works for both tcp and udp simply because the dport starts for both at 16th bit?
    – Lethargos
    Commented Jul 29, 2022 at 19:21
  • 1
    @Lethargos exactly! :) That's why you shouldn't use it with other protocols (as the documentation warns), hence the check to be sure that the ̀l4proto` is either TCP or UDP but not something else.
    – Totor
    Commented Aug 1, 2022 at 12:40

For the sake of telling it's possible (but probably not that useful), yes it's possible, using a recent enough nftables and a raw payload expression.

So for the inet (dual ip/ip6) table, you have to first filter the right level 4 protocol (here TCP=6 and UDP=17) using a set, then filter the port 53. That's handy, TCP and UDP have the same location for the destination port in their respective format. dport is expressed as the offset of the destination port in the TCP/UDP part of the packet: 16 bits, with a size of 16 bits as seen in the previous links. While tcp and udp can be used by their symbolic name, It appears that dns must be stated as 53 not dns, I can only imagine that's because dns/tcp and dns/udp (or domain see later) are in two different "protocol namespaces".

The resulting command is (additional single quotes or else escaping the double quotes is needed here):

# nft 'add rule inet filter input meta l4proto {tcp, udp} @th,16,16 53 counter accept comment "accept DNS"'

If you want it for IPv4 only, initialize the corresponding ip table and chains and replace inet with ip.

Please also note that almost the same is given as example in the 0.8.3 release notes and is now included in nft's man page, alas, that example doesn't work: dns and http have to be replaced with 53 and 80 (and anyway some distributions/versions might have required domain instead of dns).

  • Is there any performance difference between the two original simple rules compared to this single complex rule?
    – scai
    Commented Feb 24, 2020 at 10:04
  • 1
    @scai probably not (ok it should be a little better). It's all compiled into bytecode in the end. Try nft --debug=netlink -a list ruleset
    – A.B
    Commented Feb 24, 2020 at 11:15
  • Why do you think such a rule is not that useful? I think it's more elegant to place both tcp and udp in one rule then having a rule for each - such as in the case with dns where you actually do need to use both, and nowadays http with quic.
    – Lethargos
    Commented Aug 1, 2022 at 13:02
  • Btw, since a few months or years, th got a bit more official in this case. The command I wrote in 2018 would automatically have @th,16,16 53 decoded back as th dport 53 which is now a valid syntax. I'll have to revisit the answer.
    – A.B
    Commented Aug 1, 2022 at 13:12
  • 1
    @Lethargos And it's certainly useful, but when I wrote the answer I felt it wasn't worth the trouble of needing a raw payload expression (which was brand new then).
    – A.B
    Commented Aug 1, 2022 at 13:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .