OpenBSD: System Configuration After Installation

OpenBSD: System Configuration After Installation

OpenBSD systems are configured through text files. hostname.if controls network interfaces, ntpd synchronizes the system clock, doas provides delegated root privileges, and sysctl adjusts kernel parameters.

OpenBSD configures systems through text files. Every network interface has its own configuration file in /etc/hostname.INTERFACE. Time synchronization is handled by ntpd with built-in time zone support. Privilege delegation is provided by doas—OpenBSD’s system for delegated root access. Kernel parameters can be adjusted using sysctl.

This article explains network configuration using hostname.if files—for changes made after the installation as well as more advanced setups for servers, desktop systems, and specialized requirements. The filesystem layout consistently follows OpenBSD conventions.

Network Configuration with hostname.if

Each network interface is controlled by its own file. The filename follows the pattern /etc/hostname.INTERFACE—for example, /etc/hostname.em0 for Intel network adapters or /etc/hostname.vio0 for virtual machines.

The installer creates these files automatically based on the values entered during installation. Manual changes are made with a text editor:

$ doas vi /etc/hostname.vio0

Automatic Configuration with DHCP

The simplest configuration uses DHCP for automatic address assignment:

/etc/hostname.vio0:
inet autoconf
inet6 autoconf

The first line starts dhcpleased for IPv4 DHCP, while the second starts slaacd for IPv6 SLAAC. Both update addresses automatically. DNS servers are written to /etc/resolv.conf.

Controlling IPv6 Behaviour

IPv6 Stateless Address Autoconfiguration (SLAAC) is enabled on all interfaces by default. OpenBSD automatically creates link-local addresses (fe80::) and obtains global IPv6 addresses through Router Advertisements.

Disable IPv6 completely:

/etc/hostname.vio0:
inet autoconf
-inet6

This removes all IPv6 addresses and disables IPv6 autoconfiguration on the interface.

The configuration becomes active after the next reboot or immediately with:

$ doas sh /etc/netstart vio0

The /etc/netstart script reads the hostname.if file and applies the configuration. Running DHCP processes are restarted.

Static IP Configuration

Servers often require fixed IP addresses. A static configuration explicitly defines the IP address, subnet mask, and default gateway:

/etc/hostname.vio0:
inet 192.168.1.10 255.255.255.0
!route add default 192.168.1.1

The first line configures the IP address and subnet mask. The second line sets the default route. The exclamation mark ! executes the following command while the interface is being activated.

CIDR (Classless Inter-Domain Routing) notation works as well. Instead of writing the subnet mask in dotted decimal notation, /24 specifies the number of network bits—24 bits correspond to 255.255.255.0:

/etc/hostname.vio0:
inet 192.168.1.10/24
!route add default 192.168.1.1

The DNS resolver requires manual configuration in /etc/resolv.conf:

/etc/resolv.conf:
nameserver 192.168.1.1
nameserver 192.168.1.2
lookup file bind

The lookup line defines the order of name resolution—first /etc/hosts, then DNS queries.

ℹ️ Info
With DHCP configuration, dhcpleased automatically overwrites /etc/resolv.conf. Static DNS entries therefore require DHCP to be disabled for that interface.

Multiple IP Addresses on a Single Interface

A single interface can carry multiple IP addresses—useful for virtual hosts or service separation:

/etc/hostname.vio0:
inet 192.168.1.10/24
inet alias 192.168.1.11/24
inet alias 192.168.1.12/24
!route add default 192.168.1.1

The alias keyword defines additional IP addresses on the interface.

Different subnets on a single interface:

/etc/hostname.vio0:
inet 192.168.1.10/24
inet alias 10.0.0.10/24
!route add default 192.168.1.1
!route add -net 10.0.0.0/24 10.0.0.1

Different subnets require separate routing entries. CIDR notation (/24) and dotted decimal notation (255.255.255.0) can be mixed.

VLAN Configuration

VLANs provide logical separation of network traffic. OpenBSD creates VLAN interfaces using vlan0, vlan1, and so on:

/etc/hostname.vlan10:
vnetid 10
parent vio0
inet 192.168.10.1/24
up
description "Management VLAN"

The configuration defines:

  • vnetid 10 — VLAN ID
  • parent vio0 — Parent interface (here: virtio in a VM; on physical hardware, for example em0)
  • up — Bring the interface up
  • description — Optional description shown by ifconfig
ℹ️ Info
The order is important: VLAN parameters (vnetid, parent) must appear before the IP configuration. The interface must first be fully configured as a VLAN before IP addresses can be assigned.

The parent interface vio0 does not require an IP address if it is used exclusively for VLANs:

/etc/hostname.vio0:
up

Multiple VLANs can use the same parent interface:

/etc/hostname.vlan20:
vnetid 20
parent vio0
inet 192.168.20.1/24
description "Service VLAN"
up

Wireless Networks

Wireless configuration is performed through the corresponding interface files. The interface name depends on the driver—for example, iwm0 for Intel chipsets or athn0 for Atheros devices.

WPA2 configuration with a pre-shared key:

/etc/hostname.iwm0:
nwid "HomeNetwork" wpakey "supersecret123"
inet autoconf

The parameters define:

  • nwid — Network SSID
  • wpakey — WPA2 passphrase
  • inet autoconf — Start DHCP after the connection has been established

Hidden networks (SSID not broadcast) use the same syntax. The SSID must be specified manually because network scans display it without a name.

For enterprise WPA with RADIUS:

/etc/hostname.iwm0:
nwid "CorpNetwork" wpakey 0x1234567890
wpaakms 802.1x
inet autoconf

The hexadecimal key is provided by the network administrator. The wpaakms line enables 802.1X authentication.

⚠️ Important
WiFi passwords are stored in plain text in /etc/hostname.INTERFACE. The file should be restricted to root access: chmod 640 /etc/hostname.iwm0

Bridge Configuration

Bridges connect multiple network interfaces at Layer 2. A common use case is connecting virtual machines directly to the physical network.

/etc/hostname.bridge0:
add em0
add vio0
up

The bridge bridge0 connects the interfaces em0 (physical) and vio0 (virtual machine). Packets are forwarded transparently between both interfaces.

The member interfaces do not require IP addresses:

/etc/hostname.em0:
up

/etc/hostname.vio0:
up

The IP configuration is assigned to the bridge itself:

/etc/hostname.bridge0:
add em0
add vio0
inet 192.168.1.10/24
!route add default 192.168.1.1
up

Activating Network Configuration

Changes to hostname.if files become active after a reboot. To apply them immediately:

$ doas sh /etc/netstart vio0

The script stops running DHCP clients, applies the new configuration, and restarts any required daemons.

Restart all interfaces at once:

$ doas sh /etc/netstart

The system applies all hostname.if files and automatically creates any missing VLAN interfaces. This also works after larger network reconfigurations.

Time and Time Synchronization

OpenBSD uses ntpd for time synchronization. The daemon is part of the base system—no additional packages are required. The installer enables ntpd by default.

ntpd Configuration

The file /etc/ntpd.conf controls time synchronization:

/etc/ntpd.conf (Default):
servers pool.ntp.org
server time.cloudflare.com
sensor *

constraint from "9.9.9.9"              # quad9 v4 without DNS
constraint from "2620:fe::fe"          # quad9 v6 without DNS
constraints from "www.google.com"      # intentionally not 8.8.8.8

The directives define:

  • servers pool.ntp.org — Uses all IP addresses from the NTP pool. The daemon synchronizes with multiple servers simultaneously.
  • server time.cloudflare.com — An additional individual NTP server.
  • sensor * — Hardware time sources such as GPS receivers. The asterisk enables all detected sensors.
  • constraint from — HTTPS-based time validation. The daemon retrieves the current date over HTTPS and discards NTP packets that differ too much. This protects against compromised NTP servers.
  • constraints from — Uses all IP addresses of a domain for constraints. The daemon calculates the median of all responses.

The constraints protect against time-spoofing attacks. An attacker would have to compromise both NTP and HTTPS certificates simultaneously, which is practically impossible.

ℹ️ Info
Constraints work only when Internet access is available. Offline systems or systems behind restrictive firewalls should comment out these lines.
ℹ️ Info

To reduce dependencies on external service providers, you can configure your own NTP servers and constraints:

/etc/ntpd.conf (customized):
server ntp1.example.com
server ntp2.example.com
sensor *
constraint from "https://www.example.com"

Configuring the Time Zone

Internally, the system clock runs on UTC. The local time zone is defined through a symbolic link:

$ ls -l /etc/localtime
lrwxr-xr-x  1 root  wheel  33 Nov  1 21:27 /etc/localtime -> /usr/share/zoneinfo/Europe/Berlin

To change the time zone:

$ doas rm /etc/localtime
$ doas ln -s /usr/share/zoneinfo/Europe/Zurich /etc/localtime

Available time zones are stored in /usr/share/zoneinfo/. The directory structure follows the IANA Time Zone Database using the Continent/City format.

Managing the ntpd Daemon

The NTP daemon starts automatically during boot. Check its status:

$ rcctl check ntpd
ntpd(ok)

The (ok) status means the daemon is running. Verify time synchronization:

$ ntpctl -s status
5/5 peers valid, 1/1 sensors valid, constraint offset -2s, clock synced, stratum 3

The output shows:

  • 5/5 peers valid — All configured NTP servers are responding.
  • 1/1 sensors valid — Hardware sensors are active.
  • constraint offset -2s — Constraints are working. A small offset during startup is normal.
  • clock synced — The system clock is synchronized.
  • stratum 3 — Synchronization hierarchy. Stratum 0 consists of atomic clocks or GPS transmitters. Stratum 1 servers synchronize directly with those sources. Stratum 2 servers synchronize from Stratum 1. A Stratum 3 system like this obtains its time from Stratum 2 servers. Lower values indicate a more direct connection to highly accurate time sources.

Detailed peer information:

$ ntpctl -s peers
peer
   wt tl st  next  poll          offset       delay      jitter
162.159.200.1 time.cloudflare.com
    1 10  3 1551s 1627s         1.717ms    30.046ms     8.432ms
217.144.138.234 from pool pool.ntp.org
    1 10  2 1341s 1521s         3.957ms    42.019ms     9.079ms
94.130.35.4 from pool pool.ntp.org
    1 10  2    7s 1633s         1.234ms    40.783ms     8.753ms
45.9.61.155 from pool pool.ntp.org
 *  1 10  2 1381s 1565s         4.494ms    42.251ms    14.893ms
185.252.140.126 from pool pool.ntp.org
    1 10  2   30s 1641s         1.857ms    44.043ms    10.572ms

The columns mean:

  • wt (weight) — Server weighting used for time calculation (1–10, default is 1)
  • tl (trust level) — Trust level based on synchronization quality (0–10)
  • st (stratum) — Server hierarchy level
  • next — Seconds until the next query
  • poll — Current polling interval in seconds (adjusted automatically)
  • offset — Time difference between the server and the local system (ideally below 10 ms)
  • delay — Network latency to the server (round-trip time)
  • jitter — Variation of the time offset across multiple measurements

The asterisk * marks the server currently selected for time synchronization. The daemon automatically chooses the server offering the best combination of low offset, low latency, and stable jitter.

Time Synchronization with Large Clock Offsets

When the clock differs significantly from the correct time, ntpd corrects it gradually using adjtime(2) instead of stepping the clock. This avoids sudden jumps but takes longer. An offset of several minutes may require several hours before it is fully corrected.

For a faster initial synchronization, rdate can set the system clock immediately:

$ doas rdate pool.ntp.org
$ doas rcctl restart ntpd

After startup, ntpd typically requires about 5–10 minutes before the status changes to clock synced.

Privilege Delegation with doas

OpenBSD uses doas for privileged commands. The utility follows OpenBSD’s principle of simplicity—few configuration options and a straightforward syntax. Configuration is stored in /etc/doas.conf.

Basic Configuration

The installation article demonstrates a minimal doas configuration. For regular use, an extended configuration is recommended:

/etc/doas.conf:
permit persist :wheel
permit nopass keepenv root as root

The rules in detail:

permit persist :wheel — Members of the wheel group may execute any command with doas. The persist flag keeps the authentication valid for five minutes, so repeated doas invocations do not require entering the password again.

permit nopass keepenv root as root — The root user keeps environment variables when invoking doas and is not required to enter a password. This is important for root scripts that use doas for privilege delegation and depend on specific environment variables.

User-Specific Rules

Individual users can be granted specific permissions:

/etc/doas.conf:
permit persist :wheel
permit nopass michael cmd pkg_add
permit nopass michael cmd pkg_delete
permit nopass sandra cmd rcctl args restart httpd

The rules define:

permit nopass michael cmd pkg_add — User michael may execute pkg_add without entering a password. Useful for frequent package installations.

permit nopass sandra cmd rcctl args restart httpd — User sandra may execute only rcctl restart httpd. The args keyword restricts the permitted command-line arguments.

⚠️ Important
The file /etc/doas.conf must have the correct permissions: chmod 600 /etc/doas.conf. Incorrect permissions cause all doas requests to be rejected.

Controlling Environment Variables

By default, doas sanitizes the environment as a security measure against environment injection attacks. The keepenv flag preserves all variables:

/etc/doas.conf:
permit persist keepenv :wheel

More precise control is available through setenv with four variants:

/etc/doas.conf:
permit persist setenv { -ENV PS1=$DOAS_PS1 SSH_AUTH_SOCK PATH=/sbin:/bin } :wheel

The syntax supports:

  • SSH_AUTH_SOCK — Preserve the variable from the user’s environment
  • PS1=$DOAS_PS1 — Assign the value from an existing variable (using $)
  • PATH=/sbin:/bin — Set a fixed value
  • -ENV — Explicitly remove a variable (using -)

Multiple definitions are separated by spaces.

ℹ️ Info
The example from the manual page demonstrates best practice: explicitly remove ENV for security, set PS1 from DOAS_PS1 to make privileged shells easily recognizable, and preserve important variables such as SSH_AUTH_SOCK.

Testing the doas Configuration

Syntax errors in /etc/doas.conf disable doas completely—all requests will be rejected. Test the configuration before relying on it:

$ doas -C /etc/doas.conf

The -C option validates the syntax without executing any commands. A valid configuration produces no output. Syntax errors are reported together with the corresponding line number.

Kernel Parameters with sysctl

The sysctl utility reads and modifies kernel parameters at runtime. The values are grouped into categories such as networking, filesystems, hardware, and security.

Displaying Available Parameters

List all available parameters:

$ sysctl -a
kern.ostype=OpenBSD
kern.osrelease=7.9
kern.osrevision=202605
kern.version=OpenBSD 7.9 (GENERIC.MP) #449: Wed May  6 13:17:25 MDT 2026
    deraadt@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP

kern.maxvnodes=12277
kern.maxproc=1310
kern.maxfiles=7030
[...]

The output contains several hundred parameters. To filter by category:

$ sysctl net.inet.ip
net.inet.ip.forwarding=0
net.inet.ip.redirect=1
net.inet.ip.ttl=64
net.inet.ip.mtudisc=1

The hierarchical structure allows more precise filtering—for example, net.inet.ip.ifq displays only interface queue settings instead of all IP-related parameters.

Important Network Parameters

Enable IP forwarding — Required for routers or firewalls:

$ doas sysctl net.inet.ip.forwarding=1
net.inet.ip.forwarding: 0 -> 1

The system now forwards packets between interfaces. A default installation keeps forwarding disabled for security reasons.

IPv6 forwarding works the same way:

$ doas sysctl net.inet6.ip6.forwarding=1

Adjust system limits for servers handling many simultaneous connections:

$ doas sysctl kern.maxfiles=10000
$ doas sysctl kern.maxproc=2000

The maxfiles parameter limits the total number of open file descriptors system-wide, while maxproc limits the maximum number of processes. The default values are sufficient for typical servers. Adjustments are only necessary for services handling a large number of concurrent connections.

Permanent Changes

Permanent configuration is stored in /etc/sysctl.conf. The file does not exist by default and must be created:

$ doas vi /etc/sysctl.conf

Contents:

net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
kern.maxfiles=10000

The file is read automatically during boot. The syntax is identical to sysctl commands, except the sysctl prefix is omitted—for example, net.inet.ip.forwarding=1 instead of sysctl net.inet.ip.forwarding=1.

ℹ️ Info
Changes made to /etc/sysctl.conf do not take effect until the next boot. To apply them immediately, execute the corresponding sysctl commands manually as well.

Filesystem Layout

OpenBSD follows BSD conventions for its filesystem layout. There are some differences compared to Linux, particularly in the organization of system directories and package management.

Base System vs. Packages

OpenBSD maintains a strict separation between the base system and installed packages. The base system resides in /bin, /sbin, /usr/bin, and /usr/sbin, while installed software is placed under /usr/local. The article on Package Management explains this separation in detail.

Important System Directories

/etc — System configuration. All essential configuration files are located here:

  • hostname.* — Network configuration
  • rc.conf.local — Service configuration (must be created)
  • doas.conf — Privilege delegation
  • sysctl.conf — Kernel parameters (must be created)

/var — Variable data. Log files, spool directories, and temporary runtime data:

  • /var/log — System logs
  • /var/mail — Mail spool
  • /var/www — Default web root
  • /var/run — Runtime files (PIDs, sockets)

/usr/local — Installed packages:

  • /usr/local/bin — Executable programs
  • /usr/local/lib — Libraries
  • /usr/local/share — Shared data
  • /usr/local/etc — Package-specific configuration (created when needed)

/home — User home directories. Standard layout with hidden configuration files (.profile, .kshrc).

X11 Layout

X11 components are located under /usr/X11R6—provided that the X11 sets were selected during installation:

/usr/X11R6/bin       # X11 applications (xterm, xclock)
/usr/X11R6/lib       # X11 libraries
/usr/X11R6/share     # Fonts, icons, themes

This separation isolates graphical components from the rest of the system. Servers installed without the X11 sets still have the /usr/X11R6 partition, but it remains empty.

Device Nodes

Device nodes are located in /dev. OpenBSD creates them automatically during boot:

$ ls -l /dev/sd0*
brw-r-----  1 root  operator  4,  0 Jun  9 10:26 /dev/sd0a
brw-r-----  1 root  operator  4,  1 Jun  9 10:26 /dev/sd0b
brw-r-----  1 root  operator  4,  2 Jun  9 10:26 /dev/sd0c
[...]

The letter suffixes identify partitions:

  • a — Root partition (/)
  • b — Swap partition
  • c — Entire device (the complete disk)
  • d-p — Additional partitions as required

The exact assignments depend on the installation. The /dev/MAKEDEV script can create device nodes when necessary. Under normal circumstances this is not required, as the kernel manages /dev dynamically.

Mount Points

The file /etc/fstab defines which filesystems are mounted automatically during boot:

$ cat /etc/fstab
1d2d758e4ba30877.b none swap sw
1d2d758e4ba30877.a / ffs rw 1 1
1d2d758e4ba30877.k /home ffs rw,nodev,nosuid 1 2
1d2d758e4ba30877.d /tmp ffs rw,nodev,nosuid 1 2
1d2d758e4ba30877.f /usr ffs rw,nodev 1 2
1d2d758e4ba30877.g /usr/X11R6 ffs rw,nodev 1 2
1d2d758e4ba30877.h /usr/local ffs rw,wxallowed,nodev 1 2
1d2d758e4ba30877.j /usr/obj ffs rw,nodev,nosuid 1 2
1d2d758e4ba30877.i /usr/src ffs rw,nodev,nosuid 1 2
1d2d758e4ba30877.e /var ffs rw,nodev,nosuid 1 2

The hexadecimal strings are DUIDs—unique device identifiers. OpenBSD uses DUIDs instead of device names such as /dev/sd0a. This allows disks to be moved between controllers without requiring changes to fstab. The partition letters (a, b, d, e, …) are assigned during installation and vary depending on the selected partitioning scheme.

The mount options:

  • rw — Read-write access
  • nodev — Device nodes are not permitted (security)
  • nosuid — SUID binaries are not permitted (security)
  • wxallowed — Disables the W^X policy (required for JIT compilers in /usr/local)

Temporary Filesystems

The /tmp directory resides on its own filesystem. On systems with sufficient memory, /tmp can be mounted as tmpfs:

$ cat /etc/fstab
swap /tmp tmpfs rw,nodev,nosuid,-s=2G 0 0

The tmpfs filesystem uses RAM instead of disk storage. The -s=2G parameter limits its size to 2 gigabytes. All data is lost after a reboot, as expected for temporary files.


Information Used

This guide is based on:

  • OpenBSD: 7.8, 7.9
  • Architecture: amd64 (primary), the general concepts apply to all supported platforms
  • Context: Server and desktop configuration
  • Status: June 2026

System configuration provides the foundation for productive OpenBSD systems. The next article covers Package Management—software installation and updates.

Michael of the Dragons

develops books, software, and open frameworks around technical systems, digital independence, and durable software architectures.
More about Michael →
OpenBSD: System Configuration After Installation
← Next Article OpenBSD: Package Management and Software Installation
OpenBSD: System Configuration After Installation
Previous Article → OpenBSD: Installation and First Steps