On OpenBSD, I can successfully & transparently forward ports 80 and 443 to services running on custom, unprivileged ports using the following /etc/pf.conf:

tcp_pass = "{ 22 80 123 443 }"

block all
pass out log on egress proto tcp to any port $tcp_pass keep state

pass in log on egress proto tcp from any to any port 80 rdr-to port 3080 keep state
pass in log on egress proto tcp from any to any port 443 rdr-to port 3443 keep state

I tested this by loading up a static file server bound to 3080, then doing a cURL call to the server from another machine like so:

curl -v 192.168.1.xxx

... and I got back a 200 status code and the HTML content I'd expect. 🎉

Now I'd like to do the same with DNS. First I updated the tcp_pass macro to include 53 and created a second macro for udp_pass and put 53 in that too, followed by a udp rule.

Then I tried adding the following rules (see comment in code block below):

tcp_pass = "{ 22 53 80 123 443 }"
udp_pass = "{ 53 }"

# ...

pass out log on egress proto udp to any port $udp_pass keep state

# ...

# the new rules I added -- emulating the http(s) rules from before
pass in log on egress proto tcp from any to any port 53 rdr-to port 5353 keep state
pass in log on egress proto udp from any to any port 53 rdr-to port 5353 keep state

I started up a DNS server on port 5353 and tried making a request from an external machine to this one:

  • dig @192.168.1.xxx -p 5353 cnn.com works as expected: returns instantly with the correct response

  • dig @192.168.1.xxx cnn.com hangs, then times out with the following error

    ; <<>> DiG 9.10.6 <<>> @192.168.1.xxx cnn.com
    ; (1 server found)
    ;; global options: +cmd
    ;; connection timed out; no servers could be reached

I follow the pf logs with tcpdump -nettti pflog0 when I make the DNS queries and I see only the following entries when requesting on port 53:

Jun 09 17:15:22.513529 rule 10/(match) pass in on iwm0: 192.168.1.yyy.58201 > 192.168.1.xxx.53: 64594+ [1au] A? cnn.com.(36)
Jun 09 17:15:22.513933 rule 6/(match) pass out on iwm0: 192.168.1.xxx.47191 > 64594+ [1au] A? cnn.com.(36)

If I rebind the DNS server to port 53 directly using root, resolution works, so I know the problem is with my PF configuration and not my network.

My goal is to run my DNS server on an unprivileged port and port forward 53 to that port. I'm not sure what to try next and would appreciate any insight.

  • 2
    tcpdump, tcpdump, tcpdump…
    – poige
    Commented Jun 10, 2023 at 3:46
  • I don't know much about BSDs, but in Linux redirecting to 127/8 IPs doesn't work by default as they are not routable. One has to make them so by setting the sysctl route_localnet to 1. Unless you have reasons to bind to only, you may want to try not to and DNAT only the port number (but keeping the destination address untouched).
    – Tom Yan
    Commented Jun 10, 2023 at 11:09
  • @TomYan I don't want to discourage you from contributing to this site, but do please try to leave comments on topics that you know about.
    – Paul
    Commented Jun 10, 2023 at 13:17
  • @Paul To be frank, I'm not seeing how my comment "hurt". If you know OpenBSD well, you can reassure both the OP and me that it does not do something similar. Otherwise my comment could at least give the OP a potential direction to finding the cause. (The reason that route_localnet exists isn't quite "Linux-specific" anyway.)
    – Tom Yan
    Commented Jun 10, 2023 at 13:33
  • @TomYan Why would you expect an OpenBSD user to research how to configure Linux just to understand what it is you are trying to advise them of?
    – Paul
    Commented Jun 10, 2023 at 13:59

1 Answer 1


I think I solved it!

The crux of my problem—as I understand it—is that my initial rule worked for 53 but I had no rules allowing traffic in & out of 5353, so I was effectively redirecting to a dead-end. I realized my original question did mention my locally-running nameserver, but neglected to mention it is resolved/queried/whatever by way of another daemon (Tailscale)... and traffic out of that daemon was getting blocked. 🤦‍♂️

Here's my up-to-date pf.conf (note that I switched to using service names instead of numerical ports, but the two are interchangeable AFAIK):

tcp_pass = "{ ssh domain www ntp https }"
udp_pass = "{ domain }"

# block everything
block all

# allow outgoing $tcp_pass & $udp_pass
pass out proto tcp to port $tcp_pass keep state
pass out proto udp to port $udp_pass keep state

# allow ping
pass in inet proto icmp all icmp-type 8 code 0

# allow ssh
pass in proto tcp to port ssh keep state

# allow local traffic
pass in on lo proto tcp from lo to lo keep state
pass out on lo proto tcp from lo to lo keep state

# redirect www and https to custom ports
pass in proto tcp to port www rdr-to port 3080 keep state
pass in proto tcp to port https rdr-to port 3443 keep state

# redirect domain to custom port
pass in proto { tcp, udp } to port domain rdr-to port 5353 keep state
pass in proto udp from port 5353 # <-- the piece (1) I was missing!
pass out proto udp from port 5353 # <-- the piece (2) I was missing!

This configuration appears to work as I desire, allowing me to serve www, https, and domain on custom, unprivileged ports transparently. 🎉

Other notes:

  • For a time I was changing /etc/pf.conf and applying my updates with pfctl -f /etc/pf.conf and not seeing the changes I had expected, only to remember hours later that I had previously disabled pf using pfctl -d! I re-enabled pf with pfctl -e and saw results matching my expectations, after which I stepped outside, touched some grass, drank a 🍺, and watched some ☁️s.
  • When debugging with tcpdump, I was overly focused on 53 and didn't consider my issue could've been related to 5353. In the future, I'll remember to check all the ports in play.
  • @Tom Yan I haven't had any issues routing to 127/8 ips; the above solution required no extra system configuration changes on OpenBSD 7.3.
  • @poige I tried repeating "tcpdump, tcpdump, tcpdump" out loud, thinking it might summon Puffy or Theo de Raadt like Beetlejuice, but alas, nothing happened. After that disappointment, I continued to read pf.conf(5), tcpdump(8), Chapters 21 & 22 of Absolute OpenBSD, and the pf guide on openbsd.org. My epiphany arrived after a rule change blocked a request on localhost to the admin api for my http server on a custom port, and I thought to check if pf was blocking 5353 too.
  • It takes me time to read through a pf.conf and figure out what is going on, so great that you solved your problem. There is no default limitation in pf for routing packets to 127/8. Note that there is a system limitation if you want pf to route packets between interfaces (set sysctl net.inet.ip.forwarding=1 - well covered in The book of PF, an indespensible book for all OpenBSD users).
    – Paul
    Commented Jun 11, 2023 at 12:41

You must log in to answer this question.

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