The Basics

This guide is intended to be a brief overview of the basic setup of an OpenBSD server. It is intended not only as documentation for myself, but also as a guide for any who are interested in setting up their own web server. Although this guide is designed for people who already consider themselves to be "computer people" in some way or another, I believe you'll be able to at least get through this guide if you: understand conceptually how the internet works, and also know a little bit about how to use a UNIX-based Operating System. If you're not there yet and you'd like to get there, the journey is not difficult, but knowing where you need to start is. In the future, another guide is intended which should give anyone who is computer-literate the network and UNIX fundamentals they need in order to build their own server, for whatever reason they need.

What Is A Server Really?

A server - at its core - is a computer that you can access over a network. What you do with that computer is really up to you. However, if you're creating a server that is supposed to do typical 'webby' things, like serve webpages or email, you probably want some guarantees about your computer:

Getting Server Hardware

If you can ensure these guarantees yourself, then you could host the server yourself - on your own hardware. If you have the hardware lying around, it might even be cheaper. I do not have the hardware, so I'll be using a VPS (Virtual Private Server) which is essentially just renting a computer from someone else. There are many VPS provider, but my preferred is Vultr. Vultr has server for decent prices. My go to is always to go with the cheapest option first, because it's very easy to scale up your server when the need arises. That'll set you back no more than $5.00 a month at the time of writing. Now that we've decided on our hardware, let's do some work on the server.

Choosing an OS

We're going to use OpenBSD. If you're using one of Vultr's OpenBSD instances, you'll be able to access a console on your server from Vultr's web management page. Once your logged into the root account, you may notice that Vultr's OpenBSD instance comes with many packages pre-installed (you can see instaled packages by running pkg_info). Feel free to remove all* of them by running pkg_delete -X. We won't be using most of these packages, and if you're coming from a Linux background, don't worry about breaking anything by uninstalling packages. BSDs contains contain everything that is necessary to run their system in their base install, not in the package manager. If you're from a Linux background, you may want to read the page the OpenBSD Handbook's page on OpenBSD for Linux users as well. It's short and informative.

*If there are intel microcode update binaries pre-installed, you may want to leave these installed. From my understanding, these are security patches for the CPU.

Creating a User

Some folks prefer to manage their servers by logging in as root. There's merit to this, most of the commands you run will need to be ran as root anyways. However, I prefer to have my own unprivelaged use who is allowed to run commands as root. Check the footnotes for why. OpenBSD has an adduser command which provides an interactive way to add a user. The typical useradd command is also available if you prefer it. I also prefer adding all admin users to a shared admin group, but this is optional.

Creating a doas config

Our new user is unprivelaged, which is good, but we'd like them to be able to run commands as root. So let's create a file called `/etc/doas.conf` and add the following line to it, replacing [user] with your own username:

permit [user] as root

Now you can execute any command as root. Feel free to read man doas.conf if you'd like more fine grained access. OpenBSD is known for having fairly simple to understand configs

Allowing for ssh

If you're still logging into your server using the Vultr Web Management Console, you'd probably prefer being able to log in via ssh. This is fairly easy to do. First, on your server, make sure your ssh daemon is running. You can check if it's running using rcctl check sshd, and if the command fails, you can run it using rcctl start sshd and enable it to run on startup using rcctl enable sshd.

Once you're sure the ssh daemon is running, you'll be doing everything else from your client computer. The following command will launch you into an interactive prompt to generate a public and private ssh key:

ssh-keygen -t ed25519 -f [path/to/store/key]

Now you're going to want to copy the public key to your server. Replace [host] with your server's IP address. You can do this with:

ssh-copy-id -i [path/to/key].pub [user]@[host]

You should now be able to login using ssh -i [path/to/key] [user]@[host]. Although this is a command we'll be runnning very often, so you may want to make an entry in to ~/.ssh/config on your client computer so that to make logging in easier. I'll leave mine here:

~/.ssh/config on client computer
. . . Host [hostname] IdentityFile [/path/to/key] IdentitiesOnly Yes . . .

At this point, you'll likely also add or uncomment the following lines from your /etc/ssh/sshd_config file to harden your ssh daemon a little bit.

/etc/ssh/sshd_config on server
. . . PermitRootLogin no PasswordAuthentication no . . .

Set Up Your Firewall

Your firewall is meant to block all incoming connections that you may not trust. OpenBSD's firewall, pf, is incredibly robust. I encourage you to read up on it at, you guessed in, man pf.conf. Here's the spark notes of what you need to know you need to know about pf:

For every single connection on the network, inbound or outbound, pf will try to match that incoming connection against all the rules set in /etc/pf.conf. By default, the config file is going to contain a line that says pass all or something similar. This means that all traffic is allowed on the network interface, or in other words, the firewall is not blocking anything. This is only the default config because if, by default, there was a strict firewall, new users would get extremely confused when nothing on their server could connect to the internet. For security, we'd like as strict a firewall as is tolerable. Right now, the only daemon we have listening on an internet port is sshd, so let's set up stricter firewall rules. Edit your config to look something like this:

/etc/pf.conf
# Don't evaluate firewall rules on local loopback devices set skip on lo # Block all connections unless whitelisted block all pass out proto {tcp udp} to port { 53 123 } # dns and ntp - needed to resolve domain names and get time pass out proto {tcp udp} to port { 80 443 } # http(s) outbound - needed to install packages pass out inet proto icmp icmp-type { echoreq } # allows server to ping and be pinged, for testing. Remove if you want pass in proto {tcp udp} to port 22 # ssh

We can then check if our config is valid using pfctl -nf /etc/pf.conf and load our config using pfctl -f /etc/pf.conf.

If you're new to firewalls, something you should know is that if you allow an inbound connection, it also allows your server to respond to that connection, even if you're blocking outgoing connections on the same port. Inbound and Outbound rules only care about the request, not the response. For example, if a server has the following lines in its config:

Example /etc/pf.conf
... block out to port {http https} pass in to port {http https} ...

it will respond to all http(s) requests, just like a normal webserver would.

Digital Ocean has an article on basic firewall configuration, if you'd like to get even more in depth. Firewalls are your first and strongest line of defense against bad actors trying to get into your site. Treat them with the respect they deserve.

Getting a Domain

If you haven't already, you're going to want to buy a domain from a reputable registrar. I don't have any recommendations for reputable registrars right now, but I believe it's hard to make a mistake with this. If you're looking for a new registrar, remember that for our purposes, a registrar will only be used for:

And these are not actions that will take up hours of your time each month. I interact with my registrar maybe a handful of times per year. All this is to say don't overthink this part, the main thing that will differentiate registrars is their privacy policy (how they handle WHOIS requests) and their prices. You can usually switch your domain between registrars for a fee as well.

Making Your Domain Point To Your Server (Intro to DNS Records)

So for this whole tutorial we've only been using IP addresses. That's becaues computers use IP addresses to find each other across networks. They don't know how to do it any other way. But IP addresses aren't easy to remember. Domain names are much easier to remember, so how do we link our domain to our server's IP address? We do that by modifying our domain's DNS records.

When we buy a domain from a registrar, we receive exclusive rights to use that domain, yet, but we also receive a set amount of space on a their servers where we are meant to put information about our domain. This information about our domain is called our DNS records (Domain Name Server records). And as a result, in order to have our domain name point to our IP address, we need to add a new DNS record that does exactly that.

Cloudflare has a great explanation on what DNS records are, The DNS record that we need to add is called an A record if you have an IPV4 address, or an AAAA record if you have an IPV6 address. Therefore you're going to want create a new A/AAAA DNS record that points from your domain name to your IP address. There will be a TTL (time-to-live) field as well which essentially tells you how long (in seconds) it will take for a change in your DNS record to update. A higher TTL may result in quicker DNS lookups due to caching, but while we are just setting up our server, lets set the TTL to 300 so that we can make changes relatively quickly. Every registrar has a different format for viewing and changing DNS records, although they should all contain the same info. Here's roughly what my A/AAAA records look like:

A/AAAA DNS Records
prefix record type value TTL ------------------------------------------ A 192.0.2.1 300 AAAA [2001:3f37::] 300 www A 192.0.2.1 300 www AAAA [2001:3f37::] 300

As you can see, my site has 2 A records and 2 AAAA records. Since I am using both A and AAAA, this means that my server must have both a valid IPV4 and a valid IPV6 address. Each record will exist for 5 minutes before it is updated. In additional to having A/AAAA records with no prefix, I've also included A/AAAA records with the www. This way both example.com (no prefix) and www.example.com (www prefix) will resolve to the same server.

The BIND format is a fairly clear and common way of representing DNS records. This is what the above record would look like in the BIND format:

DNS Records in BIND format
$ORIGIN example.com. $TTL 5m IN A 192.0.2.1 IN AAAA [2001:3f37::] www IN A 192.0.2.1 www IN AAAA [2001:3f37::]

We're done

That's it. By now your server should be good to go. I've undoubtedly glossed over some of the finer details, but I really wanted to document the broad strokes. You should now have a pretty solid base to do whatever you'd like. Check out the other guides if you'd like to run a web server or a mail server on top of our lovely base.

Footnotes & FAQ

Why do you use separate users instead of logging in as root?

The main reason is just access control. If every admin logs into his own user, then when an admin needs to be added, a user account is added. When an admin needs to be deleted, a user account is deleted. No need to finangle and find which ssh key needs to be removed. Another secondary reason is that some people prefer to leave the ssh 'Login by password' feature on. Imagine this scenario:

You're an admin on a web server and you leave the LoginByPassword feature enabled in your ssh config. Your ssh daemon listens for incoming connections on the default ssh port, 22, and LoginAsRoot is enabled. Now image that I'm a bad actor who rights a basic web script that does the following:

For every public IP address on the internet:
    ssh root@ip_address
    If this returns a prompt for a password:
        Begin brute force attack on root@ip_address

With LoginAsRoot enabled, you have just told everyone on the internet: "Here's the most privelaged account on my server. See if you can guess what password it is." If you at least disable LoginAsRoot, you remove this basic attack vector. Yes this IS security by obscurity, but so is having a password to begin with, so if you're going down that route, try your best to obscure as much as possible. Your SSH daemon is easily overlooked, but it is also one of the most important things to harden.