After 10+ years of wishing I had a home on the internet, I recently made my website. Here’s how.

How I got the server/website

  • Vultr OpenBSD VPS
  • Epik to get the domain and handle DNS

About the website

  • Hosted on OpenBSD using httpd (no 3rd-party tools were installed at all)
  • https using relayd and acme
  • No frameworks, just plain index.html and style.css
  • It’s pretty fast

Why OpenBSD?

I agree with many of their design choices. Also, it’s an innovative and secure complete OS. While it may not be as fast as Linux or another choice, I don’t need a crazy amount of performance. I’d rather have something stable and secure. More here.

Why no frameworks?

Similar reason. I despise bloat–also I have no idea how to use many frameworks. I have tried, and every one of them is a headache. Sometimes dealing with a headache is worth it if that’s what you need. If I needed it, I’d probably get someone else to do the coding for me. Not my cup of tea.

Accessibility

I used this resource to inform my color formatting decisions

Source files

I have backed up all of the source files for the website itself to this repository

Compression

I have chosen to compress many files using gzip, automated by the script compress.sh:

#!/usr/local/bin/bash

gzip -5fk style.css
gzip -7fk *.html
find assets/ -type f -not -name "*.gz" -exec gzip -4fk "{}" \;

The /etc/httpd.conf and /etc/relayd.conf files also reflect gzip

Config files

/etc/acme-client.conf

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.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

domain conjfrnk.com {
       alternative names { www.conjfrnk.com }
       domain key "/etc/ssl/private/conjfrnk.com.key"
       domain full chain certificate "/etc/ssl/conjfrnk.com.crt"
       sign with letsencrypt
}

/etc/httpd.conf

server "conjfrnk.com" {
	listen on 127.0.0.1 port 8080
	root "/htdocs/www.conjfrnk.com"
	gzip-static
	location "/.well-known/acme-challenge/*" {
		root "/acme"
		request strip 2
	}
 	location match "/([^.]+)$" {
  		request rewrite "/%1.html"
	}
}

server "www.conjfrnk.com" {
	listen on 127.0.0.1 port 8080
	block return 301 "https://conjfrnk.com$REQUEST_URI"
}

server "conjfrnk.com" {
	listen on * port 80
	alias "www.conjfrnk.com"
	block return 301 "https://conjfrnk.com$REQUEST_URI"
}

Redirections

I have configured /etc/httpd.conf such that it will redirect www.conjfrnk.com to conjfrnk.com. I like the simple look better, plus www seems way too redundant.

/etc/relayd.conf

include "/etc/ips.conf"

table <local> { 127.0.0.1 }

http protocol https {
    tls ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:!aNULL:!SSLv3:!DSS:!ECDSA:!RSA"
    tls cipher-server-preference
    tls no client-renegotiation
    tls no session tickets
    tls keypair "conjfrnk.com"

    match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
    match request header append "X-Forwarded-Port" value "$REMOTE_PORT"

    match response header set "Referrer-Policy" value "strict-origin-when-cross-origin"
    match response header set "X-Frame-Options" value "deny"
    match response header set "X-XSS-Protection" value "1; mode=block"
    match response header set "X-Content-Type-Options" value "nosniff"
    match response header set "Strict-Transport-Security" value "max-age=63072000; includeSubDomains; preload"
    match response header set "Content-Security-Policy" value "default-src 'none'; script-src 'self' 'nonce-analytics'; object-src 'none'; style-src 'self'; img-src 'self' projecteuler.net; frame-ancestors 'none'; base-uri 'none'; form-action 'none'; connect-src 'self' www.googletagmanager.com www.google-analytics.com"
    match response header set "Permissions-Policy" value "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=()"
    match response header set "Cache-Control" value "max-age=31536000, immutable"
    match response header set "Accept-Encoding" value "gzip, deflate, br"

    return error
    pass
}

relay wwwtls {
    listen on $ipv4 port 443 tls
    protocol https
    forward to <local> port 8080
}

relay www6tls {
    listen on $ipv6 port 443 tls
    protocol https
    forward to <local> port 8080
}

Cron

I am using a cronjob to refresh certificates and reboot httpd as necessary. I also use cron to download and apply system/package updates for OpenBSD:

30 3 * * * acme-client conjfrnk.com && rcctl reload httpd relayd

30 4 * * 0 pkg_add -u && syspatch && reboot

Certificate/httpd refreshes happen every night at 3:30am and updates happen every Sunday at 4:30am. Manual urgent updates are performed as necessary.

Sources

I incorporated parts of the official OpenBSD guide and this unofficial guide, making modifications as necessary. As for the website’s content, I would like to credit Andrej Karpathy for having the best-designed personal website I’ve seen, by a wide margin. I used his style.css as a jumping-off point for my own, and I expect my style to develop further in the future. I got all of the SVG icons from this great website.

I also used this SRI Hash Generator to secure my Google Tag Manager scripts (Google Analytics used to count visitors)