How to host your server with OpenBSD ?

You are about to dive into the world of self-hosting. This document is written to help you host at home or on a dedicated (rented) server some services unfortunately too often entrusted to third parties. The main goal is to keep things as simple as possible while learning gradually. Of course, compromises have been made. If you feel you want to learn more after reading this, that's great! ๐Ÿ˜Š

Why OpenBSD?

In order to keep things simple, but secure, we describe OpenBSD OS in its last stable version. It is known to be safe. It is also, in my opinion, easy to configure because the same syntax is shared by different included tools.

OpenBSD official website

Why OpenBSD rocks

Some says OpenBSD documentation is great. How this documentation can be useful to me?

It is for sure! Manpages are amazing, use them as a reference. I see this documentation as an entry point, not a replacement. Some fundamentals will be explained with a few tips. Anyway, manpages are great and you should read them too.

You'll see, hosting your server isn't that difficult and is mostly text-edition. Everyone should be able to do it.

Ready ?

Self-hosting : what is it? Pros and cons.

Most website you're used to read -- emails, social networks... -- are hosted on computers somewhere in the world. They are only used to serve contents to other computers, so we call them "servers". The biggest difference from most people point of view is that "they don't have a screen".

When you want to read your mails, a client (a webmail, Thunderbird...) is asking the server to retrieve your messages. A copy of them is then downloaded on your computer. In "real life", that would look like this :

Hey, mailman, do you have anything for me?

Yes, a postcard from your mom. I give it to you as soon as the copier finish to print it.

Of course, you can ask the post office to delete the message. But how can you be sure ALL copy have been deleted?

Better become you own post office, don't you think? ๐Ÿ˜€

At first, everyone was supposed to make a part of the web. Now, most of us depend on private companies that disrespect privacy.

Pros

Arguing that you don't care about the right to privacy because you have nothing to hide is no different than saying you don't care about free speech because you have nothing to say. -- E. Snowden

Read about the argument "Nothing to hide"

Cons

About this document

In this document, we assume that:

Understand that your reference should always be the official FAQ.

OpenBSD

OpenBSD FAQ

About and license

This documentation is published under CC-BY license, using gemtext files (plain text), converted with "gmi2xhtml.awk" and a Makefile. It was written by an OpenBSD enthusiast (and contributors) who wondered "If I can, others too". Please tell me if you share this document somewhere else, just to know. Thanks. ๐Ÿ˜‰

If you find any mistake, whish to contribute or need help, feel free to contact me.

License CC BY

gmi2html.awk

Author's website

Write to the author

There is no ad or tracking scripts on this site.

Only your contributions help me to pay hosting and time given to this project.

If you want and can, you can donate to help me. Thank you! โ™ฅ

Do not hesitate to print this documentation on paper or on PDF ๐ŸŒณ. CSS is ready for a nice print rendering ๐Ÿ˜‰.

What about the official FAQ?

This document is not a duplicate of the official OpenBSD's FAQ. You should ALWAYS refer to official documentation and manpages when available.

For those who need to read the FAQ offline, I keep an up-to-date archive downloadable from my server.

Official OpenBSD's FAQ

openbsd-faq.tgz : download and extract to read it offline.


โ†’ What hardware can I use ?

โ˜ฐ Table of content

โ™ฅ Donate

What hardware should I use ?

You don't need phenomenal power to self-host. Start with a machine recovered because it is too weak for office use.

If you want to buy new hardware, check first if it's suported by OpenBSD :

https://www.openbsd.org/faq/faq1.html#Platforms

ARM architectures don't require much power.

If you don't know where to start, APU are quite amazing : not too expensive, small, silent, require less than 10W and well supported. Actually, this documentation is hosted on an apu2d0.

APU2 board constructor.

Take a look at bsd-hardware too and do not hesitate to contribute.

What about OpenBSD on a Raspberry Pi ?

RPi are supported from 3rd version.

Read carefully instructions as you will need some files too boot correctly.

OpenBSD install

Make sure you read official documentation about OpenBSD install.

There is really not much things to say because it's really easy.

When in doubt, just leave defaults ๐Ÿ˜‰. This is especially true for disk slicing.

Read disklabel editor's commands if necessary.

Oh, you may want to make a Full Disk Encryption install by the way.

You also should install all the sets or make sure you understand what you don't install.

Someone can host OpenBSD for me ?

If your don't have a good internet access, or if you want to try before setting it up at home, you can rent a server or a virtual host. I've had good experience with :

openbsd.amsterdam

They offer virtual machines. The team help to develop OpenBSD. Mischa is very nice.

vultr

Works very well. It's fast and reliable. A bit more expensive though. (The above link is a refferer link).

Similar setups can be found at ARP Networks:

ARP Networks

Survival guide: which commands must I know?

When you turn on your server, whether it is connected to a monitor or through SSH, you will see a command prompt:

acdc $ โ–ˆ

Enter commands to manage your server.

Of course, there are a lot of them you'll find over time.

For now, let's see a few of them. Don't try to memorize all of a sudden - come back and get what you need when the time comes.

Tips

Tip # 1: Tab

By using the "tabulation" key โ†น, you can complete a command or a path to a file. Start writing the beginning, then press โ†น.

Tab is a-ma-zing! ๐Ÿ˜‰

Tip # 2: ctrl-c

To undo what you are writing, press "ctrl" and "c" simultaneously. ("cancel").

Tip # 3: "\"

Although this is very rare, some filenames sometimes contain spaces " " or even strange symbols. However, a space is considered by the command prompt as a separator between files. It may therefore understand that you are referring to several files instead of just one.

Then use "\" to "escape" the weird symbol. That way the Command Prompt will sort of ignore it. For example :

/path/to/some/file\ with\ spaces.txt

In any case, avoid creating files with strange names.

If you need to treat a large number of them, check out the "detox" tool (port of the same name).

Tip # 4: order history

To find the history and quickly relaunch an old command, use the shortcut ctrl-R.

You must first enable it by adding export "HISTFILE = ~ / .history" in the "~ / .profile" file:

$ echo "export HISTFILE = ~ / .history" >> ~ / .profile

At next login, the history will be active.

su and doas: how to get superuser privileges (root)?

Enter the command "su -l" then the password for the root user.

WARNING: in this case, your user must belong to the "wheel" group for this to be possible. This is the case for the first user created on a system.

You can also configure doas to run a command with superuser privileges as follows:

doas command

Edit / create the /etc/doas.conf (doas.conf) file to add:

user-name permit

Adapt "user-name" according to your needs.

See also :

"man doas"

ls: list the content of a directory

Start "ls" followed by the path to the folder to list (or nothing to list the current folder).

The "-l" option also allows you to display permissions, owners, sizes and modification dates.

Example:

$ ls -l /etc
drwxr-xr-x  7 root  wheel        512 Apr 19 19:12 X11
drwx------  2 root  wheel        512 Apr 19 18:16 acme
-rw-r--r--  1 root  wheel       1542 Apr 13 15:39 acme-client.conf
-rw-r--r--  1 root  wheel       1764 Nov 28 13:56 adduser.conf
drwxr-xr-x  2 root  wheel        512 Apr 19 18:16 amd
drwxr-xr-x  2 root  wheel        512 Apr 19 18:16 authpf
-rw-r--r--  1 root  wheel         30 Aug  2  2020 boot.conf
[...]

We get one line per file / folder. Each line show these fields :

<permissions> <inode> <owner> <group> <size> <date of last access> <file name>

A simple and yet efficient method to secure your website - and more generally its server - is to adjust the permissions and the owner of the files of said site.

Read the sections on "chmod" and "chown" to learn more.

chmod: change permissions

Let's take a closer look at what the return of the "ls -l" command seen previously tells us.

The letters at the beginning of the line describe the permissions granted to the file. We can remember two things:

1. If the first character is "d", then it is a directory. Otherwise, it is a file (with exceptions).

2. The remaining characters are read by set of 3. Each "triplet" describes the permissions for the owner, for the group, and for everyone else, respectively.

For example, for this line:

drwxr-xr-x 2 www daemon 512 May 5 17:10 bin

We see that it is a directory. Then we read letters 3 by 3:

rwx: The owner www can:

"r-x": Those belonging to the "daemon" group can

"r-x": All others can:

As a general rule, you should avoid as much as possible giving write and execute rights to people other than the owner. Sometimes, reading permission is also withdrawn on certain files (passwords, etc.).

To change permissions, there are several methods.

"symbolic" chmod

Some use a set of numbers, like "chmod 700". I find this way not very explicit when you are not used to it yet. Even if you have to type a few more commands, prefer to use chmod <identity>ยฑ<permission> where:

Will you take a few examples?

These changes can be applied recursively (to all subdocuments in a folder) with the -R flag.

Tip: to allow moving in folders, without making the files executable, use X (uppercase) instead of x.

"absolute" chmod

If you want to understand the numerical notation of a chmod:

There is no distinction between folders or files, so proceed with caution.

The first number describe to the permission giver to the owner, the second to the group, the last to the others.

We add the values. This means that "chmod 700" grants "rwx" permissions to the owner, and none to the group and others (7 = 4 + 2 + 1).

Finally, in order to define the permissions by distinguishing between the folder and the files, and not to make a file executable with a "chmod -R" (recursive), the "find" command is your friend:

As always, the "man chmod" command will tell you more.

chown: Owner and group

Each file has an owner and is part of a group. This will allow us to give certain permissions to the owners, which will not necessarily be the same as those given to the group member.

To modify the owner and the group, we use the chown command.

# chown <owner>: <group> filename

File management

Before we see how to handle files, take note of the following notations:

pwd: display the current folder

With pwd you ask "where am I" ๐Ÿ˜„

mkdir: create a directory

$ mkdir name_of_new_folder

Use the "-p" option to create a whole structure at once:

$ mkdir -p ~/folder/with/some/subfolders

cd: change directory

To move to the "/var/www" folder:

$ cd /var/www

The "cd" command without argument moves you to your "$HOME".

cp: copy

To copy a file:

$ cp source_file copy_file

To copy a folder and its contents:

$ cp -R source_folder copy_folder

rm: delete

$ rm path_to_the_folder
$ rm -R path_to_the_folder

mv: move

$ mv source destination

It's like cut and paste.

less: how to read and search in a file?

To only view a file, use the "less" command.

Then you can search for any string of characters by pressing "/" and entering your search. Press "n" to go to the next occurrence, or "N" to go back.

To exit less, press "q".

If you want to search through the content of the logs, that can be handy ๐Ÿ˜‰.

man

Here is the real reason why there are not much support forums around OpenBSD but only a mailing list: the man pages are very comprehensive, complete with examples, and most of the time are sufficient to answer questions/problems encountered.

The "man" command allows you to display a man page.

Note that there are different sections for categorizing man pages:

Also, it sometimes happens that a man page exists in several different sections: its content is not the same. In order to differentiate them, one refers to a manpage as follows: "page_name(section)".

For example: "apm(8)", or "apm(4)". Or "man(1)" and "man(7)". Yes, "man" has a "man" page.

We use this command as follows, without parenthesis:

$ man (section) page

The section is optional.

To practice, run "man hier". Use arrows to scroll. As with "less", you can search with "/". Notice the "SEE ALSO" part which invites you to read other manpages that may be of interest. Exit with "q".

If you don't know what the name of the man page is, you can search for it with the "apropos" command:

$ apropos your_research

vi: to edit a file

Knowing how to edit a file is crucial.

There is a lot of text editors (vim, nano ...). The default editor on OpenBSD is vi. (There is ed too...)

vi

It may be confusing to use at first, so some people may want to install another editor instead. However, vi is handy once you get it. If, on the contrary, you are already used to the emacs editor, you will find what you are looking for with the mg editor, also available by default.

Here are some tips for using vi through an example. To edit the /etc/iloverocknroll file, you would enter this:

$ vi /etc/iloverocknroll

The contents of this file will then appear in the terminal.

Most of the time, you will only do this:

You are still here ? ๐Ÿ˜

So let's go a little further (but not too much, we promise ๐Ÿ˜). Take note that there are three modes:

To save the changes, press ":" then "w". Validate with enter. We can now quit by writing ":q". Note that you can go faster by typing directly ":wq".

To cancel a modification press "u". To remove multiple changes, press "u" then "." as many times as necessary. "." allows you to repeat the last action.

In order to search for a string, which is very useful in large files, press the "/" key then write your search.

If you want to exit without saving your changes then type: "q!".

Other very handy tips:

rcctl: How to manage daemons?

In order to activate/deactivate daemons, the "rcctl" command is provided for this purpose. All available services are in the "/etc/rc.d" folder. Here are some reminders:

If you prefer the manual method, then you can directly edit the "/etc/rc.conf.local" file which manages the services launched at startup.

Let's practice

Let's train with a little exercise. Follow the instructions below, then check that you obtain the same thing as in the "Answer". Try to do it from memory first. If you get stuck, read again the page looking for what you are missing.

The answer :

$ cd /tmp
$ mkdir ah
$ cd ah
$ vi dw.txt
$ cp dw.txt DrWho.txt
$ chmod 600 DrWho.txt
$ rm dw.txt
$ cd ~
$ pwd
/home/prx
$ ls -l /tmp/ah
total 2
-rw ------- 1 prx wheel 22 May 5 21:10 DrWho.txt
$ cat /tmp/DrWho.txt
Allons-y

Note that we can replace "cd ~" by "cd $HOME", or even by "cd".

What sould you do after first boot ?

First, read "afterboot(8)" and "intro(8)" manpages. Believe it or not, OpenBSD's developpers wrote a few words for you after install โœŒ

$ man afterboot
$ man intro

You'll learn how to read manpages actually.

man afterboot(8) online

man intro(8) online

Look configuration examples

See examples in "/etc/examples".

If you add ports, read included instructions un "/usr/local/share/doc/pkg-readmes".

Set admin mail

Edit "/etc/mail/aliases" and set root mail to get important reports:

root: batman@athome.tld

Shut down daemons

You may want to disable non used daemons, such as sndiod in charge of audio (unless you need it of course).

By the way, let's talk about "rcctl" to deal with daemons.

List enabled daemons :

# rcctl ls on

To stop daemon, i.e audio server :

# rcctl stop sndiod

And to disable daemon for next boot :

# rcctl disable sndiod

You probably want to enable power management. As an example, below lines will enable apmd, add "-A" flag to daemon startup and actually start apmd :

# rcctl enable apmd
# rcctl set apmd flags -A
# rcctl start apmd

Read intro(8) to learn about default daemons availables on OpenBSD :

man 8 intro

Network addresses

What is a netword address ?

IP means Internet Protocol. But first, let's talk about Santa ๐ŸŽ….

To send a letter asking for all the presents you want, you write to the famous address "Santa Claus, North Pole, Rainbow road.". But how does Santa knows how where to answer? Of course you wrote your own address on the back of the envelope.

That's how works computers and servers : you know where is the video you want to watch, and the server hosting the video knows where to send data.

Every device has its own IP on the internet. There are numbers like 192.0.2.2, not as good looking as Santa's pretty address isn't it?

How devices knows what is the IP of domain name like wikipedia.org ? It there a directory?

Indeed there is something like that : DNS. We'll talk about it later.

There are differences between local (or private) IP ans public IP. Most of the time, to reach the internet, your ISP lend you a router so your network look like this :

+-----------------------+
|                       |
| Local Network (home)  |
|     PRIVATE IP        |
|                       |
|                       |
|      laptop       <---+------+
|     192.168.1.20      |      |
|                       |      |
|                       |      |
|     Phone         <---+--+  ++---------------+
|     192.169.1.21      |  |  |                |
|                       |  +--+  Router  (box) |    +----------+
|                       |     |                |<---+ INTERNET |
|     Computer      <---+-----+  PUBLIC IP:    |    +----------+
|     192.168.1.22      |     |   192.0.2.2    |
|                       |  +--+                |
|   * * * * * * * * *   |  |  ++---------------+
|   * Your server   <---+--+   |
|   * 192.168.1.23  *   |      |
|   * * * * * * * * *   |      |
|                       |      |
|     Game console   <--+------+
|     192.168.1.24      |
|                       |
+-----------------------+

To host a server, it is useful to have a satic IP delivered by your ISP. Some don't, so you may want to look for a VPN or dynamic DNS or change ISP :)

Reminder

To run your own server, you must know what are its IP, public, local, and its gateway (router). At first, one may want to configure its network using dhcp then configure a static network when more confident.

Let's see a few words to do so.

Public or local/private address

First, understand your server is probably behind a router given by your ISP. The router received a public IP. People "on the internet" can reach this IP.

The router allocate private addresses to devices connected in your local network. Each device has its own private IP : one for your phone, another for a printer, a computer, a console... They all share one public address.

Private addresses can be in three ranges :

Your private or local IP is the one your computer use, the one it knows.

To find your local IP, run "ifconfig" and filter for "inet". You'll see something like that :

# ifconfig |grep inet
inet6 ::1 prefixlen 128
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x3
inet 127.0.0.1 netmask 0xff000000
inet 192.168.1.2 netmask 0xffffff00 broadcast 192.168.1.255
inet6 fe80::feaa:14ff:fe65:5f86%re0 prefixlen 64 scopeid 0x1

inet means IPV4, inet6 means IPv6.

Your public address is the one "used" when you browse the web. Websites you're used to read see this IP. If you don't know it, you can check one of these links (amongst many others):

ipaddress

ipecho

ifconfig.me

lehollandaivolant.net

wizcase

Router

The router is the box your ISP gave you. We also call it a gateway because on a side it is the "public" network, on the other your "private network". The router deals with redirections : if someone wants to reach your website, the router will lead him to your server's private address.

To find your gateway IP, use "route" command:

# route -n show
Routing tables
Internet:
Destination        Gateway            Flags   Refs      Use   Mtu  Prio Iface
default            192.168.1.1      UGS       35     4827     -     8 re0
224/4              127.0.0.1          URS        0       13 32768     8 lo0
127/8              127.0.0.1          UGRS       0        0 32768     8 lo0
127.0.0.1          127.0.0.1          UHhl       9      969 32768     1 lo0
...

The first line is what you're looking for : 192.168.1.1 in this example.

Hostname

Every device in your network has a name to identify it. It is the hostname.

On a computer, you can change this name. To do so on OpenBSD, edit the /etc/myname file and write the complete hostname:

server.athome.tld

DHCP

To configure a network interface, you can use DHCP to autmatically get a local address. Be careful though, the "D" means "Dynamic", so this addres might change in the future depending on your router.

To do so, edit /etc/hostname.if file, and replace ".if" by the name of your inteface (run "ifconfig" to find it) and just write "dhcp" inside.

dhcp

Static configuration

A bit more complicated, static configuration gives you more control.

In /etc/hostname.if (replace "if" with your interface name) :

inet            192.168.1.2    255.255.255.0     192.168.1.255
inet   alias    192.168.1.9    255.255.255.0     192.168.1.255
## and as many as you want
# inet 	(alias) local_ip       network_mask      broadcast_add
up

If you're not sure, at first configure using dhcp and look at the output of "ifconfig".

You must specify the route to your gateway. To do so, you can edit "/etc/mygate" file or add a "!route" command to "/etc/hostname.if". I prefer the latter as it avoid to split configuration in multiple places.

The gateway is you router's IP : 192.168.1.1 here.

inet            192.168.1.2    255.255.255.0     192.168.1.255
inet   alias    192.168.1.9    255.255.255.0     192.168.1.255
!route add -inet default 192.168.1.1
up

See also :

man hostname.if

IPv6

IPv6 is the new protocol to use facing the lack of ipv4 available. The main advantage is that you don't need a router doing NAT : your machine is directly reachable.

To configure your interface, magic happens in /etc/hostname.if with the "inet6" lines:

inet...
inet6   2001:db8:1:1::2    64 # adresse
inet6   autoconf              # SLAAC, if you want
!route add -inet6 default 2001:db8::1
up

You can mix inet and inet6 instructions.

Configure your router and redirect ports

Most of your devices access the Internet through a router, probably given by your ISP. The router has a public IP address, making it reachable from the outside. We must learn how to tell the router to forward requests from the outide to your server (not anything else) depending which port number is used.

You can imagine your router as a big wall when seen from the outside. In this wall, there are doors : we call them "ports". When someone knocks at the door 80, he want to see a web serveur. Your router has a configuration line for this cas and will redirect the visitor to your server internal IP.

Most of the doors will remain closed, because you didn't configure a redirection for them, and that's fine to keep your other equipments safe.

                       +---------------+
                       |               |
                       | Router  (box) |
                       |  ***********  |
                       |               |
              +--------+-- door 443    |
+---------+   |        |               |
|         |<--+        |               |
| Server  |         ?<-+---door 143  <-+--- [Evil bot ๐Ÿ‘ฟ]
|         |<--+        |               |
+---------+   |        |               |
         ^    +--------+-- door 80  <--+--- [Guest ๐Ÿ‘ผ]
         |             |               |
         |             |               |
         +-------------+-- door 22     |
                       |               |
                       +---------------+

Your router is a GATEWAY with a janitor.

Understand that if your server is reachable with IPV6, no redirection is required : its IPV6 is routable and reachable. That's why it is important to always configure the firewall on your server too.

To configure your router, it might have a web interface reachable using a browser from a computer connected on your local network. Some ISP provide a configuration tool on their web panel. It depends, you'll have to find out or ask your ISP how their router work.

Most of the time, you can try these URI in a browser :

Probably, a username/password will be required. Ask your ISP, or try "admin/admin" or "admin/password", or look what's written on the router.

Here are more clues :

https://www.wikihow.com/Configure-a-Router

DMZ

If you do not want to bother with redirectionc, configure a DMZ to your server on your router : all traffic will be redirected to your server. If so, remember to configure your firewall accordingly ๐Ÿ˜‰.

Get a domain name

Instead of writing your public IP, someone might want to reach your server using something more human friendly : a domain name.

What is a domain name ?

One can register a domain, i.e. "something-cool.tld" as a sign pointing to your IP address. When an human enter "something-cool.tld", the computer will ask the registrar what IP is behind this sign to reach it. In this case, the domain is resolved.

It is easier to remember, it gives identity to your project, and it let you organise it using subdomains (webmail.athome.tld, cloud.athome.tld...).

Below is a little comic to explain a DNS query :

../../../img/life-of-a-dns-query.png

You can find free registrar, or rent a domain name. There are many registrars out there. I've been happy with these ones, especially GANDI because it's self-hosting friendly :

GANDI (20% off with this referal link)

OVH

nic.eu.org

DNS records

When a device try to reach "athome.tld", it ask a DNS resolver what IP is behind. DNS is like road signs.

To create this sign, you must link the domain name to your IP in a zone. It can be done in registrar panel if you don't host your own domain name server (see nsd later ๐Ÿ˜‰). As example :

athome.tld    A    192.0.2.2

Different records exists. You must at least know:

blog.athome.tld CNAME athome.tld
wiki.athome.tld CNAME athome.tld
webmail.athome.tld CNAME athome.tld

Learn more with these links :

https://en.wikipedia.org/wiki/Domain_Name_System

https://howdns.works/

Learn how to host a domain name server.

Daily reports

Once your server running, you must make sure everything works fine. Do not read any further if you don't plan to maintain it. Luckily, OpenBSD developpers made things easier.

Every day (infact every night) a report from "Charlie Root" is generated and mailed to user "root". Inside, you can find :

I look like this:

Running security(8):
Checking mailbox ownership.
user spamd.black mailbox is owned by root
user spamd.black mailbox is -rw-r--r--, group wheel
======
/etc/rc.local diffs (-OLD  +NEW)
======
--- /var/backups/etc_rc.local.current   Sat Jun  4 03:46:48 2016
+++ /etc/rc.local       Thu Oct 20 09:46:54 2016
@@ -1 +1 @@
-su pi -c "tmux new -s rtorrent -d rtorrent"
+/usr/bin/su pi -c "/usr/bin/tmux new -s rtorrent -d /usr/local/bin/rtorrent"
======
/etc/spwd.db SHA-256 checksums
======
OLD: 075455a721ef[...]b942f590fb8e3edfd88c5dd4
NEW: 37eba7537dd7[...]42468cc4cbe768fcf496b230

You can read warning about file owners and changes in a file. Lines starting with "+" were added, those starting with a "-" removed.

In this example, at last, a checksum on the password database has changed : did you add a new user?

Well, that's nice, but how do I get it?

As you will see, it's very easy :

Of course, replace with a mail address you read daily.

Then, run "# newaliases".

That's it! ๐Ÿ˜„

Just to make sure, send yourself a test message :

echo "Hello sweetie!" | mail -s "test" root

You should get this mail at the address written in /etc/mail/aliases.

Now, it's up to you to read these report every day.

If you need to, you can change the domain name of the sender if you edit /etc/mail/mailname. As example :

athome.tld

File system check

Add this line to /etc/daily.local to check filesystems every day :

CHECKFILESYSTEMS=1

Read man 8 daily to learn more

The Firewall (pf)

The firewall? Already ?

Absolutely. You want to keep your fresh system safe, don't you?

We'll start with simple examples and suggest a few more for thoses interested.

On OpenBSD, the firewall is pf as in "packet filter". It is configured in "/etc/pf.conf". You will see, it's syntax is much easier than Linux's iptables.

First, you have to ask yourself what do you need. A web server (http)? Mail server? Depending on your answers, you won't open the same port number. We actually will close every port except the ones you really need.

To find the ports on which services listen, look at the contents of the "/etc/services" file. Thus, writing "www" or "80" is the same for pf, but is more readable for humans.

The rules you will define will be applied in the order they are written.

A first example

Ready? Let's go for a detailed but simple example. We are going to configure the firewall for a website with access on ports 80 (www) and 443 (https).

We start by blocking everything, and recording in the log the prohibited connections with the word "log" (obvious ๐Ÿ˜€).

block log

To make our life easier, we are going to put the ports to be opened in a variable. This will be particularly practical when there are more ports to manage.

tcp_pass = "{ 80 443 }"

Note that this is identical to 'tcp_pass = "{ www https }"'

Then, we allow potential visitors to access your server. These will be so-called "incoming" connections, so we will use the "in" keyword. Instead of looking for the name of your network interface (which depends on your hardware), we will specify the name of the group that these interfaces belongs to so that it works without worry: "egress".

pass in quick on egress proto tcp to port $tcp_pass

You're cute eh, but that's all gibberish!

Okay, we'll explain what this syntax means. We can translate the previous configuration line as follows: "let pass inward (pass in) without taking into account the rules that could follow (quick) through the egress interface (on egress) for the tcp protocol (proto tcp) to the ports in $tcp_pass (to port $tcp_pass).

Fiew! ๐Ÿ˜‹

Finally, we allow all the output traffic (keyword "out"):

pass out on egress

Finally :

tcp_pass = "{80,443}"
block log
pass in quick on egress proto tcp port $tcp_pass
pass out on egress all

Easy, right? ๐Ÿ˜

I suggest that you replace the last line with something a little more restrictive:

pass out on egress proto {tcp udp icmp ipv6-icmp} modulate state

We simply specify the protocols authorized on exit with a precaution for each outgoing connection (modulate state).

You will understand, pf allows us a first order control on our server. At first, you'll just want to choose which ports should be opened.

This makes a lot of new concepts, take the time to reread what has been written so far if necessary.

When you are more comfortable, know that we can filter incoming and outgoing traffic, and even redirect part of it to internal services (eg anti-spam). I invite you to read the part "going-further-with-pf". You will see how to ignore certain lists of IPs known as harmful that will be recorded in tables, rules against bruteforce attacks or even a proposed tool that adds potential hackers to the blacklist in real time.

I let you build your /etc/pf.conf file according to your needs. Start from the example seen above then add the ports to open. Do not hesitate to consult the example provided at the end of this document.

To be sure, can we have an example to open the SSH port?

If the configured SSH port is 22, then you will have in the pf configuration:

pass in quick on egress proto tcp to port 22

Of course, you can also just change the tcp_pass variable:

tcp_pass = "{80 443 22}"
pass in ...

Convenient commands for pf

Find below suggestions of useful commands to manage pf:

Go further with pf

pf-badhost: IP blacklist

Some IPs are known to be harmful. Between those that scan for an open port or those that attempt bruteforce attacks, the list goes on. Fortunately, we can configure the firewall so that it completely ignores these IPs and thus protect and lighten the load on our server.

This is what allows the pf-badhost project.

Author's blacklists

I keep up to date lists of IP who recentrly tried bad stuff on my server.

feel free to use them! ๐Ÿ˜‰

Bruteforce

One method hackers use to attack a server is called "bruteforce". Suck attacks consist of trying all possible username / password combinations until they find the correct one.

If you choosed strong passphrases, you should be safe. However, these attacks abuse resources on your server and can potentially succeed one day (admit that it would be bad luck).

In order to thwart these attacks, you should take the time to analyze the logs of the services you host (in "/var/log" and "/var/www/logs") for identification failures and notice where these attacks are coming from to banish the source. But let's be honest, it's both painful and time consuming.

Ideally, a tool that monitors logs for you and takes care of blacklisting "attacker" IPs on your firewall. Good thing, you can use vilain or sshguard which are made for that.

In the meantime, pf already integrates a protection which limits the number of connections on a port during a given time. It is very effective and remains light. Read the next paragraph ๐Ÿ˜‰.

Anti-Bruteforce integrated into pf

pf has a very interesting feature: "tables". This will allow us to remember certain IP addresses of hackers who would try to compromise the server. For example, to protect the SSH service, we will proceed as follows:

So that gives us this:

ssh_port = "22"
table <ssh_abuse> persist
block in log quick proto tcp from <ssh_abuse> to any port $ssh_port
pass in on egress proto tcp to any port $ssh_port flags S/SA keep state (max-src-conn-rate 3/60, overload <ssh_abuse> flush global)

Note that we have recorded the port number used for the SSH server. It is useless, because one can put in the place of "$ssh_port" simply "ssh", the name of the service. However, you may want to change the default port of the SSH server, as described in the chapter on this service.

Here is another example for a website (http and https ports):

http_ports = "{www https}" # ports http (s)
# If more than 40 connections every 5 seconds on the http (s) ports
# or if she tries to connect more than 100 times
# we add the ip to block it.
pass in on egress proto tcp to any port $http_ports flags S/SA keep state (max-src-conn 100, max-src-conn-rate 40/5, overload <http_abuse> flush global)

Anti-bruteforce in real time with vilain

I would like to offer you a tool that I wrote with the help of Vincent Delft in order to blacklist possible hackers when the attack takes place.

The script in question is called vilain.

Anti-bruteforce in real time with sshguard

Contrary to what its name suggests, sshguard is able to check connection attempts on multiple services, not just SSH.

Its installation in OpenBSD is not very complicated to set up. You will need to create a table in pf that will contain all the blacklisted IPs. Thus, we add in the /etc/pf.conf file a new table containing the IPs of the attackers who are immediately blocked:

table <sshguard> persist
block in from <sshguard>

Reload the firewall with pfctl -f /etc/pf.conf.

Now, we install sshguard as we usually do:

# pkg_add sshguard

Copy the configuration file given as an example:

# cp /usr/local/share/examples/sshguard/sshguard.conf.sample /etc/sshguard.conf

Edit this file if you want to make it more or less harsh. The default options are quite reasonable.

We can now enable and run sshguard:

# rcctl enable sshguard
# rcctl start sshguard

And there you go, your server is now safer.

You may want to whitelist some IP addresses with the "-w" option. Do this (this option can be used more than once).

rcctl set sshguard flags -w 192.168.1.0/24 -w 123.123.123.123

Trap with iblock

solene@ wrote a tool named iblock.

iblock ban every IP trying to access your server on ports that are actually unused.

This is definitely a must-have.

SSH: administer remotely

Configuring SSH

SSH is a great tool ๐Ÿ™‹. You can access to your server from another computer. You can then manage it remotely without having to connect a keyboard and screen. Truth be told, with some exceptions, all servers are administered this way, but you are at home after all, so you can do whatever you want.

SSH is a protocol useful for much more: encrypted tunnels, SFTP file storage space...

Anyway, if you didn't enable SSH when installing OpenBSD, you can enable it with the following command:

# rcctl enable sshd

Although this protocol is reliable, it does not cost anything to take some safety precautions. We will edit the SSH configuration in the /etc/ssh/sshd_config file.

PermitRootLogin no

To connect to the machine, you will need to create a simple user with the "adduser" command.

Once the changes have been made, restart the SSH daemon:

# rcctl reload sshd

Later, to connect to the server, you will use the following command from your computer (in a terminal, or with a client like putty under windows):

$ ssh toto@chezmoi.tld

And if you changed the default port:

$ ssh -p 222 toto@chezmoi.tld

It will ask for the password of the user toto, then you can administer the server remotely.

Login with keys (without password)

It is quite possible to connect in SSH without using a password. Instead, we use a pair of keys. This is a good idea at least for the following reasons:

However, this requires you to have the private key on the device used to connect, which is not always the case.

Here's the procedure to follow :

On the server, edit the /etc/ssh/sshd_config file so it contains this line:

PubkeyAuthentication yes

Now, on the computer that is used to access the server, we will generate a key pair with the following command:

ssh-keygen -t ed25519 -f ~/.ssh/sshkey -a 100

You will find two new files:

The ed25519 algorithm is suggested because it is very reliable at the moment.

Do not enter a password and then wait for the keys to be generated.

On an OpenBSD or Linux system, you will take care to edit your user's ~/.ssh/config file to fill it out as follows:

Host your_server
HostName verylong.reallylong.too.long
User myusername
Port 222
PasswordAuthentication no
IdentityFile ~/.ssh/sshkey

Of course, you will change the domain name of your server as well as the name of the user who must connect. Then run the following command to copy the public key to the server.

ssh-copy-id -p xxx -i ~/.ssh/sshkey.pub "myusername@verylong.reallylong.too.long"

Here, replace "xxx" with the port used by SSH if it's not 22.

If the ssh-copy-id tool is not available, you must copy the public key manually. To display it, type

$ cat ~/.ssh/sshkey.pub

Connect to the server in SSH, then add in the file ~/.ssh/authorized_keys the contents of the file displayed previously.

Once this is done, you can log in without having to enter a password with just the command:

$ ssh your_server

Convenient, isn't it?

If that works as expected, you can disable password identification for good so that only the keyset is left as possibilities. Edit the /etc/ssh/sshd_config file to contain this line:

PasswordAuthentication no

BE CAREFUL not to lose your set of keys!

Upload a file

You will definitely need to upload files to your server from time to time. Once SSH is configured, you will be able to use the scp command from any computer to copy a file to your server. For example :

$ scp -P <ssh port> user@athome.tld:/location/from/destination file-to-copy

It's very useful.

However, if you want to transfer many files, backup or store documents, consider SFTP instead, which has more features such as resuming a partial transfer.

If the files are too large, you can split them for multiple transfers.

SSHFP DNS records

The first time you connect to your server using SSH, the client get the fingerprint of the server. Next time, it compares the previous checkprint and display a warning if it has changed because there might be a problem if you didn't touch your server since. It's called Trust On First Use (TOFU -- not the food, sorry).

To avoid TOFU, you can publish data necessary to check the fingerprint the first time. To do so, we use a DNS SSHFP record.

Enter "ssh-keygen -r athome.tld" and copy the result in your DNS zone. Don't forget to adjust the entries with a final dot "." after domain name or replacing it with a "@" if necessary.

As example :

@ IN SSHFP 1 1 97f5a9479fd16fbee1381c425297f7b2773a67a4
@ IN SSHFP 1 2 bd50a3c271e83dc769ea7c249a5c07aea1a817c62910129de56eba2763f93607
@ IN SSHFP 2 1 05fb8516842fc270ea5abda38d1c32b41e75da8a
@ IN SSHFP 2 2 491489437e137d7d27959a4de3ba7660185ef98cbd3abc2287c82f82e8f032ab
@ IN SSHFP 3 1 b7305417b0e981c4513642020a1f57ee03915af5
@ IN SSHFP 3 2 f15d33852df5f4043c77b6bf1c6134a5e11de0a513c1ec127fcbb8fae733b178
@ IN SSHFP 4 1 a1120fe90e69c0f534fece3dc837db6a262c3371
@ IN SSHFP 4 2 a36e418b517d95a74f1e0be1228d46e7aeaa8ed310359e91162d8c40b373eb55

We'll talk about DNS a bit further ๐Ÿ˜‰.

In order the client check these records with the server fingerprint at first connection, add option "VerifyHostKeyDNS" to ssh. In "~/.ssh/config" :

Host *
    VerifyHostKeyDNS yes

Keep the system up to date

To ensure your system security, it is essential to keep it up to date. In order to apply the security patches, you must update:

Short ๐Ÿ˜‰:

# syspatch
# pkg_add -u

Update ports (packages)

Updating packages is as easy as :

# pkg_add -u

Yes that's all. ๐Ÿ˜

Thanks to solene who made this possible.

Since this paragraph is a bit short, I take this opportunity to give you two tips ๐Ÿ˜Š. I advise you to add to the /etc/daily.local file the command

pkg_add -nu

This way, in the daily mail from Charlie Root, you will see what might happen if upgrades for packages are availables. Nothing is done for real, because if an updated program needs to be restarted, it may not work until it is. Thus, you are just warned of any updates to apply, and with the -n option, the packages are kept in the cache in order to save time without risking to put a service down if it has to be restarted after the update. You will enter "pkg_add -u" when you get the chance.

If you still want to apply the updates automatically, I advise you to first install the "checkrestart" port which will tell you if a service must be restarted after the update. This will therefore look like this in /etc/daily.local:

pkg_add -u
echo "Service to restart with rcctl restart:"
checkrestart

You will still have to reload the services manually with rcctl.

Update the system

First of all, a little reminder: OpenBSD is available in 3 "flavors":

It can indeed happen that bugs are discovered. Each time, fixes are quickly proposed. It is then recommended to apply the security patches.

Since version 6.1, this operation is very simple with the following command:

# syspatch

The binary patches are then downloaded and installed. That's it! ๐Ÿ˜ It looked like this in OpenBSD 6.1 (a long time ago):

Get / Verify syspatch61-002_vmmfpu.tgz 100% | ******************************* | 9377 KB 00:49
Installing patch 002_vmmfpu
Get / Verify syspatch61-003_libress ... 100% | ******************************* | 11391 KB 00:22
Installing patch 003_libressl
...

This tool is only available for i386, amd64, and more recently arm64 architectures.

One may want to use the old method of source recovery and manual installation described in the official FAQ (for reasons).

Be notified of updates

To find out if updates need to be applied, you can consult the errata page which contains the list of available security patches. It is located at

"https://www.openbsd.org/errataXX.html" where "XX" is the version number of your release, i.e "70" for OpenBSD 7.0.

You can also be notified by email (and that's great ๐Ÿ˜Š). To receive important update messages available on the system, subscribe to the announce and security-announce lists. For that, send a first email to majordomo@OpenBSD.org simply containing:

subscribe announce

Then send a second message with:

subscribe security-announce

I also advise you to subscribe to the list indicating that there is a new version of ports by subscribing to the ports-security list by always sending to the same address a message containing:

subscribe ports-security

Upgrade from a version to another

When a new major version of OpenBSD is available, the update procedure is always detailed on the official website. You must read the release notes when upgrading at "https://www.openbsd.org/faq/upgradeXX.html" where "XX" must be replaced by the version number you want to upgrade to.

Since version 6.5, it only takes a simple command to update to the latest publication release or to -current:

# sysupgrade

ALWAYS check upgrade notes anyway ๐Ÿ˜„.

Clean up after multiple updates

If your installation is a bit outdated, you can check which files you may have forgotten to delete with the help of the "sysclean" port (command of the same name).

The files which are not supposed to be present in a base system will then be listed. Read the output carefully, most of them are configuration files you created and want to keep ๐Ÿ˜‰.

Backup

One can use its server to store important files.

In any case, you also must think to backup the server itself. You never know when the hard drive will crash, when a storm will generate a power overload or when your cat will rush trought the wires ๐Ÿ˜ผ.

Automatic backup of /

By default, you can backup / daily with OpenBSD

See The official FAQ about /altroot

If one day the main hard drive has a failure, you can boot on the /altroot. When you see the prompt "boot >" :

Find the /altroot partition

boot > machine diskinfo

Then boot on the appropriate slice :

boot > boot -s hd1a:/bsd

โš  WARNING : only / is saved, with /etc. If you need to backup /var or /usr/local or else, you must plan this. Read the next part to do so ๐Ÿ˜‰.

Read also the official FAQ to duplicate filesystems.

More backup

rsync

You may dedicate a partition to your backup, wether it's an external drive or an extra slice kept at OpenBSD install. Below, we use as example the mount point /mnt/bckp created for our needs.

We will use the "rsync" tool. You can install it throught ports, but take note there is "openrsync" already available in base install. It is a rewrite of rsync less powerful but at least avaiable in base. If the target device doesn't have rsync installed, add the option "--rsync-path=openrsync". The rsync protocol will detect modified or new files to copy and don't bother with the others.

As example, you can add those lines in /etc/weekly.local file to backup /var and /usr/local well, weekly :

/usr/local/bin/rsync -a --delete /var/ /mnt/bckp/var.bak/
/usr/local/bin/rsync -a --delete /usr/local/ /mnt/bckp/usrlocal.bak/

This is a local backup, but you can use rsync thought a SSH tunnel to send data from one computer to your server:

$ rsync -e "ssh" -avz --delete /directory/to/backup \
    brucewayne@athome.tld:/mnt/bckp

tar

Tar let you create archives of a whole directory structure, and is available in base of course.

Below is a suggestion to create a daily archive named with the current date.

BACKDIR="/mnt/bckp"
BACKLIST="/var
/home
/etc"
for i in $BACKLIST; do
    backupfile="${BACKDIR}/$(basename ${i})-$(date +%F).tar.gz"
	tar -czf "${backupfile}" "${i}"
done
chmod 700 "${BACKDIR}"
# remove olds
#find "${BACKDIR}" -type f -mtime +90 -delete

Uncomment the last line to delete achives older than 90 days.

SFTP : Secure file transfer

One can see SFTP like FTP over SSH. Of course, it's a bit more than that ๐Ÿ˜‰. It can continue partial transfer as example.

If you have an SSH access to your server, you can sftp ๐Ÿ˜„.

If you want to provide an SFTP server for multiple users, using a chroot might be helpful to keep everyone's data safe from others. We'll talk about it later.

sftp usage

To copy files with the user account "batman", start a new sftp session :

$ sftp batman@athome.tld

If you configured key authentication (good idea!), use -i flag :

$ sftp -i ~/.ssh/sshkey batman@athome.tld

Now you're in front of a prompt. You can enter commands such as :

And also cd, mkdir, quit and help to get a list of availables commands.

One can prefer to use a graphical tool to deal with sftp. Look at Filezilla on WinSCP for SFTP clients.

Also, most file manager can handle sftp protocol. Try to open a path like :

sftp://batman@athome.tld:<port_number>

At least Gnome, KDE and XFCE default file browser can do this.

However, if your file manager don't support SFTP, you still can use "sshfs" to mount a remote SFTP location as any other mountpoints.

$ sshfs -d batman@athome.tld:/remote/dir /home/batman/sftp \
    -o IdentityFile=/home/batman/.ssh/sshkey

chrooted SFTP

To share a storage space between users, you might want to lock them is a specific directory. Doing so, they can't reach other files above their own dedicated repertory. It is interesting to keep everyone's data out of sight of the others, but also avoir sftp users to reach system files. This is called "chroot".

In other words, if an user is chrooted in "/var/sftp/batman", he can't read "/var/sftp" nor "/var" and even less "/". for this user, "/var/sftp/batman" is the new root "/".

For the example, we'll chroot every sftp users in "/var/sftp/user_name". You'll need to make the appropriates directories. They will all belong to a group named "sftpusers" in order to automatically chroot them when they sftp.

Create this groupe :

# groupadd sftpusers

Then, edit "/etc/ssh/sshd_config" to chroot every user belonging to group sftpusers in "/var/sftp"

Match Group sftpusers
      ChrootDirectory /var/sftp/
      ForceCommand internal-sftp
      AllowTcpForwarding no

Note you can do the same for a specific user : you just use a section "Match User" instead.

If you wish users to authenticate using keys, then add to the above section :

PasswordAuthentication no

Reload ssh with "# rcctl reload sshd".

Now make appropriate directories with required permissions for the chroot (this is critical):

# mkdir -p /var/sftp
# chown root:wheel /var/sftp
# chmod 700 /var/sftp

Read the next part to see how to create sftp users and their own directories.

Add sftp users

Any regular account belonging to sftpusers group will match the above rule. You might consider to restrict even more the access to sftp users :

# install -d -o new_user -g new_user -m 700 "/var/sftp/home/new_user"

Now, new_user is chrooted in "/var/sftp/home/new_user" when he log in using sftp.

If you enable key authentication, then you'll have to fill the file "/home/user/.ssh/authorized_keys" with user's ssh public key. โš  I'm reffering to the file "/home/user/.ssh/authorized_keys", NOT "/var/sftp/home/user/.ssh/authorized_keys".

Keep your server on time (ntpd)

It is crucial to set your server's date and time correctly. Even if the hardware is supposed to keep track of time, it sometimes isn't reliable. However, one must avoid time shift or fear database corruption and unexpected behaviour.

OpenNTPD in OpenBSD's base installation will ensure your server is on time.

Install

Nothing to do ๐Ÿ˜ƒ, it's already there.

Just enable ntpd daemon and you're good to go :

# rcctl enable ntpd

Configure

Edit "/etc/ntpd.conf" file. You may copy the example at "/etc/examples/ntpd.conf"

# use a random selection of NTP Pool Time Servers           
# see http://support.ntp.org/bin/view/Servers/NTPPoolServers                                                            
servers pool.ntp.org          
# time server with excellent global adjacency               
server time.cloudflare.com    
# use all detected timedelta sensors                        
sensor *                     
# get the time constraint from a well-known HTTPS site      
constraint from "9.9.9.9"               # quad9 v4 without DNS 
constraint from "2620:fe::fe"           # quad9 v6 without DNS 
constraints from "www.openbsd.org"      # intentionally not 199.185.178.80 

Actually, default configuration is fine ๐Ÿ˜‰.

You just don't want to be a NTP server telling others what time it is, so let "listen on *" commented.

You choose a few public ntp server you "trust", then use every sensors availables. Finally, add some contraints from well known IP and end with a https website using it's domain name (a domain name resolution is required).

Finally, restart ntpd :

# rcctl restart ntpd

It's all good ๐Ÿ˜Š.

Host a website (httpd)

Host you own website is a great project ! ๐Ÿ˜„ It's the best way to have your personal space to do whatever you want without restrictions.

OpenBSD is shipped with "httpd", a http server. It is light and easy to configure. Most of the time, it is more than enough, but if you need even more features, look for "nginx" or "apache" in ports.

In this part, we'll see how to host a simple static website, then how to add PHP support to finally show some web applications examples.

Before going any further, take note that http is chrooted in "/var/www" by default.

What is that supposed to mean ?

To httpd, every file "above" "/var/www" can't be read. In other words, httpd see "/var/www" as the new root "/". For obvious security reasons, if httpd can't even reach system files, it reduce the risk that someone find sensitive data through a weak website.

Simple static website

A static website is just a few html files delivered by httpd. It's safe and fast. That's a good starting point. Let's see how to host you own website.

First, create a directory to store the website :

# mkdir /var/www/htdocs/my_website

Now, copy your website html pages and files in that directory (index.html...). Just make sure httpd is able to read those files by adjusting permissions with "chmod a+r /var/www/htdocs/my_website" or setting owner :

# chown -R www:daemon /var/www/htdocs/my_website

You might change permissions later.

Now you must configure httpd to add a new section for you website. If it is served on "http://athome.tld", you'll edit "/etc/httpd.conf" file so it looks like this :

types { include "/usr/share/misc/mime.types" }
server "athome.tld" {
	listen on * port 80
	root "/htdocs/my_website"
}

A few notes about the previous lines :

You can now enable and start httpd :

# rcctl enable httpd
# rcctl start httpd

Check http port (80) is open in your router and firewall, then check your website is reachable.

Every file you add in "/var/www/htdocs/my_website" will be served.

You can use SFTP previously described to upload your website ๐Ÿ˜‰.

Get a SSL certificate

SSL certificates allow to make TLS encryption and thus, in other things, serve your website through the more secure protocol HTTPS.

It is necessary when sensitive data is sent to/from your server.

Letsencrypt

"Let's Encrypt" is a free certificate authority providing easy to use API to get and manage certificates.

There is a client included in OpenBSD names "acme-client" to deal with such tasks. It currently requires a http server to generate certificates. That's why we'll discuss acme-client here. However, you certainly can use those certificates for other needs that https such as IMAPS.

โš  First of all, check you opened port 80 in the firewall and eventually in your router. Acme-client needs to check for some files you'll make available for letsencrypt with httpd.

Actually, acme-client will ensure you manage the domain you request a certificate for. To do so, it ask an unique file to Let's Encrypt, kinda fingerprint, stored in "/var/www/acme". Then, Let's Encrypt try to get this "secret" file on your server at ".well-known/acme-challenge", asking for "http://athome.tld/.well-known/acme-challenge/secret_file". If it succeeds, the request is accepted and you get a certificate. Finally the file is deleted.

Remember your website is probably located in "/var/www/htdocs/website" or something. You need to make "/var/www/acme" for letsencrypt to get the secret file. So, add a "location" instruction in "/etc/httpd.conf" :

server "athome.tld" {
    listen on * port 80
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
    root "/htdocs/website"
}

A few explanations :

โš  This section will have to be added for every domain served by httpd, even alternative names specified in the acme-client configuration file below. You might find easier to use "include" instruction in "httpd.conf" later as it is described in the "Tips" part a bit further ๐Ÿ˜‰.

Before calling acme-client, you must configure it by editing "/etc/acme-client.conf". You can use "/etc/examples/acme-client.conf" as template.

For the example, the file might look like this :

authority letsencrypt {
  api url "https://acme-v02.api.letsencrypt.org/directory"
  account key "/etc/acme/letsencrypt-privkey.pem"
}
authority letsencrypt-staging {
  api url "https://acme-staging-v02.api.letsencrypt.org/directory"
  account key "/etc/acme/letsencrypt-staging-privkey.pem"
}
domain athome.tld {
    alternative names { webmail.athome.tld www.athome.tld }
    domain key "/etc/ssl/private/athome.tld.key"
    domain full chain certificate "/etc/ssl/athome.tld.crt"
    sign with letsencrypt
}

Of course, replace "athome.tld", "alternative names { ..." with the other domains and subdomains you need and eventually the location of key and full chain certificate.

Make sure necessary directories are here (they might already exist) :

# mkdir -p -m 700 /etc/ssl/private
# mkdir -p -m 755 /var/www/acme

Check acme-client configuration with "# acme-client -n", it must return nothing.

Now you can get your certificates :

# acme-client -v athome.tld

At first, you might want to make sure everything works as expected using "sign with letsencrypt-staging" in "/etc/acme-client.conf" and switch back to "sign with letsencrypt" later. You can force getting certificates using "acme-client -F athome.tld".

Consider renewing certificates automatically using "/etc/weekly.local" file. It is a script executed every week.

/usr/sbin/acme-client -v athome.tld && /usr/sbin/rcctl reload httpd

Using "&&" make httpd to reload certificates only if there are renewed certificates. If at some point other daemons than httpd uses your certificates, you'll have to reload them too (relayd, dovecot...)

Now you've got a certificate, let's enable https in "/etc/httpd.conf" ๐Ÿ˜„. A new section "tls" appears. :

server "athome.tld" {
	listen on * port 80
	# http version
	# [...snip...]
}
# extrait de /etc/httpd.conf
server "athome.tld" {
    listen on * tls port 443
    root "/htdocs/website"
    tls {
        certificate "/etc/ssl/athome.tld.crt"
        key "/etc/ssl/private/athome.tld.key"
    }
    hsts
    # add your website specific configuration here
}

As you can see, now httpd listen on the port 443 (https) : remember to configure your firewall for this.

You might want to redirect clients accessing from http to https. To do so, add an instruction matching all request ("location *"). The clients will be redirected to the https version except if the request matched the previous one for acme-client.

# extrait de /etc/httpd.conf
server "athome.tld" {
    listen on * port 80
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
    location * { block return 301 "https://$SERVER_NAME$REQUEST_URI" }
}
server "athome.tld" {
    listen on * tls port 443
	#[...]

Remember to add such section for every website you want to serve. You may want to use "include" instructions to ease your life. See next part ๐Ÿ˜‰.

About "include" usage

You will certainly enable tls for multiple website. Instead of rewrite the same instructions again and again, you can include a file.

First, create a directory to keep multiple httpd configuration files :

# mkdir /etc/httpd.d

Then, we create "/etc/httpd.d/acme.conf" file to keep instructions related to acme seen earlier :

location "/.well-known/acme-challenge/*" {
    root "/acme"
    request strip 2
}

You even can go a bit further by creating a file "/etc/httpd.d/tls.conf" for multiples lines about certificates :

tls {
    certificate "/etc/ssl/athome.tld.crt"
    key "/etc/ssl/private/athome.tld.key"
}
hsts

Now, the file "/etc/httpd.conf" can be simplified to those lines below. The whole file let you serve 3 websites : athome.tld, www.athome.tld and webmail.athome.tld :

server "www.athome.tld" {
    listen on * tls port 443
    include "/etc/httpd.d/tls.conf"
    root "/htdocs/www.athome.tld"
}
server "www.athome.tld" {
    listen on * port 80
    include "/etc/httpd.d/acme.conf"
    location * { block return 301 "https://$SERVER_NAME$REQUEST_URI" }
}
server "athome.tld" {
    listen on * tls port 443
    include "/etc/httpd.d/tls.conf"
    block return 301 "https://www.$SERVER_NAME$REQUEST_URI"
}
server "athome.tld" {
    listen on * port 80
    include "/etc/httpd.d/acme.conf"
    location * { block return 301 "https://$SERVER_NAME$REQUEST_URI" }
}
server "webmail.athome.tld" {
    listen on * tls port 443
    include "/etc/httpd.d/tls.conf"
    root "/htdocs/webmail.athome.tld"
}
server "webmail.athome.tld" {
    listen on * port 80
    include "/etc/httpd.d/acme.conf"
    location * { block return 301 "https://$SERVER_NAME$REQUEST_URI" }
}

The above configuration redirect from athome.tld to www.athome.tld. Another website webmail.chez.tld is handled. For each domain, using acme is possible.

You may have noticed each section is quite a routine. You can imagine other usages to "include" instruction if you need it.

Thank you Grรฉgory Marchal for your suggestions on this part. ๐Ÿ˜‰

Facultative : CAA records

You can add a CAA record in your DNS zone to show you own the domain and YOU asked a certificate to Let's Encrypt. This is just a proof of honesty helping someone to trust the certificate used.

@ 300 IN   CAA   0 issue "letsencrypt.org"

Generate a self-signed certificate

You can generate your own self signed-certificate. At first connection, clients will see a warning asking if they trust or not the certificate.

To do so, see man 8 ssl in section "Generating RSA server certificates".

Check your server SSL configuration

Find below a few tools to check how well you've configured your server (not only https) :

https://www.ssllabs.com/

https://tls.imirhil.fr/

https://observatory.mozilla.org/

PHP

The very minimal PHP support

You may want to add PHP support to your website if you use a CMS as example.

Use OpenBSD's ports to install php (adjust the version number)

# pkg_add php-7.4.7

To see every PHP version availables :

# pkg_info -Q php

Then, enable and start php :

# rcctl enable php74_fpm
# rcctl start php74_fpm

Here, "74" suggest you installed PHP version 7.4.

Now edit httpd configuration so it send php files to the interpreter. In "/etc/httpd.conf" :

server "athome.tld" {
        listen on * port 80
        root "/htdocs/website"
        directory index index.php
        location "*.php*" {
                fastcgi socket "/run/php-fpm.sock"
        }
}

Notice "directory index index.php" instruction. By default, when someone reach "http://athome.tld/", it's like "http://athome.tld/index.php".

You're good to use PHP for most use cases.

PHP modules and chroot

For more complex softwares (Wiki, CMS...), you must enable some PHP extensions disabled by default. You must remember httpd is chrooted and can't see every files on the system for safety purposes.

Add PHP modules

If you read "/usr/local/share/doc/pkg-readmes" php file, you already know what to do ๐Ÿ˜‰. You have to add symlinks from "/etc/php-7.4.sample" to "/etc/php-7.4" (edit php version).

# cd /etc/php-7.4.sample
# for i in *; do ln -sf ../php-7.4.sample/$i ../php-7.4/; done
# rcctl restart php74_fpm

With main php package, most extension are already installed. You may add the following as they are quite common and useful :

Edit PHP configuration

Edit "/etc/php-7.4.ini". Below is an example of some useful changes :

; Increase the size of uploadable files
post_max_size = 10M
upload_max_filesize = 10M
; let php download remote content
allow_url_fopen = On
; Timezone
date.timezone = Europe/Paris
; Enable cache to avoid every page regeneration
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.enable_file_override=1

Configuration to deal with httpd's chroot

Your php pages may need to download remote content. Therefore, it must be able to do domain name resolution, check tls certificates and more. The necessary is stored in "/etc". However, if you remember, httpd is chrooted. Do you remember where ?

In /var/www !!!

Indeed Jean-Michel! Good to have you here. ๐Ÿ˜

We'll have to copy a few files usually stored in "/etc" to "/var/www/etc":

# cd /var/www     # go in the chroot 
# mkdir etc/      # create etc directory
# cp /etc/resolv.conf etc/resolv.conf # for Domain resolution       
# cp /etc/hosts etc/hosts # DN too
# cp /etc/localtime etc/localtime
# mkdir etc/ssl   # Create another directory for tls certs
# install -m 444 -o root -g bin /etc/ssl/cert.pem /etc/ssl/openssl.cnf /var/www/etc/ssl

Those files must be read only :

chmod -R 444 /var/www/etc/*
chmod -R a+X /var/www/etc/

Les fichiers copiรฉs servent notamment ร  :

Files in "/etc/ssl/*" must be updated periodically. Add in "/etc/monthly.local" :

install -m 444 -o root -g bin /etc/ssl/cert.pem /etc/ssl/openssl.cnf /var/www/etc/ssl

If you need PHP to send mails, you must copy "sh" in chroot (see "/usr/local/share/doc/pkg-readmes/femail-chroot*").

# cp /bin/sh /var/www/bin/

At last, reload php ๐Ÿ˜‰.

A few tips for httpd

man

You should read man "httpd.conf".

Really.

You'll find what's written here and more.

Logs

Choose where to store logs.

By default, they are in "/var/www/logs"

log access "website-name.log"

Disable logs with "no log".

gzip compression

Add "gzip-static" instruction in a domain configuration or a "location" section. Thus, httpd try to deliver the requested file with ".gz" suffix if it is present.

This is good for bandwidth, it can reduce 2x to 10x the weight to transfer. Compressoin ratio is good on text files (html, css, js, svg, ...)

To gzip a file before uploading on your server :

$ gzip -vk9 index.html
index.html:                49.5% -- replaced with index.html.gz
1395 bytes in, 733 bytes out

Custom error pages

Add "errdocs" intruction to tell which directory contains custom error pages. A generic "err.html" or multiple pages with error code as name can be used.

As example :

errdocs "/htdocs/athome.tld/err"

In /var/www/htdocs/athome.tld/err", there is the following "err.html" :

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/favicon.png" type="image/png" />
<style type="text/css">body, html {height:100%; margin:0}
#bg {
	position: relative;
	background-position: center;
	background-repeat: no-repeat;
	background-size: cover;
	background-image: url("/img/errimg.jpg");
	height: 100%;
	padding:0;
	margin:0;
}
#content {
	padding:1.5em;
}
</style>
<title>
$RESPONSE_CODE : $HTTP_ERROR
</title>
</head>
<body>
<div id="bg">
	<div id="content">
		<h1>Error page ๐Ÿ˜–</h1>
		<p>Sorry!</p>
	</div>
</div>
</body>
</html>

Improve disponibility

To increase the number of server processes and thus serve simultaneously content to multiple clients, increase the default value of 3 :

prefork 10

Publish with utf-8

If you try to serve plain text, the web browser may have trouble to display some glyphs except if you specifically set it to utf-8.

To avoid clients to look for this setup, you can explicitly send the appropriate header. However, the semicolon has to be escaped making the configuration a bit weird. Here is an example for ".txt" and ".gmi" file extensions :

types {
        text/"plain;charset=UTF-8" gmi
        text/"plain;charset=UTF-8" txt
}

Password restricted area

Create login credentials with "htpasswd":

# htpasswd /var/www/secret.htpw login

Replace "login" with any username and set a strong password.

Do it again to add more users.

Set appropriate permissions on this file :

# chown www /var/www/secret.htpw
# chmod 400 /var/www/secret.htpw

Finally, tell httpd to read this file for credentials and ask client to enter login + password when "/hidden_directory" is requested :

location "/hidden_directory/*" {
    authenticate "Restricted access" with "/secret.htpw"
}

"secret.htpw" location is relative to httpd's chroot.

For a whole website :

location "/*"

Or add "authenticate" instruction at the very beginning without any "location".

File index

To display a list of files availables in a directory if no "index.html" is found :

location "/dir/*" {
    directory auto index
}

Include configuration files

If you have many websites, you can include files in "/etc/httpd.conf" instead of writing again and again the same thing :

include "/etc/httpd/site1.conf"
include "/etc/httpd/site2.conf"

TLS

Add a "ticket session lifetime" to speed up TLS.

hsts preload
tls {
  certificate "/etc/ssl/athome.tld.crt"
  key "/etc/ssl/private/athome.tld.key"
  ticket lifetime default
}

How to set permission for website files ?

Setting appropriate permissions on files is mandatory to keep your data safe. You want to keep an eye on who can read some files or not, and even more carefully on files to execute or not.

For a website, there are no perfect solution, so make sure you understand the following advices and make your own choices.

You should set owner and group to "www" and "daemon" so httpd can read the files :

# chown -R www:daemon /var/www/htdocs/website

For a static website (no PHP), remove execution (-x) and writing (-w) permissions. You should let one change current path in these directories (+X).

# chmod -R a-xw /var/www/htdocs/website 
# chmod -R ug+X /var/www/htdocs/website

First, you remove x and w permissions to everyone, and then add to owner and group permission to chang into this directory.

For a dynamic website, you need to write in the directory and execute files in them :

# chmod -R a-xw /var/www/htdocs/website # remove all x and w permissions
# chmod -R u+xwX /var/www/htdocs/website # owner can xwX
# chmod -R g+rX /var/www/htdocs/website  # group can read

WARNING : you really should edit permissions more carefully to suit your website case.

For information, most commercial web hosters use the following permissions (not saying that's the best) :

To apply the previous permissions :

# chmod -R a-rwx /var/www/htdocs/site    # remove all perms
# chmod -R a+rX /var/www/htdocs          # everyone can read and cd in directories
# chmod -R u+w /var/www/htdocs           # only owner can write

Notice the "X" (not "x").

Relayd and headers

Because httpd can't manage headers itself, you can put relayd "before" httpd. It helps add headers or modify others.

As you can guess, relayd is included in base installation.

Visit this link to test your website headers.

Relayd configuration

Edit /etc/relayd.conf to configure relayd. Inside, add the following lines as example :

http protocol "http" {
  match request header remove "Proxy"
  match response header set "X-Xss-Protection" value "1; mode=block"
  return error
  pass
}
relay "www" {
  listen on 192.0.2.2 port 80
  protocol "http"
  forward to 127.0.0.1 port 80
}

Those lines means :

That's why you must edit httpd configuration accodingly :

# httpd's configuration
listen on localhost port 80

To sum up, things goes like this now :

1. A client ask to see your website and knocks at port 80.

2. relayd modify a few headers and redirect to httpd which still serve the website.

Dont forget to reload relayd and httpd:

# rcctl enable relayd
# rcctl restart httpd
# rcctl start relayd

Notice logs will show incoming connections from 127.0.0.1 (relayd local address). You may want to use "forwarded" log format for httpd :

log style forwarded

You can find a configuration example at the end of this documentation.

TLS / https

Find below an example to add TLS support to relayd. There are a few things to take care of about certificates and keys.

http protocol "https" {
  match request header remove "Proxy"
  match response header set "X-Xss-Protection" value "1; mode=block"
  return error
  pass
  tls keypair athome.tld
  tls keypair here.tld
}
relay "tlsforward" {
  listen on 192.0.2.2 port 443 tls
  protocol "https"
  forward with tls to 127.0.0.1 port 443
}

Look at the lines starting with "tls keypair". They define the certificate and keys to use for TLS. In the above example, two certificates for two different domains are used, you can add as much as you have certificates to use.

However, thos certificates MUST be stored with the appropriate filename in the correct location :

/etc/ssl/private/athome.tld.key
/etc/ssl/athome.tld.crt

Make sure /etc/ssl/athome.tld.crt is the "full chain".

This means you should have such configuration in acme-client configuration :

domain athome.tld {
    domain key "/etc/ssl/private/athome.tld.key"
    domain certificate "/etc/ssl/athome.tld-cert.crt"
    domain chain certificate "/etc/ssl/athome.tld-chain.crt"
    domain full chain certificate "/etc/ssl/athome.tld.crt"
    sign with letsencrypt
}

This way, relayd will automatically pick up the right certificate.

Renew certificates

Remember to reload relayd after certificate renewal :

/usr/sbin/acme-client -v athome.tld && \
    /usr/sbin/rcctl reload relayd

IPv6

If you want to add ipv6 support with relayd, it is obviously possible.

First, make sure you set the local address in "/etc/hosts" :

127.0.0.1 localhost
::1 localhost

You can now use "localhost" to refer to both ipv4 or ipv6 local depending on context.

Now add two entries in relayd configuration:

relay "http" {
        listen on $ext_ip4 port 80
        protocol "http"
        forward to 127.0.0.1 port 80
}
relay "http6" {
        listen on $ext_ip6 port 80
        protocol "http"
        forward to ::1 port 80
}

httpd's configuration will make use of "localhost" so it's simpler :

listen on localhost port 80

Deal with headers

Security headers

You can set a few headers to improve your website security. It's mostly useful if you host huge webapps, not really for static websites.

match request header remove "Proxy"
match response header set "X-Xss-Protection" value "1; mode=block"
match response header set "Frame-Options" value "SAMEORIGIN"
match response header set "X-Frame-Options" value "SAMEORIGIN"
match response header set "X-Robots-Tag" value "index,nofollow"
match response header set "X-Permitted-Cross-Domain-Policies" value "none"
match response header set "X-Download-Options" value "noopen"
match response header set "X-Content-Type-Options" value "nosniff"
match response header set "Permissions-Policy" value "interest-cohort=()"

If you only host one domain, add :

match response header set "Access-Control-Allow-Origin" value "athome.tld"

Learn more

Optimize client cache and bandwidth usage

You should consider to tune up th amount of request a client should make each time he checks on your website. As example, you can specify to keep in cache files such as pictures, stylesheets or fonts for a few days before asking again.

In "protocol" section, just before "pass" keyword, add :

match request path "/*.css" tag "CACHE"
match request path "/*.js" tag "CACHE"
match request path "/*.atom" tag "CACHE"
match request path "/*.rss" tag "CACHE"
match request path "/*.xml" tag "CACHE"
match request path "/*.jpg" tag "CACHE"
match request path "/*.png" tag "CACHE"
match request path "/*.svg" tag "CACHE"
match request path "/*.gif" tag "CACHE"
match request path "/*.ico" tag "CACHE"
match response tagged "CACHE" header set "Cache-Control" value "max-age=1814400"

Everytime a client ask for a file ending with ".css" or ".js" or ".atom" (...), relayd tag the resquest with "CACHE". At last we add a header to increase cache to 21 days to requests with this tag.

Set default encoding

Following the same scheme, you can specify the default encoding according to file extension :

match request path "/*.html" tag "UTF8"
match request path "*/" tag "UTF8"
match response tagged "UTF8" header set "Content-Type" value "text/html;charset=UTF-8"

About tags

Understand you can't set multiple tags at once. If you want to apply the two above headers (cache-control and content-type), you must do it separately since relayd configuration is processed in order :

match request path "/*.html" tag "CACHE"
match response tagged "CACHE" header set "Cache-Control" value "max-age=1814400"
match request path "/*.html" tag "UTF8"
match response tagged "UTF8" header set "Content-Type" value "text/html;charset=UTF-8"

Databases

Databases are essentials to organize data linked to each others.

As example, a blog engine needs to know who commented which article, at which date and by whom. This comment has a link, the author may have left a mail address to get an alert when there is an answer... All those are tied together.

When you self host, you may not need a huge database engine. In this scenario, SQLite is a better choise : easy to backup and light even if there are less features.

SQlite

SQlite is amazing.

It is easy and light. To backup, just copy a file. It is more than enough in mosts cases.

To install, just :

# pkg_add sqlite3.

To use with PHP, add "php-pdo_sqlite-*" and "php-sqlite3-*".

MariaDB (MySQL)

Very well known database engine, MySQL or MariaDB is often required in webapps. Make sure to understand how to secure your installation as it is a sensitive software.

Read "/usr/local/share/doc/pkg_readmes/*" related to mariadb install.

To use with PHP, install php-mysqli-* and php-pdo_mysql-* then enable extensions as explained in PHP's part.

Afin d'installer MariaDB, il faut lancer les commandes suivantes :

To install MariaDB :

# pkg_add mariadb-server
# /usr/local/bin/mysql_install_db

The second command install a default database.

To start mysql :

# rcctl enable mysqld
# rcctl start mysqld

Finally, use the following command to improve mysql safety :

# /usr/local/bin/mysql_secure_installation

To let httpd talk with MariaDB (it is chrooted), launch thos command to reproduce root structure with appropriate permissions :

# install -d -m 0711 -o _mysql -g _mysql /var/www/var/run/mysql

Add those lines to /etc/my.cnf to change MariaDB socket location so it is accessible to httpd :

[client]
    socket = /var/www/var/run/mysql/mysql.sock
[mysqld]
    socket = /var/www/var/run/mysql/mysql.sock

At last, restart mysql :

# rcctl restart mysqld

Now you can add users and databases.

Create a database

As example, we will show how to create a database for Wordpress.

Enter "mysql -u root -p" To get to the MariaDB shell. Below see a log of inputs and ouputs.

# mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.0.23-MariaDB-log openBSD port: mariadb-server-10.0.23p0v1
Copyright (c) 2000, 2015, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> CREATE DATABASE wordpress_base;
Query OK, 1 row affected (0.01 sec)
MariaDB [(none)]> CREATE USER 'wp'@'localhost' IDENTIFIED BY 'password';
Query OK, 0 rows affected (0.01 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON wordpress_base.* TO 'wp'@'localhost';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> exit
Bye

That's it ๐Ÿ˜Š.

PostgreSQL

PostgreSQL is another database engine. Install postgresql-server to use it.

With PHP, you'll need php-pgsql-* and php-pdo_pgsql-*.

Read /usr/local/share/doc/pkg-readmes/postgresql* carefully.

Create a default database :

# su - _postgresql
$ mkdir /var/postgresql/data
$ initdb -D /var/postgresql/data -U postgres -A scram-sha-256 -E UTF8 -W
$ exit

Default user is postgres.

Edit /var/postgresql/data/postgresql.conf to suit your needs.

To let httpd access postgresql, you should have :

unix_socket_directories = '/var/www/tmp/postgresql, /tmp'

You have to edit permissions on this directory :

# mkdir -p /var/www/tmp/postgresql
# chown _postgresql:www /var/www/tmp/postgresql

To start postgresql, as usual :

# rcctl enable postgresql
# rcctl start postgresql

To get the postgresql shell :

# su _postgresql -c psql

Below a few examples to dael with postgresql :

Change admin password :

# psql -U postgres -c "ALTER USER postgres WITH PASSWORD 'new_password'";

Add "toto" user :

# psql -U postgres -c "CREATE USER toto WITH PASSWORD 'password';"

Crรฉer une nouvelle base et donner ร  toto tous les droits dessus :

Create a new database and let toto do everything he wants with :

# psql -U postgres 
\connect template1
CREATE DATABASE "new_db" WITH ENCODING 'UTF-8';
GRANT ALL PRIVILEGES ON DATABASE "new_db" TO toto;
ALTER DATABASE "new_db" OWNER TO toto;
\q

Backup / Restore databases

With SQLite

Just copy the database file. That's it ๐Ÿ˜Š.

With MariaDB

To get a db-name dump :

# mysqldump -u root -p db-name > /var/backup/db_backup

Of course, edit user "root" and "db-name".

To restore the database :

Notice the "<" backwards.

With PostgreSQL

To save a database with postgresql, you actually save all the instructions to recreate the database :

# pg_dump db-name > /var/backup.db

To restore the base :

# psql -U postgres db-name < /var/backup.db

Webapps you can host

There are numbers of webapps : Wikis, Blogs, CMS, Webmails...

Most of the time, you'll need PHP and sometimes a database engine to run them.

โš  Make sure to check official documentation related to each webapps, and check release notes as long as you host these apps to keeps your install secure.

Most of the time, the steps to install a webapp are the same.

Nice webapp selection

There are so many webapps you may ask yourselves which one you should consider. Below I suggest a few of them with a few requirements :

One can appreciate :

Where can I find other apps to host ?

Look on alternativeto.

This github selection is well filled.

Webmail

Attachments size are 35M by default with smtpd mail server, that's why you should edit php-*.ini configuration :

post_max_size = 35M
upload_max_filesize = 35M

You have to set this limit in httpd's configuration too :

connection max request body 36700160

About hosting emails

To get in charge of your communication tools is a big deal to more freedom and independence. This part will describe how to host an email server at home. No database will be required because it has no benefit on a small server. Let's keep things simple !

Setting up a mail serer is often considered difficult. That's why we will split this tutorial in small easy steps : complex, but not complicated.

In order, you will :

Of course, you'll learn how to anew accounts and how to configure a mail client.

You first have to open port 25 (smtp) in your firewall and router.

Some ISP are very restrictive with this port. You can ask them to be more permissive, but make inquiries first to avoid bad surprises. At last resort, we'll discuss how to use external smtp serices.

Read also opensmtpd main developper article about this subject.

DNS configuration for an email server

Emails requires specific DNS records. You have add at least two new fields.

First, make sure you have an "A" record (probably) :

athome.tld.  IN  A  192.0.2.2

Of course, use your real IP here.

You must add a MX field pointing to the previous record :

athome.tld.  IN  MX  1  athome.tld.

Notice ending dots.

"1" describe the weight of the field. This parameter will be used to prioritize mails servers because some of them will be backups if the main server is unavailable. We'll talk about this later.

Some may want to use a specific domain, another A field to organize their mail servers. That is not mandatory, but interesting :

mail1.athome.tld. IN  MX  1  mail1.athome.tld..
mail1.athome.tld. IN  A      192.0.2.2

Below is another example using a different syntax :

$ORIGIN athome.tld
[...]
@      IN  MX  10  mail1.athome.tld.
@      IN  A       192.0.2.2
mail1  IN  A       192.0.2.3

Here, you can see the mail server for this zone is hosted on a different IP.

That's all for now.

Get certificates

To encrypt traffic with your email server, you'll need SSL certificates.

It is explained in the chapter about httpd.

However, you can use self-signed certificates if you prefer, that is absolutely not an issue. (seel man ssl(8))

In the next parts, we'll consider using letsencrypt certificates to keep consistent.

Just remember to hae a certificate for the domain in the MX field of your server.

A mail server in 10 minutes

I you're in a hurry, here is a quick setup to have a working email server. It will also be a good starting point to build on later. With the following :

Later, we'll discuss virtual users to fix these minor issues. But here, we're in a rush so let's go! ๐Ÿ˜Š

smtpd.conf file

Edit /etc/mail/smtpd.conf :

# Generic config
## Tables 
table aliases "/etc/mail/aliases"
## Certificates
pki athome.tld key "/etc/ssl/private/athome.tld.key"
pki athome.tld cert "/etc/ssl/athome.tld.crt"
### Get messages
listen on all tls pki athome.tld 
### To send with mail client
listen on all port submission tls-require pki athome.tld auth 
# ACTIONS
action "sendthismail" relay 
## System aliases
action local_maildir maildir alias <aliases>
## Deliver in maildir
action in_maildir maildir 
## Get
### From system to local users
match for local action local_maildir
### deliver remote messages for users if match the domain
match from any for domain "athome.tld" action in_maildir
## Sending
match for any action "sendthismail"
match auth from any for any action "sendthismail"

That's it ๐Ÿ˜Š

There a almost more comments than reals instructions.

Dovecot configuration (IMAP)

Install dovecot : "# pkg_add dovecot".

In "/etc/dovecot/local.conf" add :

# listen on IPV4 and IPV6.
listen = *, [::]
# We use Maildir
mail_location = maildir:~/Maildir
# imap > pop
protocols = imap
# Safety. Edit those lines !
ssl = yes
ssl_cert = </etc/ssl/athome.tld.crt
ssl_key = </etc/ssl/private/athome.tld.key
disable_plaintext_auth = yes
# auth methods
passdb {
        driver = bsdauth
}
userdb {
        driver = passwd
}

Reload dovecot : "# rcctl restart dovecot".

Here you go, it's done, you can configure your mail client.

Full mail server with virtual users

"Virtual users" means they are not UNIX system users with a shell. However, they are obviously using your mail server.

New accounts won't be created with "adduser" but you will edit a file with the username and it's password hash.

Here we'll discuss how to install a SMTP (Opensmtpd) and IMAP (Dovecot) server

One user to rule them all : _vmail

We will create an user with the sweet name "_vmail" to manage every emails access. It won't have any shell, it's safer ๐Ÿ˜Š.

# useradd -m -g =uid -c "Virtual Mail" -d /var/vmail -s /sbin/nologin _vmail

A new directory is created : /var/vmail. Emails will be stored inside, but following a structure with subdirectories for each virtual users. As example :

/var/vmail/athome.tld/batman/Maildir
/var/vmail/athome.tld/user/Maildir
/var/vmail/athome.tld/ninja/Maildir
...

/etc/mail/virtuals

In this file, we write user's emails, one on each line. Actually, it works like the file /etc/mail/aliases. (read man aliases(5) ๐Ÿ˜‰)

heroes@athome.tld batman@athome.tld,superman@athome.tld
batman@athome.tld _vmail
superman@athome.tld _vmail
kiki@athome.tld _vmail

As you can see, every email addresses belongs to user _vmail.

Notice the first line : you see an example showing how to transfer emails sent to "heroes@athome.tld" to "batman@athome.tld" and "superman@athome.tld".

Notez que sur la premiรจre ligne, on a fait un alias ร  titre d'exemple pour transfรฉrer un mail d'une adresse ร  une autre.

/etc/mail/passwd

Same as before, one line per passphrase :

batman@athome.tld:$2b$09$lerdFpdQtnu.Bs5EpAsVbeF851GjdD0aza8IDhho38i1DOHk.ujzi
superman@athome.tld:$2b$09$VRU/CYJUS3QZHVUFP70xIOURPbiNQeyOEZHoZo6NOY3uO.XSpd2MW

Notice how ":" split email address and the hashed passphrase : <email>:<hash>.

To hash passphrases, use "encrypt" command :

encrypt -p

Or "smtpctl encrypt passphrase".

(Facultative) Fine tuning files permissions

Previous files shouldn't be readable by everyone. Let's adjust permissions so root and mail daemons -- dovecot and smtpd -- can read passwords. It is not mandatory, but I suggest this part as good habits that can hurt anyone ๐Ÿ˜‰.

Dovecot and smtpd run as users _dovecot and _smtpd. If one day, one of this process is compromised, it will avoid privilege escalation and threaten the whole system.

Let's create a new group _maildaemons for _dovecot and _smtpd to ease permissions :

# groupadd _maildaemons
# usermod -G _maildaemons _smtpd
# usermod -G _maildaemons _dovecot

Of course, if you haven't installed dovecot yet since we discuss about it later : "# pkg_add dovecot".

Now we can set owner and group for files storing logins and passwords :

# chown root:_maildaemons /etc/mail/passwd /etc/mail/virtuals

Finally, only root can modify these files and _maildaemons can only read. Others can't do anything :

# chmod 640 /etc/mail/passwd /etc/mail/virtuals

To check everything is as expected :

# ls -l /etc/mail/passwd
-rw-r-----  1 root  _maildaemons  17226 Nov 12 08:40 /etc/mail/passwd

Opensmtpd (smtpd) config

Opensmtpd or smtpd is the default mail server shipped with OpenBSD. You just have to configure it.

First of all, make sure you opened thos ports : 25 (smtp), 587 (submission) and 993 (imaps). The latter will be user with dovecot. Do not care about 465 (smtps) since it is deprecated.

To configure smtpd, edit "/etc/mail/smtpd.conf". Instructions will be applied in order.

We will split it in 3 parts :

Adjust the example below to your needs :

# Generic configuration
## Tables 
table aliases "/etc/mail/aliases"
table passwd "/etc/mail/passwd"
table virtuals "/etc/mail/virtuals"
## Certificates
pki athome.tld key "/etc/ssl/private/athome.tld.key"
pki athome.tld cert "/etc/ssl/athome.tld.crt"
## Listening ports
### Reception
listen on all tls pki athome.tld 
### Sending with a mail client
listen on all port submission tls-require pki athome.tld auth <passwd> 
# ACTIONS 
action "sendthismail" relay 
action local_mail maildir alias <aliases>
action virtual_maildir maildir "/var/vmail/%{dest.domain:lowercase}/%{dest.user:lowercase}/Maildir" virtual <virtuals>
# In/Out
## Reception
### Message for virtual users
match from any for domain athome.tld action virtual_maildir
### Message for system users
match from any for local action local_mail
## Sending
match auth from any for any action "sendthismail"
match for any action "sendthismail"

There is almost nothing to change in this file, except domain name.

WAIIIIIT a minute! Tell me more!

Take a look at each lines :

First, there are generic instructions

Then, we define actions applied on envelopes.

At last, we apply actions according to envelope criterias.

If not mentioned, the rule is matching for a local mail. In other case, we add "from any".

Finally, in order to label your outgoing messages with the appropriate domain name, add in "/etc/mail/mailname" your email's domain name. It's the domain mentioned in the MX record.

athome.tld

Now enable and start smtpd :

# rcctl enable smtpd
# rcctl restart smtpd

That's all for ๐Ÿ˜Š.

Dovecot

We will use Dovecot as IMAP server so you can read mails from a client like Thunderbird.

As usual, install dovecot with pkg_add :

# pkg_add dovecot

Now configure dovecot. Edit "/etc/dovecot/local.conf" :

# listen IPv4 and IPv6
listen = *, [::]
# imap
protocols = imap
# Encryption. Edit those lines according to your certificates.
ssl = yes
ssl_cert = </etc/ssl/athome.tld.crt
ssl_key = </etc/ssl/private/athome.tld.key
disable_plaintext_auth = yes
# where mails are stored. %d is domain, %n is username
mail_location = maildir:/var/vmail/%d/%n/Maildir
# essential since we edited persmission on /etc/mail/passwd
service auth {
    user = $default_internal_user
    group = _maildaemons
}
# Auth methodes
passdb {
    args = scheme=blf-crypt /etc/mail/passwd
    driver = passwd-file
}
# Mails are /var/vmail , belongs to _vmail
userdb {
    driver = static
    args = uid=_vmail gid=_vmail home=/var/vmail/%d/%n/ 
}

I left comments above to help you understand what is done here.

Adjust ssl_cert and ssl_key according to your certificates.

By the way, ssl configuration is already available in "/etc/dovecot/conf.d/10-ssl.conf". It is supposed to help up with a script generating a self-signed certificate. However, you probably already have your own. You should comment lines in this file :

## /etc/dovecot/conf.d/10-ssl.conf
#ssl_cert = </etc/ssl/dovecotcert.pem
#ssl_key = </etc/ssl/private/dovecot.pem

Finally, reload mail daemons :

# rcctl enable dovecot
# rcctl start dovecot
# rcctl restart smtpd

Now you can use a mail client to read your messages.

Add a mail account

Add a new line for the new account in "/etc/mail/virtuals" and "/etc/mail/passwd" then reload tables for smtpd (dovecot read them on the fly) :

smtpctl update table virtuals
smtpctl update table passwd

Or "rcctl restart smtpd"

Manage multiple domains

You can host an email server and manage multiple domain names.

However, you should organize how you set this up.

Below are a few notes on how to achieve this.

smtpd

I suggest to create a file containing every hosted domain, one per line. Let's call il "/etc/mail/domains" :

athome.tld
domain.tld
other.bar

This, in "/etc/mail/smtpd.conf" you can write one line for multiple domains :

table domains "/etc/mail/domains"
...
match from any for domain <domains> action virtual_maildir

Take care of used TLS certificates. If you have a certificate for each domain, you can specify each of them in smtpd.conf. Make sure you have a default certificate in the end ("*").

pki athome.tld key "/etc/ssl/private/athome.tld.key"
pki athome.tld cert "/etc/ssl/athome.tld.crt"
pki domain.tld key "/etc/ssl/private/domain.tld.key"
pki domain.tld cert "/etc/ssl/domain.tld.crt"
pki other.bar key "/etc/ssl/private/other.bar.key"
pki other.bar cert "/etc/ssl/other.bar.crt"
pki "*" key "/etc/ssl/private/athome.tld.key"
pki "*" cert "/etc/ssl/athome.tld.crt"
...
listen on all tls
...
listen on all port submission tls-require auth <passwd>

HOWEVER, you can use only one certificate matching multiple domains. To do so, use "alternative names" in acme-client configuration. It is absolutely valid and much easier to manage. If so, configure smtpd as if there was only one certificate.

dovecot

Dovecot will need some care to handle certificates for each domain. Add sections "local_name" in its configuration so it looks like this :

ssl = yes
ssl_cert = </etc/ssl/athome.tld.crt
ssl_key = </etc/ssl/private/athome.tld.key
# no plaintext
disable_plaintext_auth = yes
local_name domain.tld {
        ssl_cert = </etc/ssl/domain.tld.crt
        ssl_key = </etc/ssl/private/domain.tld.key
}
local_name other.bar {
        ssl_cert = </etc/ssl/other.bar.crt
        ssl_key = </etc/ssl/private/other.bar.key
}

Here also, a single certificate for multiple domains is much more easier to set up.

Redirecting mails

As explained in man aliases(5), you can redirect mails to various destination.

As example, you host jdoe@athome.tld and want to redirect every mails received by jdoe to batman@wayne.com.

Edit /etc/mail/virtuals and add :

jdoe.athome.tld: batman@wayne.com

You can set redirections in /etc/mail/aliases too. For system users, you can use their login only :

root: jdoe
hostmaster: root
postmaster: root
webmaster:  bruce
jdoe: jdoe@domain.tld

Remember to restart smtpd so changes are considered :

# rcctl restart smtpd

Easy. ๐Ÿ˜Ž

How to configure you mail client ?

To use your mail server with a client such as Thunderbird, use the parameters below :

DNS record for clients (facultative)

To help clients finding automatically how to configure parameters to use your mail server, you can add specific DNS records.

_submission._tcp.athome.tld  86400   IN  SRV 0 1 587 athome.tld
_imap._tcp.athome.tld        86400   IN  SRV 0 0 0   .
_imaps._tcp.athome.tld       86400   IN  SRV 0 1 993 athome.tld

Read rfc6186 about these records.

Do not loose mails : MX fields and backup

If, for reasons, your server is offline for a few days or a week, mails could be lost. Normally, senders try again for a while if they couldn't deliver a message.

However, you can plan secondary mail servers to keep your messages if yours is unreachable. You only need :

It will keep messages for your server in queue and will deliver them as soon it gets back online.

On your side, add a new MX field in your DNS zone with a bigger weight. This field points to the secondary server :

@                     IN MX 10  athome.tld.
@                     IN MX 70  mail.friend.eu.

In the above example, your server is ligther (10) than the secondary (70). This means a sending server will try first to deliver on your server, then on the secondary if the first failed to answer.

On his side, your friend just has to add your domain as a backup. If he uses smtpd, then /etc/mail/smtpd.conf should look like this :

action relaybackup relay backup mx "athome.tld" 
...
match from any for domain athome.tld action relaybackup

Of course, you should return the favor ๐Ÿ˜‰.

Do not be considered as a spam (SPF, DKIM...)

Some servers could consider your mails as spams. There are a few proofs of good faith you can setup if possible. They are not mandatory. Keep in mind that most spams comes from most well known servers (gmail, I'm looking at you) so don't be too hard on yourselves.

Reverse DNS

Your ISP might let you configure a reverse DNS. As it suggests, a reverse DNS links your IP to your domain name.

Look in your ISP panel for reverse DNS, or ask them directly. They are responsible for this ๐Ÿ˜‰.

If you can't and reaaaallly want a rDNS, you could rent a VPN and get a dedicated IP with a rDNS configured by the VPN provider. But in most cases, following next steps should be sufficient.

SPF

SPF records show that only YOUR server is allowed to send mails for YOUR domain name. Since it's usually the server's admin who also deals with DNS record, it's a proof of good faith.

Add a DNS record of type SPF in your zone such as :

athome.tld.   SPF "v=spf1 a mx ~all"

Or use a TXT field if SPF is not available :

athome.tld. TXT "v=spf1 a mx ~all"

Above is a very simple example that works for most cases. Consider reading about SPF records if you want to fine-tune this record.

DKIM signing

With a private key, your server will sign outgoing emails. In DNS records, you will publish a public key to let recipient check if it matches the signature from your server.

Ahem... Say that again?

Here we go. We will generate a private and a public key.

Private key is used to sign mails. It is "private" because you must be the only ont able to add a signature to outgoind mails.

Public key displayed in DNS record -- viewable by all -- let one check signature authenticity. You can see it as a unique puzzle piece, the only one that can fit the puzzle.

We'll see two ways to sing outgoing messages : one with an smtpd extension, the other with dkimproxy. You also could to the same with rspamd, it is described later, following the same method to generate keys. Choose the one you prefer ๐Ÿ˜‰.

Create DKIM keys

Following commands below, you will create a directory for keys, set permissions on this folder and go inside before generating keys with openssl and set permissions on the private part :

# mkdir -p /etc/dkim/
# chmod 770 /etc/dkim/
# cd /etc/dkim/
# openssl genrsa -out private.key 2048               
# openssl rsa -in private.key -pubout -out public.key
# chmod 400 private.key

DNS records

Add a DKIM or TXT field so anyone can check signature on your messages match what is published in DNS records.

Mails will receive a flag "dkimpubkey" when signed, it is used to identify the signature in DNS record.

Replace "..." the content of the file "public.key" :

# cat /etc/dkim/public.key

The record will look like this :

dkimpubkey._domainkey    IN TXT    ( "v=DKIM1; k=rsa; t=s;p=v+Fb...vhP/oB")

Sign using opensmtpd-filter-dkimsign

Since smtpd has filters support, you can sign your messages with port opensmtpd-filter-dkimsign :

# pkg_add opensmtpd-filter-dkimsign

Make sure the script can read keys generated as seen before :

# chown -R _dkimsign:_dkimsign /etc/dkim/

In "/etc/mail/smtpd.conf", you add now a new filter with instructions on how to sign messages :

filter "dkimsign" proc-exec "filter-dkimsign \
    -d <domain> \
    -s <selector> \
    -k /etc/dkim/private.key" \
    user _dkimsign group _dkimsign

Replace "<domain>" with your domain name and "<selector>" by "dkimpubkey" : that's what we defined in the DNS field earlier.

Now, make sure outgoing mails are processed by this new filter. In "/etc/mail/smtpd.conf", the line for outgoing messages now look like this :

Enfin, il vous suffit de faire passer les mails sortants par ce filtre. On modifie la ligne correspondant ร  l'envoi des messages dans "/etc/mail/smtpd.conf":

listen on all port submission tls-require auth <passwd> filter "dkimsign"

Sign with dkimproxy

As an alternative, you can use dkimproxy if you prefer to sign mails.

As usual :

# pkg_add dkimproxy

Make sure dkimproxy can read keys :

# chown -R _dkimproxy:_dkimproxy /etc/dkim/

Now configure dkimproxy to match you configuration (domain and DNS selector) :

In "/etc/dkimproxy_out.conf" :

listen    127.0.0.1:10027
relay     127.0.0.1:10028
domain    athome.tld
signature dkim(c=relaxed)
signature domainkeys(c=nofws)
keyfile   /etc/dkim/private.key
selector  dkimpubkey

Of course, edit domain and selector according to your DNS record.

Now, tell smtpd to listen for incoming signed and to-send messages on port 10028. They will be tagged "DKIM". Then we can send mails with this tag. On the contrary, they are forwarded to dkimproxy on port 10027.

In /etc/mail/smtpd.conf :

listen on lo0 port 10028 tag DKIM   
...
match tag DKIM for any action "sendthismail"
match auth tag DKIM from any for any action "sendthismail"
...
action dkimproxy relay host smtp://127.0.0.1:10027 
...
match auth from any for any action dkimproxy
match for any action dkimproxy

Finally enable dkimproxy and restart smtpd :

# rcctl enable dkimproxy_out
# rcctl start dkimproxy_out
# rcctl restart smtpd

Check it works

Follow instructions on mail-tester.com. You'll send a mail to a randomized recipient and get a score :

../../../img/mail-tester-fr.png

You may read about dmarc and other advices if you want to fin tune your configuration. Remember your score is already better than most "big" provider. Last time I checked with a gmail address, I got 6.1/10... ๐Ÿ˜œ

Blocking ISP : use external SMTP

If your ISP restrict the use of smtp port (25), you can't send any email from your server. To fix this, you may :

However, you need another smtp provider. Put the necessary credentials to access this mail account in "/etc/mail/secrets" :

# echo "secret_id user:passphrase" > /etc/mail/secrets

Make sure permissions are appropriate, you don't want everyone to know your password :

# chmod 640 /etc/mail/secrets
# chown root:_smtpd /etc/mail/secrets

Then, edit "/etc/mail/smtpd.conf" so outgoing messages go throught external mail server :

...
table secrets "/etc/mail/secrets"
...
listen on all...
...
action "relay" relay host smtp+tls://secret_id@smtp.example.com \
      auth <secrets> mail-from "@athome.tld"
...
match from any for any action "relay"

Some details :

In the end, reload smtpd.

rcctl restart smtpd

Read the example in the end of smtpd manpage to learn more.

Avoid receiving spam : senderscore filter

Senderscore keeps a database of mail server reputation.

Havin a moderate reputation doesn't mean much, but having a bad one means the server already has sent spams before.

You can use a filter in smtpd to recognize as spam incoming mails according to senderscore.

Install opensmtpd-filter-senderscore port.

Then, edit /etc/mail/smtpd.conf to add a filter :

filter senderscore \
         proc-exec "filter-senderscore -junkBelow 70 -slowFactor 2000"
...
listen on all tls pki athome.tld filter { senderscore }

Avoid receiving spams : spamassassin

spamassassin is a well-known anti-spam. It is used by OpenBSD project with spamd for their mailing lists. Let's see how to use it with smtpd using tags since the setup is interesting if you want to understand how things works. You may instead want to use the filter opensmtpd-filter-spamassassin : install the port then read /usr/local/share/doc/pkg-readmes/opensmtpd-filter-spamassassin.

Install appropriate ports :

# pkg_add p5-Mail-SpamAssassin spampd

Run spamassassin daemons.

You may add a few flags to make sure it is running as we expect.

# rcctl enable spampd
# rcctl set spampd flags "--relayhost=127.0.0.1:10026"
# rcctl start spampd
# rcctl enable spamassassin
# rcctl start spamassassin

Every incoming mails now must be checked by spammassassin, which is listening on port 10025. Once they've been scanned, they will be delivered on port 10026 and tagged "SPAMASSASSIN" so we don't check them twice.

That's how it looks in "/etc/mail/smtpd.conf" :

...
# mails checked by spammassassin
listen on lo0 port 10026 tag SPAMASSASSIN
...
action spamassassin relay host smtp://127.0.0.1:10025 
...
# system mails
accept from local for local alias <aliases> deliver to maildir "~/Maildir"
# Mails scanned by SPAMASSASSIN
match tag SPAMASSASSIN from any for domain "athome.tld" action virtual_maildir
### not tagged mails, must be checked by spammassassin
match from any for domain "athome.tld" action spamassassin
...

Spams subject are modified by spammassassin.

You can automatically delete them, but it's a risk in case of false positive.

In order to save spams in a junk folder, modify smtpd's action so it checks for "X-spam" header :

# /etc/mail/smtpd.conf
action virtual_maildir maildir "/var/vmail/%{dest.domain:lowercase}/%{dest.user:lowercase}/Maildir" junk virtual <virtuals>

Avoid receiving spams : rspamd

Rspamd is a very complete mail filter. It is not only an anti-spam, but can handle greylisting, DKIM...

It is also very fast and efficient. If you want to use it, you should read it's official documentation. For now, let's see how to use it with OpenBSD's smtpd as an antispam and for DKIM.

Install

# pkg_add rspamd redis opensmtpd-filter-rspamd
# rcctl enable redis rspamd
# rcctl start redis rspamd

/etc/mail/smtpd.conf

Just add a new filter named "filter-rspamd" and use it for incoming messages.

filter rspamd proc-exec "filter-rspamd"
### filtre en reception
listen on all tls pki athome.tld \
    filter { rspamd }

DKIM with rspamd

Since rspamd can handle DKIM signatures, you won't have to configure dkimproxy or another tool.

Create keys as described before and make sure they belong to _rspamd group.

# chown -R _rspamd:_rspamd /etc/dkim/

Remember to edit your DNS zone ๐Ÿ˜‰.

Now create "/etc/rspamd/local.d/dkim_signing.conf" file :

# If true, username does not need to contain matching domain
allow_username_mismatch = true;
path = "/etc/dkim/private.key";
selector = "dkimpubkey";

Then add a few lines in "/etc/mail/smtpd.conf" to sign outgoing messages :

filter rspamd proc-exec "filter-rspamd"
### Send and DKIM sign with rspamd
listen on all port submission tls-require pki athome.tld auth \
    filter { rspamd }

Greylisting

Rspamd does greylisting by default. If you want to still use spamd instead, disable rspamd greylisting in file "/etc/rspamd/local.d/actions.conf" :

greylist = none;

And file /"etc/rspamd/local.d/greylist.conf" :

enabled = false;

Spamtraps

Contrairement ร  spamd, rspamd ne garde pas captif les spammeurs qui รฉcrivent sur une spamtrap. Cela sert tout de mรชme ร  reconnaรฎtre des spammeurs pour plus tard.

You can use a spamtrap (like what spamd does) using the following lines in "/etc/rspamd/local.d/spamtrap.conf" :

action = "no action";
learn_spam = true;
map = file://$LOCAL_CONFDIR/maps.d/spamtrap.map;
enabled = true;

Then fill with regular expressions of fake trap mail addresses : "/etc/rspamd/maps.d/spamtrap.map"

/^trap@athome.tld$/
/^fake@athome.tld$/

Blacklists

To do so, use multimap module.

See WebUI

Configure admin following these instructions

Then, dig a SSH tunnel from your computer and open in a browser http://localhost:9999.

ssh -N -L 9999:127.0.0.1:11334 sshuser@athome.tld

Enjoy wonderfull charts ๐Ÿ˜„

Manage spams with dovecot

You might want to help the anti-spam to learn if it missed spams or got wrong for some messages.

If you use dovecot and a mail client with IMAP protocol, you can move spams in "Junk" folder so the anti-spam recognize them in the future. On the contrary, moving a mail from "Junk" to "INBOX" folder indicates it is legitimate.

No matter if you use rspamd or spammassassin, we will see both. 3 steps are necessary :

1. Install dovecot plugin.

2. Enable and configure the plugin.

3. Create scripts so the anti-spam can learn.

It is described in dovecot documentation (you should read ๐Ÿ˜‰)

First, let's install dovecot pigeonhole plugin :

# pkg_add dovecot-pigeonhole

Add "imap_sieve" in plugins list to enable it at the end of "/etc/dovecot/local.conf" :

protocol imap {
	mail_plugins = $mail_plugins imap_sieve
}

Then, still in dovecot's local.conf, configure sieve plugin si it run scrits according to where messages are moved..

plugin {
	sieve_plugins = sieve_imapsieve sieve_extprograms
	# When a message is moved in Junk folder; report as spam
	imapsieve_mailbox1_name = Junk
	imapsieve_mailbox1_causes = COPY
	imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve
	# When a mail is removed from Junk folder : it is nor a spam 
	imapsieve_mailbox2_name = *
	imapsieve_mailbox2_from = Junk
	imapsieve_mailbox2_causes = COPY
	imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve
	sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
	sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
}

Now create two files in the directory "/usr/local/lib/dovecot/sieve/".

The first is "report-spam.sieve":

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
if environment :matches "imap.user" "*" {
  set "username" "${1}";
}
pipe :copy "learn-spam.sh" [ "${username}" ];

The second is "report-ham.sieve" :

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";
}
if string "${mailbox}" "Trash" {
  stop;
}
if environment :matches "imap.user" "*" {
  set "username" "${1}";
}
pipe :copy "learn-ham.sh" [ "${username}" ];

Now reload dovecot and compile the above scripts :

# rcctl restart dovecot
# sievec /usr/local/lib/dovecot/sieve/report-spam.sieve
# sievec /usr/local/lib/dovecot/sieve/report-ham.sieve

As you may have notices, the above files call "learn-spam.sh" and "learn-ham.sh". Thoses scripts will be different wether you use rspamd or spammassassin since they don't learn spams/hams with the same command. Read the next part ๐Ÿ˜‰.

Learning scripts.

Scripts below are stored in "/usr/local/lib/dovecot/sieve".

For spamassassin

#!/bin/sh
#learn-spam.sh
/usr/local/bin/spamc -u ${1} -L spam -C report
#!/bin/sh
# learn-ham.sh
/usr/loca/bin/spamc -u ${1} -L ham -C report

for rspamd

#!/bin/sh
#learn-spam.sh
exec /usr/local/bin/rspamc -d "${1}" learn_spam
#!/bin/sh
# learn-ham.sh
exec /usr/local/bin/rspamc -d "${1}" learn_ham

For both :

Scripts must be executables :

# chmod +x /usr/local/lib/dovecot/sieve/learn-*.sh

At last, reload dovecot:

# rcctl reload dovecot

Here you go, now your anti-spam will learn when you'll move messages in approriate folders.

Learn more with dovecot official documentation.

Fight against spams before receiving one : OpenBSD's spamd

You can sort incoming spams into a Junk folder. But imagine annoying spammers ๐Ÿ˜? That's what spamd can do, faking being a mail server and holding spammers so they can't deliver spams while it's talking to them veeery slowly.

How does it work ?

You can let somewhere on the web a fake mail address : a spamtrap. Everyone sending a message to this address will be blacklisted and trapped.

In the meantime, new senders are greylisted. They're told to try again later. Every proper email server will try again. Spammers won't, so you don't get the spam. Others who try again correctly are put on a whitelist.

There is also a blacklist-only mode, trapping IPs from lists you already have.

However, this means senders do things correctly. That's is sadly not always true for commercial senders. This said, it is easy to put a server on a whitelist.

To keep track of good and bad senders, the command "spamd-setup" should be run periodically.

Everything is very well explained in spamd(8) manpage. You must read it since the following are just advices, not a complete howto.

Setup

# rcctl enable spamd
# rcctl set spamd flags "-v -G 25:4:864"
# rcctl start spamd

Also enable spamlogd to keep track of servers already whitelisted and to add to whitelist servers you write to.

# rcctl enable spamlogd
# rcctl start spamlogd

spamd is running in front of smtpd (like a shield, or a janitor ๐Ÿ˜‰). To do so, edit packet-filter configuration : spamd should redirect mails to smtpd if it is not a spam.

Add in /etc/pf.conf :

table <spamd-white> persist
table <nospamd> persist file "/etc/mail/nospamd"
pass in on egress proto tcp to any port smtp divert-to 127.0.0.1 port spamd
pass in on egress proto tcp from <nospamd> to any port smtp
pass in log on egress proto tcp from <spamd-white> to any port smtp

On line order :

Remember to reload pf :

# pfctl -ef /etc/pf.conf

It is necessary to reload spamd blacklist in pf. Edit root's crontab with "# crontab -e" then uncomment the line below :

~  *  *  *  *  /usr/libexec/spamd-setup

With "~", spamd-setup is run at a random schedule ๐Ÿ˜‰.

Spamtrap

Let somewhere on the web fake email addresses : spamtraps. Everyone writing to these addresses is a spammer and will be trapped. It is important thos addresses don't really exist!

To keep these spamtraps for bots only, you can add such html snippets on your website so they are invisible for human eyes :

<a href="mailto:trap@athome.tld"></a>

You can copy it on public pastebins.

To teach spamd what are spamtraps, use the command :

# spamdb -T -a 'trap@athome.tld'

Use pre-existing black/white lists

Some nice people gather IP lists and let everyone download them. To use such lists, edit /etc/mail/spamd.conf.

As example, the default configuration uses :

nixspam's list

Add in /etc/mail/spamd.conf the following lines :

all:\
        :nixspam
# Nixspam recent sources list.
# Mirrored from http://www.heise.de/ix/nixspam
nixspam:\
        :black:\
        :msg="Your address %A is in the nixspam list\n\
        See http://www.heise.de/ix/nixspam/dnsbl_en/ for details":\
        :method=http:\
        :file=www.openbsd.org/spamd/nixspam.gz

At the beginning, you can read "all". Then are written lists you want to use and configured below, separated with ":".

Use an URL to download the list with spamd-setup automatically or a file already on your server.

bsdly's blacklist

Peter N.M.Hansteen publish a blacklist, gathered with its own spamtraps.

Avoid to download it too often or you'll be put on a blacklist. Be king with Peter's bandwidth ๐Ÿ˜‰.

To get the list every hour past 20 minutes :

20 * * * *   /usr/local/sbin/bsdly-spamd

Here is bsdly-spamd script you should copy and make executable :

#!/bin/sh
# update bsdly.net traplist into DST
URL="https://www.bsdly.net/~peter/bsdly.net.traplist"
# alternative URL
#URL="https://home.nuug.no/~peter/bsdly.net.traplist"
DST=/var/db/bsdly.traplist
ftp -o "${DST}" "${URL}"

Then add the new list in /etc/mail/spamd.conf so spamd uses /var/db/bsdly.traplist file :

all:\
      :nixspam:bsdlyblack:
nixspam:\
      :black:\
      :msg="Your address %A is in the nixspam list\n\
      See http://www.heise.de/ix/nixspam/dnsbl_en/ for details":\
      :method=https:\
      :file=www.openbsd.org/spamd/nixspam.gz
bsdlyblack:\
      :black:\
      :msg="SPAM.  Your address %A has sent spam within the last 24 hours.  See http://www.bsdly.net/~peter/traplist.shtml for details.":\
      :method=file:\
      :file=/var/db/bsdly.traplist

uceprotect black and whitelists

See official website.

Below we describe how to periodically download lists provided by uceprotect. We will use rsync to reduce bandwidth usage.

Add a new cronjob to call a script downloading lists. Not too often or you'll be blacklisted.

10 * * * *     /usr/local/sbin/uceprotect-spamd 

/usr/local/sbin/uceprotect-spamd is the script below :

#!/bin/sh
RSYNC="/usr/bin/openrsync -a"
URLS="rsync-mirrors.uceprotect.net::RBLDNSD-ALL/dnsbl-1.uceprotect.net
rsync-mirrors.uceprotect.net::RBLDNSD-ALL/dnsbl-2.uceprotect.net
rsync-mirrors.uceprotect.net::RBLDNSD-ALL/ips.whitelisted.org"
OUT="/var/db/RBLDNSD-ALL/"
mkdir -p "${OUT}"
for URL in ${URLS}; do
      ${RSYNC} "${URL}" "${OUT}"
done

Load the lises in /etc/mail/spamd.conf. There are white and blacklists.

all:\
      :nixspam:bgp-spamd:bsdlyblack:\
      rbldnsd-1:rbldnsd-2:rbldnsd-white:
...
rbldnsd-1:\
      :black:\
      :msg="Your address %A is listed on UCEPROTECT-Level 1\n \
      see http://www.uceprotect.net/en":\
      :method=file:\
      :file=/var/db/RBLDNSD-ALL/dnsbl-1.uceprotect.net
rbldnsd-2:\
      :black:\
      :msg="Your address %A is listed on UCEPROTECT-Level 2\n \
      see http://www.uceprotect.net/en":\
      :method=file:\
      :file=/var/db/RBLDNSD-ALL/dnsbl-2.uceprotect.net
rbldnsd-white:\
      :white:\
      :method=file:\
      :file=/var/db/RBLDNSD-ALL/ips.whitelisted.org

Do not hesitate do donate to uceprotect.net for all their work ๐Ÿ˜„.

Common issues with greylisting

Some big mail provider uses multiple servers to send mails. If you do greylisting, the IP trying to send an email from a provider might change between two tries. This means spamd can't recognize a legitimate server, never whitelist the IP and you never get the mail.

However, you can whitelists those domains if you want.

Create a file containing domains not to greylist : /etc/mail/nospamd_domains_list.txt :

gmail.com
hotmail.com
facebookmail.com
apple.com
microsoft.com
lists.openbsd.org
linkedin.com
freebsd.org
twitter.com
amazon.com
yahoo.com
yahoo.fr
live.fr
mail-out.ovh.net
mxb.ovh.net
gandi.net
laposte.net
protonmail.com

Add more domains if you need to.

Now create a script to get sending IP of above domains : /usr/local/sbin/generate-nospamd :

#!/bin/sh
# /usr/local/sbin/generate-nospamd
# Auteur :      prx <prx@si3t.ch>
# licence :     MIT
DOMAINS=/etc/mail/nospamd_domains_list.txt
WHITELIST=/etc/mail/nospamd
echo "#$(date)" > "$WHITELIST"
smtpctl spf walk < "${DOMAINS}" >> "$WHITELIST"
exit 0

Call this script daily with /etc/daily.local and reload nospamd table :

# /etc/daily.local
/usr/local/sbin/generate-nospamd
pfctl -t nospamd -T replace -f /etc/mail/nospamd

See spamd activity

Run "spamdb" command to see what's happening :

WHITE|62.4.1.33|||1462699174|1462699174|1465809574|1|0
GREY|182.70.43.24|abts-mum-dynamic-024.43.70.182.airtelbroadband.in|<Estella32@thunderguy.co.uk>|<toto@athome.tld>|1473409924|1473424324|1473424324|1|0
GREY|14.183.132.63|static.vnpt.vn|<Abby5@toddelliott.com>|<kiki@athome.tld>|1473410586|1473424986|1473424986|1|0

Read spamdb(8) manpage to learn what it means.

To translate times in human-readable format, run "date" with -r flag :

$ date -r 1462699174
    Sun May  8 11:19:34 CEST 2016

You can put on whitelist an IP with "spamdb -a" :

# spamdb -a "62.4.1.37"

Share spamd data between backups

Remember to keep spamd databases synchroniszd between your server and backups (secondary MX). Read about -Y and -y flags in spamd manpage.

Check everything works well

To make sure your mail server works as expected, you can send an email to friends. however, this leads to issues :

Luckily there are robots taht can automatically answer. As example :

Take a look at mxtoolbox and mail-tester too, the last one is very nice.

mxtoolbox

mail-tester

What if smtp server doesn't work as expected?

You should look at smtpctl command to get details about what's happening.

As example, to have generic status :

# smtpctl show stats
control.session=1
mda.envelope=0
mda.pending=0
mda.running=0
mda.user=0
mta.connector=1
mta.domain=1
mta.envelope=1
mta.host=2
mta.relay=1
mta.route=1
mta.session=1
mta.source=1
mta.task=1
mta.task.running=0
queue.evpcache.load.hit=1675
queue.evpcache.size=2
queue.evpcache.update.hit=6
scheduler.delivery.ok=826
scheduler.delivery.tempfail=6
scheduler.envelope=2
scheduler.envelope.incoming=0
scheduler.envelope.inflight=1
scheduler.ramqueue.envelope=2
scheduler.ramqueue.message=2
scheduler.ramqueue.update=0
smtp.session=0
smtp.session.inet4=787
smtp.session.local=31
smtp.tls=0
uptime=777861
uptime.human=9d4m21s

To see awaiting envelopes :

# smtpctl show queue
1101f6e60c169eac|inet4|mta||0100015b3a046476-f7d7955a-5842-49af-927c-4fa677f311c6-000000@bounces.duolingo.com|deux@domaine.eu|deux@domaine.eu|1491327053|1491672653|0|5|pending|3154|Network error on destination MXs
1a2123e1c2b3e462|inet4|mta||gitlab@framasoft.org|deux+framagit@domaine.eu|deux+framagit@domaine.eu|1491333449|1491679049|1491333849|1|inflight|50|Network error on destination MXs

To see in real time what's happening :

# smtpctl monitor

And of course; read "man smtpctl" ๐Ÿ˜‰.

Domain name server : fundamentals

DNS (Domain Name System), are road signs of the Internet. That's how a human can recognize server's names, like a big directory but for Internet.

It is also used to know whre a mail should be delivered, and many others things hidden to normal human beings but crucial to have a reliable network.

DNS is based on the idea of a "zone". First, you start at the root represented by a dot ".". The root list addresses of all authoritary name servers : tld (fr, de, com, net, org...). Each tld has a list of servers for the next levels.

That's how it looks like :

                     .
                     |
                     |
 +-------+-------+---+--+-------+-------+
 |       |       |      |       |       |
 v       v       v      v       v       v
.fr    .com    .tld   .xyz    .org    .net
                 |
   +-------------+--------------+
   |             |              |
   v             v              v
site.tld     athome.tld     other.tld
                 |
                 +------------------+
                 |                  |
                 v                  v
         webmail.athome.tld   wiki.athome.tld

If you start at the root, you can find the IP of any domain name. Searching through the zone is called "resolving". A server hosting data about a zone is "authoritary".

When a resolver retrieved a device IP, for a website or other purpose, it is kept in cache, in memory, as long as it is specified in the Time To Live (TTL) of the zone. So it is not reached too often.

This validity expiration time means there will be a delay before every resolver update a fresh zone. However, a bigger TTL means less network stress. It's about balance.

DNSSEC

A few years ago, clever people started worrying since DNS is critical. It had to be secured.

It is actually quite easy to be redirected on fake addresses. We make sure there is a way to ensure DNS authenticity.

To do so, domain name owner wite a zone and sign it. Resolvers check if the signature is correct.

Thus, you are certain to have reached the corret IP behind a domain name, not hijecked on a fake website.

One can publish through DNS safe data. That's the idea of DANE/TLSA to to publish fingerprints of TLS certificates.

Zone example

Zone files uses a standard format undestrood by every domain name servers.

Below is an example of "/var/nsd/zones/athome.tld" :

$TTL 1D
@           IN SOA    master.athome.tld. hostmaster.athome.tld. (
          ; domain of DNS server
          ; followed by admin email address
          ; Here : hostmaster@athome.tld
          ; "@" is replaced by a "."
                    2014110502      ; serial number to increment
                                    ; after each changes
                    86400           ; Refresh
                    7200            ; Retry
                    3600000         ; Expire
                    172800 )        ; Negative Cache TTL
$ORIGIN athome.tld.
@           IN NS       master
@           IN NS       secondary
@           IN MX       10 mail1
@           IN MX       20 mail2
master      IN A        192.0.2.2
master      IN AAAA     2001:db8:1:1::2
mail1       IN A        192.0.2.10
mail2       IN A        192.0.2.11
ipv4only    IN A        192.0.2.15
ipv6only    IN AAAA     2001:db8:1:1::400
dualstack   IN A        192.0.2.200
dualstack   IN AAAA     2001:db8:1:1::200
gate        IN AAAA    %%ipv6_gate
master      IN A       %%ip_pub_master
master      IN AAAA    %%ipv6_master
secondary  IN  A       %%ip_pub_second
secondary  IN  AAAA    %%ipv6_second
...

DNS records and how to use them

DNS records are usually written like this :

NAME    CLASS    TYPE    TTL    DATA(RDATA)

NAME is what you're looking for when you ask a resolver.

Class means internet (IN). At the beginning, other classe were used but not anymore.

Type is the king of data in the record.

TTL is Time To Live, delay while the data is considered as valid.

At last, RDATA is the data relative to the Name.

Types : @, $ORIGIN, $TTL

"$ORIGIN" is the complete zone name. As example, "athome.tld".

When a zone file don't have a "$ORIGIN" instruction, the domain name server will create one with the zone name.

"@" is replaced by "$ORIGIN".

Read this page if you want to learn more about this intruction.

"$TTL" is the validity duration of datas. It is recommended to set to 1 day (1D).

Each DNS record can have its own TTL if you want to reduce network stress for very stable fiels such as MX and NS.

"$ORIGIN" and "$TTL" must be written at the beginning of the zone file.

SOA

The first record with "$ORIGIN" and "$TTL" is called SOA as in Source Of Authority. It is crucial. The first field after SOA indicate the origin name server, in example master.athome.tld. and the last field indicates the domain administrator mail address. "hostmaster.athome.tld." will turn into "hostmaster@athome.tld". The first dot is turned into a "@". You may have set an alias on hostmaster when you configured the mail server. If you can't and have to use a dot before the "@" in the admin mail (you like it rough... ๐Ÿ˜ฒ), you have to write a "\" before :

john\.doe.athome.tld.

Autoritary domain name server and administrator mail may be on different servers or domains.

The serial number can have multiple formats, but always must increase everytime the zone is updated. Thus, secondary servers otice there is an update to download. Some admins use a timestamp, others start from 1 and increment each time. It's up to you.

SOA values (refresh, retry, expire, negative) and TTL in the example are those recommended in standard (RFC). Of course you can use your own values, but in doubt you have a robust example.

By default, values are seconds. You can write them as hours (H), days (D) or weeks (W).

Refresh et retry describe when secondary autoritative servers should reload the zone. Nowadays, most servers send alerts to secondary servers so it's automatic.

Expire tells how many time we can still use data in the zone if servers are unreachable. It is not TTL.

Negative is the delay a NXDOMAIN answer (non existent data requested) is kept in cache.

A, AAAA

It may be one of the most important record type. Like others, they follow this format : {NOM, TTL, CLASSE, TYPE, RDATA}.

Addresses of device master.athome.tld are recorded like this :

master          IN A        192.0.2.2
master          IN AAAA     2001:db8:1:1::2

Remember the last dot in addresses is very important. It represent the root zone. If a data doesn't end with a dot, it is expanded or will be buggy.

CNAME

CNAME, means Canonical NAME, it is an alias.

In the following example, the real name of www is master.

www IN CNAME master

It is used for virtual hosts such as blog.athome.tld, webmail.athome.tld, ...

NS

A DNS zone should have at least 2 NS records since they describe where are the authoritative servers. Actually, the zone will work with only one as long as the domain name server doesn't encounter any problem.

There is no NS servers maximum limit in theory. If you got two, that's a good start : yours and a friend's server.

athome.tld.   IN  NS   master.athome.tld.

This record means : for zone athome.tld, authoritative name server (NS) is master.athome.tld. It also can be written this way :

@     IN NS   master

In this case, the "@" is replaced by zone"s "$ORIGIN", actually its full name to the root, and master hasn't an ending dot so it is expanded with $ORIGIN too, : "master.athome.tld".

Name servers must be known to the rest of the world, you have to register then in the registrar, in the tld zone. This is actually one of the two only records you have to do in your registrar panel with DNSSEC keys records. Later, once authoritative server are published, everythings happens on your server.

When recording NS on the registrar, you generally give two fields : the full hostname of the server, master.athome.tld here, and its addresses. In this case, we talk about "Glue Record". How to know master.athome.tld even though it's it wo knows addresses for zone athome.tld? In this specific case, the address is written in the registrar.

To record authoritative servers on the registrar, you have to log on your registrar panel

With GANDI, it's quite easy (20% discount with this link).

You'll find a panel "GLUE" in your domain setup.

Once the new GLUE recorded, you can modify the name server list for your domain.

Read more about registrars and self-hosting with this article.

MX

MX, a little like NS, tells where is a king of service for the zone. In this case, where mail should be delivered. The record is build the same way as before :

@     IN MX 10   mail1
@     IN MX 20   mail2

For the zone athome.tld, the mails server (MX) is mail1.athome.tld.

The only significant differences is the "10", indicating the priority or "weight". When you have multiple servers, which one has priority to receive the mail? The one with the lowest weight.

If you self-host, you can choose to use the main domain name instead of a subdomain ith the same address in the A record, it is still valid:

@     IN MX 10   athome.tld.

MX and NS can't be redirections (CNAME), they must point to A or AAAA records, or in last resort to IP.

TXT

TXT records are used to publish various data about your server. It is useful to share public keys as example. It looks like this :

@    IN TXT    "v=spf1 a mx ~all"

Ending words

That's it for this long part about DNS. Do not hesitate to read again later, there are many things to understand here.

Of course, you can do so much more with DNS (SRV, SSHFP, ...), we'll describe eache case later if necessary.

Validating DNS resolver : unwind

Unwind is available in OpenBSD base install. It is able to do DNS resolution on your own devince instead of asking to your ISP or external provider. Results are kept in cache. This increase your server's performances and I strongly suggest to enable it๐Ÿ˜Ž.

Notice unwind only work locally and can't do resolution for other devices. To provide such feature, look at unbound.

To use unwind, enable is as usual :

# rcctl enable unwind
# rcctl start unwind

Edit "/etc/resolv.conf" file so your server asks unwind to resolve domain names. This is not necessary if you use DHCP.

nameserver 127.0.0.1

Here you go, your server now resolve domain names on its own.

You can try how well unwind works using dig command to see DNS requests results:

$ dig si3t.ch
[...]
;; Query time: 61 msec

61 ms were necessary to get an answer. Now try again :

$ dig si3t.ch
[...]
;; Query time: 0 msec

Yay, the address in in cache, speeding up future requests until TTL.

If you want to go further, read :

unwind(8) man

unwind.conf(5) man

unbound(8) man

Authoritative name server : nsd

You absolutely can host the authoritative server for your own zone, one step further to independence ๐Ÿ˜. That is exactly the purpose of NSD available in OpenBSD base install.

Configuring NSD

To configure nsd, edit "/var/nsd/etc/nsd.conf".

server:
        hide-version: yes
        zonesdir: "/var/nsd/zones"
        ip-address: 192.168.1.2
        ip-address: 2001:db8:1:1::2
        debug-mode: no
        verbosity: 2
## master zones
zone:
        name: "athome.tld"
        zonefile: "master/athome.tld"

We will follow this structure : zones are categorized wether the server is authoritative (master) or secondary (slave). Each zone is a file with the name of the corresponding domain. As example "/var/nsd/zones/master/athome.tld". Or cours you can choose to do things differently i fyou prefer.

Do not forget to adjust the above example with your own IP.

Once setup, start nsd as usual :

rcctl enable nsd
rcctl start nsd

Remember to open (and redirect) port 53 (domain) UDP and TCP since it's used by nsd.

Once your nsd is up, you can set in the registrar your server's public ip in the list of authoritative servers for your zone.

An nsd example is available at the end of this documentation.

Sign a domain with DNSSEC

A few notes about DNS zone signing

DNSSEC requires two king of keys : ZSK (Zone Signing Key) light and short time living, and KSK (Key Signing Key) bigger with a long time usage.

Why such differences ?

It is recommended to renew KSK regularly -- about monthly, some do it weekly. However, it is quite annoying to update this monthly in the registrar.

That's why we created another kind of key to sign the firsts, but used only for this task. Those should be stronger, heavier since they are less used with a longer lifetime. Those will be recorded in the registrar.

Actually, we won't record KS in the registrar but a checksum instead in the upper zone. It's the same idea with each zone publishing their NS in the upper zone.

Keep in mind changes won't propagate instantly. There is TTL, lifetime and validity of keys and signatures, so you can't expect to see things working right away.

Especially, You must publish early keys that will be used in the near future. That's why many administrators have 2 ZSK in the meantime : one currently used and another that will be used when the first has expired. That's what we'll do below. KSK are also published in advande, however rarely two in the same time.

Setup with ldnscripts

Signing your DNS zone ensure its integrity.

You have to do it periodically since signature have an expiration date. You'll need a tool to sign and a bit of automation.

We will describe how to use ldnscripts designed for this task (thanks to 22decembre).

Note there are tools more complete/complicated such as OpenDNSSEC or KNOT.

With ldnscripts, you'll only need the package "ldns" :

# pkg_add ldns-utils

Automation relay on tools already available on OpenBSD, I mean scripts /etc/weekly, /etc/monthly...

Let's follow ldnscripts' author's intructions :

$ cd /tmp
$ ftp https://framagit.org/22decembre/ldnscripts/-/archive/master/ldnscripts-master.tar.gz
$ tar xvzf ldnscripts-master.tar.gz
$ cd ldnscripts-master* 
# make install

Configure ldnscripts in the file "/etc/ns/ldnscripts.conf". There is an example in the previous archive. Configuratoin should look like this :

# repository where to find unsigned zone file and specific conf
NS_REP=/etc/ns
# serial : unixtime
SERIAL=unixtime
# algorithm to use. They are listed : ldns-keygen -a list
ALG=ECDSAP384SHA384
# length of the zsk
ZSK_BITS=1024
KSK_BITS=2048
# validity of signatures in days
VALIDITY=9
#NSEC3
NSEC3_ALG=SHA-384
RUN=24
# Verbose - set to 1 if needed
VERBOSE=1

If you want, you can set a configuration file per domain : they should be named "/etc/ns/domain-name.tld.conf".

That's it for configuration ๐Ÿ˜Š

To actually start using ldnscript, run with "init" parameter to create everything required : first keys, directories...

# ldnscript init athome.tld

You can read such output (below, VERBOSE is enabled):

This script will initialize your zone athome.tld with the general configuration or the
one set at /etc/ns/athome.tld.conf.
If you are not happy with it, modify your configuration (by copying the conf file to /etc/ns/athome.tld.conf and then editing it) and run this script again.
The script will create one KSK and two ZSK and finally sign the zone (which will triger a reload of your NSD server on the athome.tld zone).
The key Kathome.tld.+010+25115 has been generated with these arguments : -a RSASHA512 -b 1024 athome.tld
The key Kathome.tld.+010+34655 has been generated with these arguments : -a RSASHA512 -b 1024 athome.tld
The key Kathome.tld.+010+12321 has been generated with these arguments : -k -a RSASHA512 -b 2048 athome.tld
A new KSK key has been generated.
Make sure to update the DS record in your registrar quickly.
The key is Kathome.tld.+010+12321
DS content : 
athome.tld. IN      DS      12321 10 2 f6f91afd522680a3c459e1956e75f8eda078f99b8cf07114f0d299161bff0145
create a complete zone file for athome.tld with the current time as SOA
Signing zone
Zone is verified and complete

A directory "/var/ldnscripts/athome.tld/" is created, containing ZSK and KSK.

Signed zone file is in /var/nsd/zones/signed/athome.tld. Adjust nsd configuration to serve it :

server:
	zonesdir: "/var/nsd/zones"
	...
## master zones
zone:
    name: "athome.tld"
    zonefile: "signed/athome.tld"

Notice nsd is a static server, that's why ldnscript will reload it's configuration.

Warning, you must publish in your registrar public keys you can find in "/var/ldnscript/athome.tld/ksk" with ".key" extension. Depending on your registrar, you may have to publish DS records in ".ds" files (not at GANDI).

It's not over yet, we will set up a signing process with key renewal when necessary. Everything is ready, don't worry ๐Ÿ˜Š.

ldnscript offers those actions :

Let's do this. First, edit "/etc/monthly.local" to add the rollover :

/usr/local/sbin/ldnscript rollover all

Then, make sure signatures are renewes before the end of VALIDITY parameter in configuration file. Above, we set 9 days so you can sign each week 2 day early. Edit "/etc/weekly.local" :

/usr/local/sbin/ldnscript sign all

Finally, every year, you will create a new KSK :

/usr/local/sbin/ldnscript ksk athome.tld

To remember, you can add a new crontab to send you a message every May the 2nd:

# crontab -e
0 0 2 5 * echo 'renew ksk' | mail -s "KSK ALERT" root

Remember to publish this new key in the registrar. The script rollover will delete the old key automatically. At this point, you may remove its record in the registrar.

That's it, enough!. Everything esle is handled by ldnscript, si it requires some efforts only the first time.

Notice in the example, you deal with multiples zones automatically.

Add or remove keys in the registrar

When you renew KSKs, you must add in your registrar the public key. Make sure to do it early enough si it is correctly propagated before the old one is dismissed.

Ldnscript will display the number of the new key (keytag) and its checksum (DS) useful to record it in the registrar.

The public key is located at "/var/ldnscripts/zones/athome.tld/ksk/Kathome.tld*.key".

Once a key is dismissed, you can delete the previous DS record.

Add a secondary DNS server

Sometimes called "slave", a secondary server (NS) can serve your zone in case the primary (master) is unreachable for reasons : network unreachable, NSD daemon crashed, meteor shower, frog invasion or worse : pancakes shortage.

You could ask a friend with a domain name server (not necessarily OpenBSD nor NSD either, even if it's what we describe here) to host your zone as a slave on your domain. You can even install a secondary server on your own -- on a VM rented at openbsd.amsterdam as example ๐Ÿ˜Š. It's important that the secondary server is in another network, and even the same area : a few hundreds of kilometers are a good plan to mitigate power outage or network issues ๐Ÿ˜‰.

Note you can absolutely set up secondary servers for your friend's domain, being mutually the slave of the other's domain name server.

We will describe both side of the picture. You could be administrator of master server, secondary server or both at the same time.

First, we prepare a little bit of authentication.

Yes, I know, it's hard work. But we want to do things right ๐Ÿ˜‰? Here, we ensure our zone is updated by a legitimate server.

We will create a shared secret key, identical on both primary and secondary.

WIP \_o>

We use ldns-keygen command available in ldns-utils package you already have installed if you followed the previous part. It will create a keypair with a "secret" code inside :

$ cd /tmp
$ ldns-keygen -a hmac-sha256 -b 160 athome.tld

You have two new files, display the content of the private key :

$ cat Kathome.tld.+159+54791.private
Private-key-format: v1.2
Algorithm: 159 (HMAC_SHA256)
Key: H8D/Ka9RerEtmC0jN5hSQeATxNI=

Copy "Key" string in nsd configuration for master and slave servers in a "secret:" parameter. Remember to adjust with the proper algorithm:

key:
        name: "transfer"
        algorithm: hmac-sha256
        secret: "H8D/Ka9RerEtmC0jN5hSQeATxNI="

The "name:" parameter -- "transfer" here -- doesn't matter. It's just a mark.

You can delete previously created files in /tmp now.

On the master server :

In the configuration of the domain name server, you add two lines to alert the slave server so it retrieve the zone. For this purpose, you add instructions "notify" and "provide-xfr" :

# master zone 
zone:
       name: "athome.tld"
       zonefile: "signed/athome.tld"  
       notify: 192.0.2.1 transfer
       provide-xfr: 192.0.2.1 transfer
key:
        name: "transfer"
        algorithm: hmac-sha256
        secret: "H8D/Ka9RerEtmC0jN5hSQeATxNI="

Everytime you update the zone on your primary domain name server, it will alert the server on the address 192.0.2.1 so the latter update the zone.

Many online services -- such as registrars -- offer to host your zone as slaves for free. In this case, it is not necessary to notify : servers get the zone on schedule. Keep the "provide" without authentication : NOKEY in place of the key name.

To see how to use secondary server of GANDI, follow this link.

You can find GANDI's slave server :

$ dig ns6.gandi.net +short
217.70.177.40

Now add in your zone and in the registrar the secondary servers for your zone (NS) :

$ORIGIN athome.tld.
$TTL 86400
@           IN SOA    master.athome.tld. hostmaster.athome.tld. (
                        2014110502      ;
                        86400           ; refresh
                        7200            ; retry
                        3600000         ; expire
                        172800 )        ; negative
@               IN NS       master.athome.tld.
@               IN NS       secondairy.athome.tld.
@               IN NS       other.domain.tld.    ; a friend is
                                                 ; helping
maitre          IN A        192.0.2.2
maitre          IN AAAA     2001:db8:1:1::2
secondaire      IN A        192.0.2.3

On the secondary server now:

Just add a bit of configuration for nsd:

# slave zone 
zone:
       name: "athome.tld"
       zonefile: "slave/athome.tld"
       allow-notify: 192.0.2.2 transfer
       request-xfr: 192.0.2.2 transfer
key:
        name: "transfer"
        algorithm: hmac-sha256
        secret: "H8D/Ka9RerEtmC0jN5hSQeATxNI="

Notice both transfer keys share exactly the same configuration on each server.

Zones files should stay untouched on the secondary server. They will be updated automatically when needed. Edit the nsd configuration if you need to change their location : zones will be fetched again.

You can check if zones are in sync with "dig":

$ dig -q @master.athome.tld athome.tld
$ 192.0.2.10
โ€ฆ
$ dig -q @secondairy.athome.tld athome.tld
$ 192.0.2.10

Make sure you added secondary servers in the name servers lists in your registrar.

Check your zone works as expected

You may use the following links to check if your zone is correctly propagating over the world, has no errors and if DNSSEC is correct :

Check your zone

Zone check and various tests

Is it propagating ?

DNSSEC checks

Visual and complete checks

Complete and free example with nic.eu.org

nic.eu.org offer caee domain names ending with "eu.org".

We'll see how to set up a zone with this registrar.

First, check availables domains open for registration then choose one you like

For the example, we'll use "athome.tld.ca.eu.org"

Create the domain zone. Since we'll use "ldnscripts" later to enable DNSSEC, we write it in "/etc/ns/athome.tld.ca.eu.org" :

$TTL 1D
$ORIGIN chezmoi.ca.eu.org.
@       IN SOA ns1.chezmoi.ca.eu.org. batman.athome.tld. (
                              2017111301
                              1D
                              2H
                              2W
                              2D )
@                             IN NS     ns1.chezmoi.ca.eu.org.
@                             IN A      192.0.2.2
@                             IN AAAA   2001:db8:1:1::2
ns1                           IN A      192.0.2.2
ns1                           IN AAAA   2001:db8:1:1::2
ns2                           IN A      192.0.2.3

This is a pretty simple zone with two name servers, "ns1" and "ns2", the latter only availiable on IPV4.

We add a new section in nsd for "nsd1" :

# cat /var/nsd/etc/nsd.conf
key:
    name: "transfert"
    algorithm: hmac-sha256
    secret: "Hsd/Ka9RerEtmC0jsd5d5eATxNI="
zone:
    name: "athome.tld.ca.eu.org"
    zonefile: "signed/athome.tld.ca.eu.org"
    provide-xca: 192.0.2.3 transfert
    notify: 192.0.2.3 transfert

Do the same on secondary server "ns2" :

# cat /var/nsd/etc/nsd.conf
key:
  name: "transfert"
  algorithm: hmac-sha256
  secret: "Hsd/Ka9RerEtmC0jsd5d5eATxNI="
zone:
  name: "athome.tld.ca.eu.org"
  zonefile: "slave/athome.tld.ca.eu.org"
  allow-notify: 192.0.2.3 transfert
  request-xca: 192.0.2.3 transfert

reload nsd :

# rcctl reload nsd

Enable the zone with ldnscripts and prepare for DNSSEC :

# ldnscript init athome.tld.ca.eu.org

That's ready. Now it is served, you can register the domain.

Create an annount on nic.eu.org and connect.

Choose to create a New Domain.

Fill the fields with the full domain name you want to register and data about you.

example niceuorg 1

Then, fill "Name server" section. You must link the domain name and NS servers for the zone. The zone should already be managed by the servers. You can use IPV4 and IPV6 both. In other words, fill with the "NS" records in your zone.

example niceuorg 2

At last, after validating, you may read something like that if everything works as expected :

  ---- Servers and domain names check
  Accepted IP for NS1.CHEZMOI.CA.EU.ORG: 2001:db8:1:1::2 192.0.2.2
  Accepted IP for NS2.CHEZMOI.CA.EU.ORG: 192.0.2.3
  ---- Checking SOA records for chezmoi.ca.eu.org
  SOA caom NS1.CHEZMOI.CA.EU.ORG at 2001:db8:1:1::2: serial
  2019100702 (21.005 ms)
  SOA caom NS1.CHEZMOI.CA.EU.ORG at 192.0.2.2: serial 2019100702 (6.006 ms)
  SOA caom NS2.CHEZMOI.CA.EU.ORG at 192.0.2.3: serial
  2019100702 (73.715 ms)
  ---- Checking NS records for chezmoi.ca.eu.org
  NS caom NS1.CHEZMOI.CA.EU.ORG at 2001:db8:1:1::2: ok (20.674 ms)
  NS caom NS1.CHEZMOI.CA.EU.ORG at 192.0.2.2: ok (5.953 ms)
  NS caom NS2.CHEZMOI.CA.EU.ORG at 192.0.2.3: ok (65.559 ms)
  No error, storing for validation...
  Saved as request 20191007195509-arf-42318
  Done

Check your emails. You soon will get a configmation message about your registration.

Once it's done, you can enable DNSSEC in the dedicated panel.

Copy the DS record in nic.eu.org field :

cat /var/ldnscript/athome.tld.ca.eu.org/ds

Now you can check dnssec is correctly enabled.

Virtualization

It is realy easy to virtualize an operating system wit OpenBSD thanks to vmd.

Well, that's nice, but what virtualizing means ?

Instead of installing different OS on different computers, you can install an OS inside your current OS. The virtualizer fake to be a full device and run an OS installed on a disk, which is in this case a simple file.

It is very handy since :

However, it require more resources.

Before going further, make sure we use the same vocabulary :

OpenBSD offer 3 tools for virtualization :

First of all, check if your hardware can virtualize :

$ dmesg | egrep '(VMX/EPT|SVM/RVI)'

If the result isn't empty, it's all good ๐Ÿ˜Š.

Don't forget to upgrade firmwares if needed : "# fw_update".

To add a new virtual machine, you will always :

1. Install in a new disk file with vmctl.

2. Configure vmd to manage this client automatically.

How to virtualize OpenBSD ?

Actually, everything is already well documented on OpenBSD's website. This chapter will just focus on a few tips.

You should definitely read OpenBSD's FAQ on this topic ๐Ÿ˜‰.

Use other installation media

To use img disk (installXX.img or minirootXX.img) :

# vmctl start -c -m 1G -L -i 1 -d installXX.img -d /var/vm/obsdvm.qcow2 openbsdvm

Or if you don't want to download any file, you still can use the bsd.rd you rprobably already have :

# vmctl start -c -m 1G -L -i 1 -b /bsd.rd -d /var/vm/obsdvm.qcow2 openbsdvm 

However, such install requires to have configured a network access for clients.

In /etc/pf.conf :

# using quad9 DNS
pass in quick proto { tcp udp } from 100.64.0.0/10 to any port domain \
    rdr-to 9.9.9.9 port domain
match out on egress from 100.64.0.0/10 to any nat-to (egress)

Better if you have configured unwind on host, which is an excellent idea ๐Ÿ˜Ž :

pass in proto { tcp udp } from 100.64.0.0/10 to any port domain \
    rdr-to localhost port domain
match out on egress from 100.64.0.0/10 to any nat-to (egress)

In "/etc/sysctl.conf" :

net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1

Virtualize debian

Like most linux distro, you'll have to set the installer and bootloader to start the client with a serial console at speed 15200.

# download debian image
ftp "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-10.5.0-amd64-netinst.iso"
# create disk
vmctl create -s 50G /var/vm/debian.qcow2 
# start the VM
vmctl start -c -m 1G -L -i 1 -r debian*.iso -d /var/vm/debian.qcow2 debianvm

Choose install menu without validating โš .

Press TAB then edit the line to change vga and console parameters :

/install.amd/vmlinuz vga=off initrd=/install.amd/initrd.gz --- quiet console=ttyS0,115200n8 

Then Enter.

After installing and rebooting on the fresh debian install, edit "/etc/default/grub" so serial console is still used.

GRUB_TIMEOUT=1
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX="console=tty1 console=ttyS0,115200"
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"

Then reload grub:

# update-grub

Virtualize Alpine Linux

Alpine Linux is a very light distro.

Here again, you need to boot using serial console.

# vmctl create -s 50G /var/vm/linux.qcow2 
# vmctl start -c -m 1G -L -i 1 -r image.iso -d /var/vm/linux.qcow2 linux

When starting virual machine, press "TAB" to see available image (i.e. "lts" or "virt"). Add appropriate parameter to use serial console :

virt console=ttyS0,115200

Then press Enter and it's all good.

What is a VPN?

VPN stands for "virtual private network". As the "private" suggests, the network created is "hidden" from the great internet.

Most of the time, VPN are used to bypass ISP restrictions, secure a public WiFi access or hide from a government monitoring.

There are various VPN tools, each offering pros and cons.

Here, we'll discuss Wireguard, OpenIKED and a bit of SSH. The two lasts are already very well documented in OpenBSD documentation. They all are available in base install.

A bit of vocabulary

In the following pages, we'll use as designations :

We'll configure "roadwarriors".

roadwarrior... what does that mean ?

Roadwarrior describe a setup allowing a client from any origin to reach the Internet through the VPN. Thus, the VPN servers appears to be the actual identity of the client, hiding it in the process.

A bit further, we'll describe how to set up your server so you get a fixed IP provided by the VPN. It becomes useful if your ISP doesn't provide static IP or if your server is travelling for some reasons.

Wireguard

Wireguard is probably the easiest VPN to set up while on of the most secure since it requires only recent encryption methods. For clients, it is handy since it stay up even if the device's IP changes -- switching from wireless to wired as example. IPv4 and IPv6 are supported so you can get an IPV6 if your ISP doesn't offer one. Wireguard is supported natively since OpenBSD 6.8.

Wireguard official website

Many software for varous platforms are availables (android, MacOS...) si it's even more handy.

It might be the best choice if you want to self-host a VPN.

If you want to learn how Wireguard works, look at the whitepaper.

Setting up an exit point ("roadwarrior")

Below we describe the following structure:

Understand that route's changes are for OpenBSD's clients and won't be a question with other OS clients.

In this example, IPs inside the VPN are in 10.0.0.0/24 subnet :

Here's how it will look like :

    +-------------+
    |   server    | wg0: 10.0.0.1 port 4545
    |             |---------------+
    +-------------+               |
           | Public IP            |
           | 192.0.2.2            |
           |                      |
           |                      |
    /############\                |WireGuard
    |  Internet  |                |VPN
    \############/                |
           |                      |
           |                      |
           |rdomain 1             |
    +-------------+               |
    |   client    |---------------+
    +-------------+ wg0: 10.0.0.2
                    rdomain 0 (default)

By default, traffic goes through 10.0.0.2 unless you explicitly ask to use another route (ie : "route -T1 exec ping openbsd.org")

VPN is set up by creating wgN interfaces, where "N" is a number from 0 to 9 as example. Such interface is created by filling a file "/etc/hostname.wgN".

Server will listen on port 4545 UDP. Any other port can be used, just check it isn't already reserved in "/etc/services".

Create keys

Use the following to create a key :

openssl rand -base64 32

It returns as example : "uA1ggvRv66QslZ0ygorWvcLVTxzFauffqMigXTrmLVY="

Once an interface receive a private key, you can retrieve the associated public key with ifconfig :

# ifconfig wgN

On the server

Create a key :

# openssl rand -base64 32
r8uSGD6vyycE5n5/atU9/NX9JQPo4SJryNGpjbQG+rA=

Create wg0 interface with the previous private key :

# ifconfig wg0 create wgkey r8uSGD6vyycE5n5/atU9/NX9JQPo4SJryNGpjbQG+rA= wgport 4545

Now get the matching public key :

# ifconfig wg0
wg0: flags=8082<BROADCAST,NOARP,MULTICAST> mtu 1420
        index 5 priority 0 llprio 3
        wgport 4545
        wgpubkey x9VXlh4AMa2YRjTMRVE39pQRsFHRJHUYrATL6vkqFmU=
        groups: wg

The line starting with "wgpubkey" indicates the interface's public key. You'll need it for clients so they can authentify the server, so write it down.

Of course, take note of server's private key : we'll write it later in a file to automate interface creation.

On a client

We do pretty much the same : create a private key and set up a wg interface without specifying a port:

# openssl rand -base64 32
    q/7uIx6wBIRUIdxOi5D6OWEQRVUt2AXhMj7j29W/s3s=
# ifconfig wg0 create wgkey q/7uIx6wBIRUIdxOi5D6OWEQRVUt2AXhMj7j29W/s3s=
# ifconfig wg0 |grep wgpubkey
    wgpubkey V3pCAhxnRl0QEL8luB9D4EvTVxGT7QGDDCZ3O26kY3A=

Once again, write down keys.

Redirecting traffic through server

Here, we want the server to be a king of relay between the client and the rest of the world.

You have to redirect IP forwarding on server :

# sysctl net.inet.ip.forwarding=1

Add the following line in "/etc/sysctl.conf" so changes are applied at reboot:

net.inet.ip.forwarding=1

For IPv6 :

net.inet6.ip6.forwarding=1

Then, add a nat rule in "/etc/pf.conf" :

# open 4545 UDP
pass in proto udp from any to any port 4545 keep state
# What's from VPN (wg0) is NATted to public servers iface
# serveur
match out on egress from (wg0:network) to any nat-to (egress)
pass on egress from (wg0:network) to any
pass in on wg0

Reload pf ๐Ÿ˜‰

Dig the tunnel

Now we've got all the material to authenticate clients and server, we can dig up the VPN. To do so, we'll indicate public keys on each devices and which IP are allowrd to use the tunnel.

To make things easier, we'll edit "/etc/hostname.wg0" files now. When rebooting, our configuration will remain.

Pay attention to keys, they are the same we got above. ๐Ÿ˜‰

On the server :

"/etc/hostname.wg0" :

inet 10.0.0.1/24
wgkey r8uSGD6vyycE5n5/atU9/NX9JQPo4SJryNGpjbQG+rA=
wgport 4545
wgpeer V3pCAhxnRl0QEL8luB9D4EvTVxGT7QGDDCZ3O26kY3A= wgaip 10.0.0.2/32
up

You can add as many "wgpeer" line as you want. However, each client must have its own IP :

wgpeer V3pCAhxnRl0QEL8luB9D4EvTVxGT7QGDDCZ3O26kY3A= wgaip 10.0.0.2/32
wgpeer m7K/gfmMPYRJx1IOP01zYrNbEuMnnZ29xN4OBgRoRXo= wgaip 10.0.0.3/32
wgpeer qnuq5MgezCDHXsYYGmrcegPCNcJvz9EOIG3XyHp1DBk= wgaip 10.0.0.4/32

On client :

Here you must specify where the client can find the server ("wgendpoint") and set default routes so traffic goes through the tunnel.

wgkey q/7uIx6wBIRUIdxOi5D6OWEQRVUt2AXhMj7j29W/s3s=
wgpeer x9VXlh4AMa2YRjTMRVE39pQRsFHRJHUYrATL6vkqFmU= wgendpoint athome.tld 4545 wgaip 0.0.0.0/0
inet 10.0.0.2/24
wgrtable 1
!route add -net default 10.0.0.1
up

Dig the tunnel on client and server with :

# sh /etc/netstart wg0

Edit client's interface used to reach the internet so it uses routing table nยฐ1. As example in "/etc/hostname.em0":

dhcp
rdomain 1
up

You can now check client's IP : it's the same as the server's.

Configure clients using other OSes

Wiregard supported OS

F-droid (android)

Below is the minimal requirements to set up a client :

Interface :

Peer :

Ressources

This page is heavily inspired from :

Thank you very much to Solรจne Rapenne who had the brilliant idea to use "rdomain".

VPN with OpenIKED

OpenIKED is a free implementation of IKEv2 protocol.

As it is already very well documented, and istead of duplicating good documentation, I suggest you to read OpenBSD's official documentation about IKEv2.

See FAQ 17

Get a static IP thanks to a VPN

If your ISP don't offer static IP, then you could set up a VPN to use endpoint's static IP.

It also gets handy if :

Here is a scheme of what we're going to achieve :

+-----------+        +---------------------+      +--------------+
| Your own  +<------>+ Server with a       +<-----+              |
| Server    |  VPN   | static IP           |      |   Visitor    |
| -sniper-  |        |     -tank-          |      |              |
+-----------+        +---------------------+      +--------------+
  hidden IP                       Public IP

To avoid confusion, I'll refer your server with "sniper" and the VPN endpoint with "tank".

Warning for pf

โš  Remember to adjust your pf.conf. Now, incoming traffic is on VPN interface, i.e. "wg0" if you use Wireguard or "enc0" with IKED.

Setup

From now on, we suppose there is a tunnel between sniper and tank using Wireguard.

Now edit pf.conf on tank to add a binat-to rule :

serv_int = "10.0.0.2"
serv_ext = "192.0.2.2"
[...]
pass quick on egress from $serv_int to any binat-to $serv_ext
[...]
# this line is already here
match out on egress from (wg0:network) to any nat-to (egress:0)
[...]
pass on wg0

Make sure to edit "serv_*"

Reload pf and you're done ๐Ÿ˜Š.

Some may want to fine tune their firewall. Use rdr-to rules instead:

serv_int = "10.0.0.5"
ports_tcp = "{ ssh www https smtp submission imaps domain }"
ports_udp = "{ domain spamd-sync }" 
pass in on egress proto tcp from any to egress port $ports_tcp rdr-to $serv_int
pass in on egress proto udp from any to egress port $ports_udp rdr-to $serv_int

Modification des interfaces d'รฉcoute

Daemons on sniper don't listen on egress anymore but on wg0 instead. Adjust configurations if necessary. If you have modified default routes, wg0 belongs to egress group but check with ifconfig to be sure.

As example, httpd configuration could look like this :

server "athome.tld" {
		listen on wg0 port 80
		root "htdocs"
}

Edit DNS

Now, in your DNS zone, your domain name should point to tank's IP.

Printable version / PDF

Follow the link to get this whole documentation on one page.

If you want to read it offline or print it, use your browser "Print" menu to get a nice PDF. I wrote a CSS part just for this purpose ๐Ÿ˜‰.

Contributing

If you want and if you can, you can contribute by donating money :

With paypal Paypal

Or mail me for bank transfer or other.

Thank you !

You can get the source archive

If you notice an error or want to suggest improvements, please follow these guidelines :

Send changes to prx@si3t.ch.

Thanks

Thank you to :

Solรจne Rapenne

Vincent Finance

Stรฉphane aka 22decembre

PengouinBSD

Pรฉhรค, amazing illustrator

Fred Galusik

And so much more I may have forgotten.

Special thanks to my beloved one who reboot the server when I'm away.