User Tools

Site Tools


running_webservices_openbsd_vmm

notice: I have been having issues with vmm/Alpine Linux, where, after a VM shutdown, Alpine Linux will become somehow be corrupted and I must boot into the installation iso to repair the system. if this happens to you, all I had to do was update Alpine.

running webservices from OpenBSD's vmm

although it is most likely best to run most things directly on an OpenBSD host, compatibility issues and other pains may arise depending on the service.

this is a guide on how to host web content from a virtual machine running inside OpenBSD's vmm and serve it through a reverse proxy with relayd.

in the setup this guide shows, relayd will handle http and https requests to the OpenBSD machine and separate them based on domain (yonderly.org, example.com, …), then send them to either the host or the virtual machine. httpd will also listen on the domain for the virtual machine in order to force https; the virtual machine's http server will only be accessible through https.

vmd

vmd is the daemon that interacts with vmm to make virtual machine creation and management easy. its config file is at /etc/vm.conf.

what I like to do is create /etc/vm.conf.d and use “include” in vm.conf to load the configs within it. this allows you to create individual config files and enable or disable them by simply commenting their include line.

for example:

/etc/vm.conf
include "/etc/vm.conf.d/alpine0.conf"
#include "/etc/vm.conf.d/alpine1.conf"
include "/etc/vm.conf.d/openbsd0.conf"
...

here is the vm.conf used for this guide:

/etc/vm.conf
include "/etc/vm.conf.d/alpine0.conf"

also, I create /var/vm, /var/vm/disk, and /var/vm/iso for the disk and installation images for the VMs.

# mkdir -p /var/vm/disk /var/vm/iso

now, set secure permissions for the disk directory.

# chmod -R 600 /var/vm/disk

for convenience, I create the vm group and allow its members to control virtual machines (in next section). this allows you to start, stop, monitor, and stuff without needing to be root.

# groupadd vm
# usermod -G vm user

pf.conf

pf.conf needs to be modified so that networking works properly for the VMs.

/etc/pf.conf
...
match out on egress from 100.64.0.0/10 to any nat-to (egress)
pass in proto { udp tcp } from 100.64.0.0/10 to any port domain \
	rdr-to 1.1.1.1 port domain
...

the virtual machine

for this guide, Alpine Linux will be used. download the installation ISO.

# cd /var/vm/iso
# ftp https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/x86_64/alpine-extended-3.21.3-x86_64.iso

create the disk image. a size of 64G will be used here.

# cd /var/vm/disk
# vmctl create -s 64G ./alpine0.qcow2
vmctl: qcow2 imagefile created

make sure that the disk image has the correct permissions!

# ls -l ./alpine0.qcow2
-rw-------  1 root  _vmd    256K Feb 1 12:00 alpine0.qcow2

here is the vm block for the VM. read vm.conf(5) for more information.

/etc/vm.conf.d/alpine0.conf
vm alpine0 {
        owner   :vm # allow group vm to control this virtual machine.
 
        boot device cdrom
 
        cdrom   /var/vm/iso/alpine-extended-3.21.3-x86_64.iso
        disk    /var/vm/disk/alpine0.qcow2
 
        memory  8192M
 
        local interface
}

if you haven't already, start vmd.

# rcctl start vmd

if you have, run the following to have vmd reload its config.

# vmctl reload

the virtual machine will automatically start, use the following command to connect to it.

# vmctl console alpine0

if you see nothing, wait, and then press Enter. the login prompt should be visible.

Alpine installation

see Alpine Linux's installation guide for more information.

  • the hostname doesn't matter.
  • initialize interface “eth0”. the IP 100.64.1.3 will be used for this guide.
    • you can either use dhcp or figure out a valid IP by looking at the tun device being used by the VM.
  • you should still enter secure root and user passwords, as a vulnerability in your web stack could still be present (we will only forward port 80; no other ports will be accessible publicly).
  • probably choose openssh as the ssh server. I have little experience with dropbear. you should at a minimum enable an sshd server as it will make interacting with the VM much more convenient, no longer needing to use slow cu(1) after installation.
  • you can do a crypt install, but it will mean you'll need too cu into the VM and enter the disk password each time it starts up.

improving console

this is optional, but it is very beneficial in debugging a VM which isn't booting. by default, Alpine Linux's output will not be visible in cu until it has reached the login prompt. you can change this by appending “console=…” to the kernel options.

the installer will have unmounted the installation, so it must be remounted.

# mount /dev/vda3 /mnt
# mount /dev/vda1 /mnt/boot
# mount /proc /mnt/proc
# mount /dev /mnt/dev

chroot into the install.

# chroot /mnt

now, edit /etc/update-extlinux.conf and add “console=ttyS0,115200” to 'default_kernel_opts'.

/etc/update-extlinux.conf
...
# default_kernel_opts
# default kernel options
default_kernel_opts="... quiet console=ttyS0,115200"
...

now, run update-extlinux.

# update-extlinux

if it mentions the configuration was unchanged, you did not edit its config properly.

exit the chroot and unmount the installation.

# exit
(no longer in chroot)
# cd /
# umount /mnt/dev
# umount /mnt/proc
# umount /mnt/boot
# umount /mnt

after running the commands, press Enter, then ~, then Ctrl+D to exit cu.

power off the virtual machine.

# vmctl stop alpine0

change the VM's configuration so it boots from disk.

/etc/vm.conf.d/alpine0.conf
vm alpine0 {
        ...
        boot device disk
        ...
}

have vmd reload the configuration.

# vmctl reload

setting up Alpine

start the virtual machine, and, using the -c flag, immediately connect to it.

# vmctl start -c alpine0

look through the output and make sure everything looks correct, then exit cu using the keys earlier.

if you enabled sshd, you can now login to the VM. it should have told you the IP earlier. if it is your first VM, the IP should be 100.64.1.3. your second would be 100.64.2.3, and so on. 100.64.1.3 will be used in this guide.

$ ssh 100.64.1.2
you cannot login as root.

simple nginx setup

become root and install nginx (from Alpine Linux Wiki).

# apk add nginx
# mkdir /www
# chown -R www:www /var/lib/nginx
# chown -R www:www /www

edit the default server.

/etc/nginx/http.d/default.conf
server {
        listen 80 default_server;
        listen [::]:80 default_server;
 
        location / {
                index index.html;
                root /www;
        }
}

create /www/index.html.

/www/index.html
<html>hi</html>

start nginx.

# rc-service nginx start

it should be possible to fetch the html page.

# wget localhost
Connecting to localhost ([::1]:80)
saving to 'index.html'
# cat index.html 
<html>hello</html>

on the host, make sure you can access the VM's http server.

# ftp http://100.64.1.3/index.html
Requesting http://100.64.1.3/index.html

relayd

now that an http server is running inside the virtual machine, we can make it publicly accessible through relayd.

this section will use 10.0.0.4 as the IP of the server running OpenBSD, and alpine0.yonderly.org as the domain meant for the VM.

/etc/relayd.conf
ext_ip="10.0.0.4"
 
table <self> { "127.0.0.1" }
table <alpine0> { "100.64.1.3" }
 
http protocol wwwtls {
        tls keypair "yonderly.org"
        #tls keypair "alpine0.yonderly.org"
 
        match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
        match request header append "X-Forwarded-By" \
            value "$SERVER_ADDR:$SERVER_PORT"
        match request header set "Connection" value "close"
 
        block
 
        pass request header "Host" value "yonderly.org" \
                forward to <self>
        ...
        pass request header "Host" value "alpine0.yonderly.org" \
                forward to <alpine0>
}
 
http protocol www {
        match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
        match request header append "X-Forwarded-By" \
            value "$SERVER_ADDR:$SERVER_PORT"
        match request header set "Connection" value "close"
 
        block
 
        pass request header "Host" value "yonderly.org" \
                forward to <self>
        ...
        pass request header "Host" value "alpine0.yonderly.org" \
                forward to <self>
}
 
relay www {
        listen on $ext_ip port http
        protocol www
 
        forward to <self>       port 80
}
 
relay wwwtls {
        listen on $ext_ip port https tls
        protocol wwwtls
 
        forward to <self> port 8080
        forward to <alpine0> port 80
}

make sure that the config is sane and start or restart it.

# relayd -n
# rcctl restart relayd

httpd

httpd will serve the acme-challenge directory for TLS certificate generation and redirect http to https.

/etc/httpd.conf
...
server "alpine0.yonderly.org" {
        listen on * port 80
 
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2
        }
 
        location * {
                block return 301 "https://$HTTP_HOST$REQUEST_URI"
        }
}

make sure that the config is sane and start or restart it.

# httpd -n
# rcctl restart httpd

acme-client

/etc/acme-client.conf
...
domain alpine0.yonderly.org {
        domain key "/etc/ssl/private/alpine0.yonderly.org.key"
        domain full chain certificate "/etc/ssl/alpine0.yonderly.org.crt"
        sign with letsencrypt
}

generate the certificate.

# acme-client -v alpine0.yonderly.org

finalizing

now that the certificates exist, the “tls” line for alpine0.yonderly.org can be uncommented in relayd.conf.

/etc/relayd.conf
...
        tls keypair "alpine0.yonderly.org"
...
# rcctl restart relayd

the virtual machine's nginx server should now be accessible at https://alpine0.yonderly.org.

running_webservices_openbsd_vmm.txt · Last modified: 2025/04/20 11:38 by ethan

Except where otherwise noted, content on this wiki is licensed under the following license: CC0 1.0 Universal
CC0 1.0 Universal Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki