IRCNow

On a workstation where you are the only user, you can use a very simple /etc/pf.conf:

set skip on lo0 # don't filter localhost packets
ext_if = "em0" # replace em0 with your external interface

set block-policy drop # by default, drop packets. You can also set block-policy reject
set loginterface $ext_if # log that interface

block all  # block all traffic by default
pass in inet proto icmp icmp-type 8 code 0 # icmp packets
pass in inet proto icmp icmp-type 3 code 4 # icmp needfrag (MTU)
pass in inet6 proto ipv6-icmp icmp6-type {2 128} keep state
pass out all # pass all outgoing traffic

This will allow the necessary ICMP traffic (useful for network diagnosis) while blocking all other incoming connections.

(As a general rule, the last matching rule determines the action.)

I generally don't whitelist by IP addresses because I've had times where I needed to access a system from a different IP. I also avoid OS fingerprinting because, although it is available, it's not 100% accurate.

To load the ruleset once you've edited it, run:

$ doas pfctl -f /etc/pf.conf

To disable the firewall (useful for diagnosing the network), run:

$ doas pfctl -d

To enable it again:

$ doas pfctl -e

For a server, you will want to, at a minimum, allow incoming ssh packets:

set skip on lo0 # don't filter localhost packets
ext_if = "em0" # my external interface is em0

set block-policy drop # by default, drop packets. You can also set block-policy reject
set loginterface $ext_if # log that interface

pass in proto tcp from 192.168.1.1 to port ssh
pass in inet proto icmp icmp-type 8 code 0 # icmp packets
pass in inet proto icmp icmp-type 3 code 4 # icmp needfrag (MTU)
pass in inet6 proto ipv6-icmp icmp6-type {2 128} keep state
pass out all # pass all outgoing traffic

Replace 192.168.1.1 with your IP.

As a general rule, your servers should also accept incoming http and https connections. This is necessary for running a web server and also for acquiring a properly signed SSL certificate. Here is the /etc/pf.conf:

set skip on lo0 # don't filter localhost packets
ext_if = "em0" # my external interface is em0

set block-policy drop # by default, drop packets. You can also set block-policy reject
set loginterface $ext_if # log that interface

pass in proto tcp from 192.168.1.1 to port ssh
pass in inet proto icmp icmp-type 8 code 0 # icmp packets
pass in inet proto icmp icmp-type 3 code 4 # icmp needfrag (MTU)
pass in inet6 proto ipv6-icmp icmp6-type {2 128} keep state
pass in proto tcp to port {http https}
pass out all # pass all outgoing traffic