Introduction
As an aspiring security professional learning about the wonder that is OpenBSD opened my eyes to a new way to secure systems. Security through simplicity. As complexity grows, the first thing you'll notice about modern frameworks is the CVE number1. Jokes aside, in this post I defend my reasoning for admiring simplicity over complexity, reduced attack surface over bloated dependency chains, and why I love OpenBSD.
Why OpenBSD?
You might be asking yourself, why on Earth would I ever need to use OpenBSD when I can use something like Windows Server, or Linux? Don't get me wrong those are great too. Most of my servers run Linux, but if you're anything like me and want your mission-critical software to just work causing as little headaches as possible - OpenBSD might just be that precious gem hiding in a sea of supply-chain attacks, dependency hell (trust me, I know.), and vibecoded slop with 30+ vulnerabilities that will likely never be patched.
Simplicity to the Rescue!
For those who either don't know, or haven't heard of OpenBSD. Here is a brief summary.
OpenBSD is a free, Unix-like operating system started in 1995 by Theo de Raadt. It's built around a simple philosophy; security, correctness, and simplicity above all else. Regular code audits, built-in cryptography, and a relentless focus on doing less but doing it right are what sets it apart from the crowd.2Another fun fact I enjoy about OpenBSD is it is a Canadian project (I, myself am Canadian). Okay, now I will dive into why I love this operating system!
Need a firewall, DHCP server, HTTP server, or even a load balancer? OpenBSD has you covered
Firewalls are stupid simple in OpenBSD with the glorious pf syntax. Look at this lovely ruleset for a small LAN behind a NAT. So simple, and secure. :)
# /etc/pf.conf - minimal router/firewall with NAT for a LAN
# Interfaces and networks
lan_if = "re1"
lan_net = "10.0.0.0/24"
# Non-routable and reserved address space
table { \
0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16, \
172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 198.18.0.0/15, \
198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/3, 192.168.0.0/16 \
}
# Default behaviors
set block-policy drop
set loginterface egress
set skip on lo
# Normalize traffic
match in all scrub (no-df random-id max-mss 1440)
# NAT for LAN to the current egress address
match out on egress inet from $lan_net to any nat-to (egress:0)
# Anti-spoofing
antispoof quick for { egress, $lan_if }
block in quick on egress from to any
block return out quick on egress from any to
# Default deny
block all
# Allow established and related traffic
pass out on egress inet keep state
# Allow LAN to anywhere
pass in on $lan_if inet from $lan_net to any keep state
To reload pf configuration we use:
pfctl -f /etc/pf.conf
DHCP servers are even easier to spin up.
# /etc/dhcpd.conf
subnet 10.0.0.0 netmask 255.255.255.0 {
option routers 10.0.0.1;
option domain-name-servers 10.0.0.1;
range 10.0.0.51 10.0.0.250;
}
To enable dhcpd service with our new configuration we use the following commands as root:
# rcctl enable dhcpd
# rcctl start dhcpd
Wanna run your own self-hosted, recursive, caching DNS resolver? Of course, OpenBSD has that too. Baked into the default install is unbound. No external packages, or dependencies required.
# /var/unbound/etc/unbound.conf
server:
# If no logfile is specified, syslog is used
# logfile: "/var/log/unbound/unbound.log"
verbosity: 0
interface: 127.0.0.1
interface: 10.0.0.1
interface: ::1
port: 53
do-ip4: yes
do-udp: yes
do-tcp: yes
access-control: 10.0.0.0/24 allow
access-control: 127.0.0.0/8 allow
access-control: ::1 allow
access-control: 0.0.0.0/0 refuse
access-control: ::0/0 refuse
# May be set to no if you don't have IPv6 connectivity
do-ip6: yes
# You want to leave this to no unless you have *native* IPv6. With 6to4 and
# Terredo tunnels your web browser should favor IPv4 for the same reasons
prefer-ip6: no
# Use this only when you downloaded the list of primary root servers!
# If you use the default dns-root-data package, unbound will find it automatically
#root-hints: "/var/lib/unbound/root.hints"
# Trust glue only if it is within the server's authority
harden-glue: yes
# Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
harden-dnssec-stripped: yes
# Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes
# see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details
use-caps-for-id: no
# Reduce EDNS reassembly buffer size.
edns-buffer-size: 1232
# Perform prefetching of close to expired message cache entries
# This only applies to domains that have been frequently queried
prefetch: yes
# One thread should be sufficient, can be increased on beefy machines. In reality for most users running on small networks or on a single machine, it should be unnecessary to seek performance enhancement by increasing num-threads above 1.
num-threads: 1
# Ensure kernel buffer is large enough to not lose messages in traffic spikes
so-rcvbuf: 1m
# Ensure privacy of local IP ranges
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: 172.16.0.0/12
private-address: 10.0.0.0/8
private-address: fd00::/8
private-address: fe80::/10
# Ensure no reverse queries to non-public IP ranges (RFC6303 4.2)
private-address: 192.0.2.0/24
private-address: 198.51.100.0/24
private-address: 203.0.113.0/24
private-address: 255.255.255.255/32
private-address: 2001:db8::/32
To enable our new DNS resolver we use the following command, similar to the one used for our DHCP server. See man rcctl for more information not covered in this post.
OpenBSD also teaches you how to effectively read and use documentation.
# rcctl enable unbound
# rcctl start unbound
Wrapping Up
These are just some of the reasons I enjoy using OpenBSD. It really is a versatile operating system, and the peace of mind knowing it is secure by default is just the cherry on top. OpenBSD isn't for everyone, but then again, neither is reading the manual. If you've made it this far, you might just be one of us.