9front in QEMU


A few months ago, I decided to attempt an installation of 9front (a popular fork of Plan 9) in QEMU. I was ultimately successful, and decided to write some notes down as I went.

Ultimately, I cannot give any guarantees or warrantees about this document: It is an outline of what I did, not a guide or a how-to.


My objective was to install a combined CPU, filesystem, auth server, and secstore all in on 9front virtual machine, running on QEMU on my home NAS (running Arch Linux). My NAS has a Wireguard connection up to my rented VPS exposing the 9p ports publicly (modern 9p supports TLS connections). This allow me to access my 9front installation from any of my Wireguard-connected, LAN devices, or through the VPS’s exposed ports using drawterm. Eventually I should be able to connect to the server from other 9front instances as well, although I haven’t tried that yet.

Getting Started

My primary resource was the FQA.

FQA on 9front.org

I installed QEMU on my NAS. I downloaded the latest ISO from the website, uploaded it my NAS, and added a dedicated ZFS dataset for the VM. Those steps can vary based on an individual’s setup.

I started out reading FQA s3.3.1 and s4. As root, I created a sparse disk image in my VM directory:

qemu-img create -f qcow2 my9front.qcow2.img 30G
I then booted the ISO with VNC turned on. Since my NAS is headless (SSH-only), I made sure to enable VNC in the QEMU:

qemu-system-x86_64 -cpu host -enable-kvm -m 4096 \
-vnc :2 \
-net nic,model=virtio,macaddr=52:54:00:00:EE:03 -net user \
-device virtio-scsi-pci,id=scsi \
-drive if=none,id=vd0,file=/vms/my9front/my9front.qcow2.img \
-device scsi-hd,drive=vd0 \
-drive if=none,id=vd1,file=9front.iso \
-device scsi-cd,drive=vd1,bootindex=0
The -vnc :2 ends up exposing the VNC session on port 5902. After punching the appropriate hole in my firewall, I was able to use Remmina to connect to the booted session.

I jumped to the FQA section 4.3, “Performing a simple install”.


It prompted for a boot device. I pressed [enter] since the default was correct. I pressed [enter] again to choose the user as glenda.

The next question was on VGA and input. I accepted the defaults, since I wasn’t planning on accessing the session over VNC regularly anyway. The defaults worked, but the mouse control was super wonky. I tried increasing the screen resolution by listing the available resolutions and then selecting one, but it didn’t help the mouse:

@{rfork n; aux/realemu; aux/vga -p}
@{rfork n; aux/realemu; aux/vga -m vesa -l 1920x1080x32}
I then thought I’d try changing the mouse type, which got my scroll wheel working, but didn’t help the weird mouse behavior:

aux/mouse ps2intellimouse
I decided to go ahead despite the incorrect mouse sensitivity, since I’d ideally not be using it this way much longer.

Performing a Simple Install

In the booted system, I ran inst/start and followed the suggested steps in order. I chose hjfs for the filesystem because it sounded simpler.

In the partdisk step I chose my qcow2 40GB disk (sd00). I chose mbr partition, then w, q.

Next on prepdisk I chose the default partition, then w, q.

On mountfs, I chose the default partition, default cache, and reamed the partition.

In the configdist I chose local.

For confignet I left the system at DHCP, although was apprehensive about how networking would go after reboot.

At the mountdist step I used the defaults.

Then it was the copydist step, [enter]. Wait up to an hour for the copy to complete.

Next it was the ndbsetup step. Chose system name (my9front). For the tzsetup I chose my timezone, and for the boot-related settings I just kept the defaults. I was then ready to reboot.


After the initial install, the QEMU startup command has to be modified. I had to kill the VM with ctrl-c. I then had to run the following, as root since I’m using reserved ports. If I wanted to avoid running as root, I could use my firewall settings to map those ports to different ones, but I personally wasn’t concerned about running QEMU as root:

qemu-system-x86_64 -cpu host -enable-kvm -m 4096 -vnc :2 -net nic,model=virtio,macaddr=52:54:00:00:EE:03 -device e1000,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::17019-:17019,hostfwd=tcp::567-:567 -net user \
-device virtio-scsi-pci,id=scsi \
-drive if=none,id=vd0,file=/vms/my9front/my9front.qcow2.img -device scsi-hd,drive=vd0 -display none
Once remote access is all working, the VNC part will be able to be removed.


In the newly-installed system, I checked for internet:

ip/ping google.com
I wasn’t able to get a connection. I ran:

Which got networking working. Ping times seemed pretty flaky though, which I attribute to the VM.

A resource for me was a blog post about setting up 9front on a Raspberry Pi:

Plan 9: Setting up 9front on a Raspberry Pi

So, I edited /rc/bin/termrc and added ip/ipconfig just before the DNS stuff (so that it runs it on boot). This worked for the time being, although I was unsure how it would end up working once the CPU server stuff got enabled.

Automatic Boot

Next I wanted to ensure the system would boot automatically, without a VNC session and a person to accept the boot parameters. I ran 9fs 9fat to gain access to the boot config, and edited /n/9fat/plan9.ini, adding the following:

And switching bootargs to nobootprompt. I rebooted to test, and confirmed that it fully booted without my intervention.

Remote Access

I then went to FQA section 7.3.3, instructions for setting up a fileserver.

First, I switched the system to CPU sever mode by editing /n/9fat/plan.ini and either adding or editing the service parameter:

I then had to ensure networking would still come up - I edited /rc/bin/cpurc and made sure the line ip/ipconfig was included.

I gave the glenda user a password (putting in my password instead of supersecret):

auth/factotum -g 'proto=p9sk1 dom=my9front user=glenda !password=supersecret'
Then I followed the nvram setup and the CPU server setup. I had to make sure ports 17019 and 567 were both open on my firewall.

I also explicitly set the servicedir in /rc/bin/cpurc. I removed some of the if-statements, and added the following:

# cpu+auth server
auth/keyfs -wp -m /mnt/keys /adm/keys
auth/cron >>/sys/log/cron >[2=1] &
aux/listen -q -t /rc/bin/service.auth -d $serviced tcp
There appeared to be two files to enable the CPU and auth services:

/rc/bin/service/tcp17019 # CPU server
/rc/bin/service.auth/tcp567 # Auth server
This confirmed to me that I should only need those two ports forwarded in QEMU, and optionally later 17020 for mounting the filesystem (have not tried this yet). To confirm I understood this correctly, I rebooted the VM with all but those two ports closed. I was able to successfully log into drawterm.

9front-drawterm -a -h -u glenda
Note: I have noticed that when logging in via drawterm, I seem to have to sometimes wait for nearly a minute for some sort of timeout to occur before drawterm proceeds. This only happens on my LAN, not my Wireguard network. I have more permissive firewall rules on my Wireguard network, so my current suspicion is there is another port that I may need unblocked or modified. Temporarily shutting off the firewall during this testing section might be a good idea. If anyone has any info on what could be happening here, I’d love to hear from you!

I then created a riostart-remote script so that my drawterm would connect straight to a graphical connection: I copied the riostart script from /usr/glenda/bin/rc/riostart to /usr/glenda/bin/rc/riostart-remote, customized it as I saw fit, then added a few lines under the CPU section of $home/lib/profile:

if(! webcookies >[2]/dev/null)
    webcookies -f /tmp/webcookies
rio -i riostart-remote
bind -ac /mnt/term $home/term
The bind mount at the end allows me to conveniently access the drawterm machine’s files from $home/term (they are usually available under /mnt/term whenever one logs into a system).

I read that there is a way to get the 9fat mount working from in here, so in the future I might consider that. Otherwise, the 9fat can only be serviced from a VNC connection.

I confirmed that I was able to log in over drawterm and have my session start up as expected, even after rebooting the machine.

Further Setup

I next did some reading about setting up a CPU server from a 9p.io page:

Configuring a Standalone CPU Server

I read something in the FQA section that misled me into thinking I needed to convert to a newer key format. I believe that can be ignored - I think it is for people who had an existing installation when the change to the new key format occurred. New installations seem to already use the new key format.

After some more reading I determined that I would want to set up a secstore. So, as hostowner (glenda), I made the secstore:

mkdir /adm/secstore
chmod 770 /adm/secstore
I added the following to cpurc:

Then I added glenda to the secstore by running:

auth/secuser glenda
At this point I also tried to fix the sysname in cpurc - replacing all instances of cirno with my9front anywhere I could find it. I rebooted, and all looked good.

Logging in via drawterm I tried accessing the secstore:

It seemed to load fine, even if it was empty.

Adding a Non-Glenda User

Next I decided to add a thor user, to use as my regular login. Do some reading in the FQA, but what I ended up running:

auth/changeuser thor
auth/secuser thor
echo newuser thor >> /srv/hjfs.cmd
Then I generated files by logging in as the user and running

This seemed to work. Inspecting the $home/lib/profile under the cpu section showed auth/factotum was being run on login. It raised an error:

secstore: remote file factotum does not exist
secstore: cmd failed
And prompted for the secstore password. I suspected this is related to the nvram being wrong or the factotum not having anything initialized in it. So, I initialized the secstore factotum file with a blank file:

touch factotum
auth/secstore -p factotum
Then I rebooted to see if the error goes away. That seemed to work. It prompts for my password and my secstore key on login (they are the same), and there are no errors. From what I can tell, this is correct behavior.

The glenda user exhibited the same symptoms, so I logged in there and used the same solution to fix it.

Get a High-Quality Web Browser

Netsurf has been ported to 9front. I installed it. It is fantastic.

9front Netsurf Port on GitHub


After a quite a lot of reading, it seems that modern 9p/drawterm connections should already be using TLS encryption; it is set up during the auth/handshake at some point.

For using CA certificates for TLS for other services, such as HTTPS or 9p without auth, I did find some hints on Peter Mikkelsen’s blog:

How I get tls certificates for 9front

For the time being, I won’t be hosting any public shares, so I’ll consider this setup done!

Hopefully somebody, somewhere finds this useful. If you have any tips or corrections, please do not hesitate to let me know (see the [Contact] tab on my website).

Contact Me

Creative Commons License

Unless stated otherwise, content on this page is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.