Migrating from Codeberg Pages to an OpenBSD VPS

Lobsters Hottest News

Summary

The author details migrating their static websites from Codeberg Pages to an OpenBSD VPS due to performance issues, covering setup processes and personal experiences.

<p><a href="https://lobste.rs/s/4wh5ng/migrating_from_codeberg_pages_openbsd">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 08/25/26, 11:42 AM

# Migrating from Codeberg Pages to an OpenBSD VPS Source: [https://nemin.hu/vps/index.html](https://nemin.hu/vps/index.html) ## Table of Contents - [Background](https://nemin.hu/vps/index.html#background)- [Picking my provider](https://nemin.hu/vps/index.html#picking-my-provider) - [SSH config](https://nemin.hu/vps/index.html#ssh-config)- [Be careful with MIMEs](https://nemin.hu/vps/index.html#be-careful-with-mimes) - [Upgrading OpenBSD to 8\.0\-beta](https://nemin.hu/vps/index.html#upgrading-openbsd-to-8.0-beta) - [Firewall](https://nemin.hu/vps/index.html#firewall) - [TLS certificates](https://nemin.hu/vps/index.html#tls-certificates) - [Web server](https://nemin.hu/vps/index.html#web-server) - [German bureaucracy](https://nemin.hu/vps/index.html#german-bureaucracy) - [Setting up my VPS](https://nemin.hu/vps/index.html#setting-up-my-vps) - [Findings; or, Bots Love Your Obscure Content\!](https://nemin.hu/vps/index.html#findings-or-bots-love-your-obscure-content) - [Conclusion](https://nemin.hu/vps/index.html#conclusion) - [Footnotes](https://nemin.hu/vps/index.html#footnotes) In this post I'd like to explain the "how"\-s and "why"\-s of moving from forge\-based static site hosting to a full on VPS\. This isn't really a tutorial, mostly because there are far better texts out there how one would do these things, but I do link to many of them and point out stuff I found confusing or surprising\. Also, if you're reading this post, you're reading it served by the VPS, say hi\! ## Background I've been blogging for almost eight years now and for those eight years, I've always relied on some sort of free host for my content\. This seemed fine for a pretty long time, especially considering neither this nor my[other site](https://oddwords.hu/)is monetized at all\. However, recent\-ish changes in my needs and financial situation finally got me to break with the status quo and actually experiment a little\. Originally, I relied on Github Pages, which was for a site like mine almost perfect\. It was free and required nearly zero setup, it built almost immediately, it had impeccable uptime \(something Github famously[started to struggle with](https://mrshu.github.io/github-statuses/)\), and I could super easily place a cheap domain in front of it to make it a little more appealing than the factory\-default`nemin32\.github\.io`address\. However, due to the, ehm,*recent trends*and emboldened by the[rallying cries](https://taggart-tech.com/migrate-to-codeberg/)of the people who had enough, I too took the dive and moved my[presence](https://codeberg.org/Nemin)over to Codeberg\. This, of course, meant my two websites had to move too\. Thankfully the process wasn't particularly painful \(the article linked above pretty much gets you up to speed in 10\-15 minutes\) and so I quickly had everything set up\. While I thought I was done, sadly I quickly realized that though the two services were similar in design, their quality of service was very different\. Codeberg Pages really struggled at times\. My pages are rarely over a couple hundred kilobytes \(most of which being a couple AVIF\-encoded pictures\) and yet there were times where loading my sites took 10\-15 seconds, while the browser just hanged\. Before anyone gets the wrong idea, I'm not blaming Codeberg for this in any shape or form\. In fact I'm very grateful, that the option exists in the first place\. After all, they are a \(relatively\) small non\-profit, who have to serve not just my silly little webpages, but sites for far more serious projects too\. With this in mind, it's no wonder they can't always afford the computing power for me\. However, as much as they have my sympathies, I was still stuck with this problem and had to figure out some solution\. And said solution was joining something useful with something pleasant: Enter VPS\-es\. ## Picking my provider A VPS is a managed virtual machine, that one may rent from cloud provider companies\. The userbase is very wide, you can find people using them from all areas of life, from students and hobbyists, to very serious and large companies, who don't want to bother with on\-prem solutions\. These days, if one happens to fall into the former category and only really needs a machine that has some minimal RAM and a reasonably fast few \(perhaps even one\) core CPU, they have a plethora of providers to pick from, who specialize in pushing down prices and lending you such VMs for literal peanuts\. ![](https://nemin.hu/vps/deals.avif)An example of a[site](https://lowend-deals.xbit.win/)that collects such cheap deals\.For a striking example, just take a look at[LowEndBox's $1 per month VPS list](https://lowendbox.com/blog/1-vps-1-usd-vps-per-month/), the name tells you what to expect\. But if you're willing to shop around for more obscure providers and can contort your needs even further \(for instance by not requiring an IPv4 address\), I've found a[company](https://web.c-servers.co.uk/category/welcome-to-c-servers), who'll rent you a VM with 5Gb of storage and cca\. 400MB of RAM for $4\.5 per year, not month,*year\.*I don't think you can go much lower than that\. However, figuring that it's not like I'll be spending fortunes either way, I decided to stick to a well\-known and trusted option,[netcup](https://www.netcup.com/en)\. While their cheapest option is closer to 2€ a month, in return you get an unmetered, 100Mbps connection \(as long as you use it in good faith\), 30Gb of storage space, the option to make a snapshot of your VM for backups, and the assurance that your money is going to a bunch who know their stuff\. Sounds all great… if only it wasn't so unexpectedly convoluted to do business with them\. ## German bureaucracy As explained earlier, my choice fell on the[VPS Pico G11s](https://www.netcup.com/en/server/vps/vps-pico-g11s-iv-12m-nue)\. At first the process was as one would expect: I put the VPS in my cart, entered my address and personal data, then clicked through the pages, until I noticed that there is a Netcup newsletter and if you sign up for it, you get a voucher for 5€ off\. I figured "Hell yeah, that's almost three months" and signed up\. The voucher arrived almost instantly and I happily saw that after entering it, the price of the VPS dropped from 24€ to around 19€\. Everything seemed fine, so I pressed on the big, friendly "Order" button… And was thrown back with an error\. Apparently the VPS I picked was too cheap for the voucher to be applicable for, so I begrudgingly removed it and proceeded with the full price\. At the end of the day, 24€ is the cost of two fancy lunches, so I wasn't going to throw everything away just because I couldn't be even more of a cheapskate\. Having corrected my deal and pressed "Order" again, I expected to be redirected to a payment portal\. Instead, I was simply informed that an actual human being will verify that my order is legit and that I must wait until that happens\. Considering this was very late in the afternoon \(way past the usual European working hours\), I sighed and resigned myself to wait until the next day or however long it'd take\. In the meantime I received an email, which I found really interesting\. It was written in a very serious and legalese way, with long paragraphs of dry text describing my rights and responsibilities, and that, by placing an order, I now have two weeks to refuse entering into the contract with Netcup, after which it becomes binding\. I found this both quite refreshing, a lot of companies hide their nasty details deep into Terms of Uses or other such texts, but not here\. Sure, it makes the communication towards their customer less bombastic, but I absolutely didn't mind that\. Then, to my biggest surprise, not ten minutes later my inbox pinged me again: My inspection was successfully finished and I'm ready to log into the portal\. So I did just that, expecting to be finally able to pay… only to be hit with a huge interface, where I was ordered to declare my handling of user data according to GDPR\. Sadly, I didn't make a screenshot of this and I'm no longer able to access it, but more or less I was just asked what sort of data I intend to handle\. Considering I was making a simple static site, I declared that I'm not handling any personal data and that was that\. At this point, finally, I was able to add my credit card and pay for my server\. Five minutes and another two emails later, I received a link to*yet another completely different page,*which \(to my great relief\) did indeed allow me to access my machine\. ## Setting up my VPS The VM came with Debian 13, which is a fine system otherwise, but having had a recent interest in the various BSDs and stumbling on[httpd\.rocks](https://httpd.rocks/)through[Lobste\.rs](https://lobste.rs/), I decided to ditch it and try my luck with OpenBSD 7\.9 instead\. ![](https://nemin.hu/vps/images.avif)A list of images provided by Netcup\. All the BSDs and some nice distro choices… also Windows, I guess, if you're into that…\.*weirdo*\.To my delight netcup provides the newest OpenBSD ISO on their interface\. One simply just has to click on it and the VM automatically gets the disk loaded and the boot order changed to boot from it\. Power\-cycling the VM is a little slow \(taking around an entire minute\), but I don't expect to do it much, so whatever\. Installing OpenBSD was a breeze\. Hands down one of the easiest processes when it comes to putting an OS on your machine\. I disabled the X windowing sets and the games set, but kept the rest\. Apparently this is a bit of a[faux pas](https://www.bsdhowto.ch/instsets.html), but worst case I'll just reinstall the machine, no biggie\. ![](https://nemin.hu/vps/filesystem.avif)The default filesystem I went with\.`/var`is woefully small\.The only true error I think I made during installation was accepting the default partitioning scheme, which wastes around 6GB of storage on`/home`, which I won't be using anyway, while`/var`\(arguably my most important folder, as it contains the code for my website and my logs\) ended up being a measly 2GB\. It is a bridge that I'll eventually have to cross, but for now it's fine\. > **Note from the future:**I did end up reinstalling while troubleshooting an unrelated issue and made`/var`into a far bigger partition\. Thankfully with how fast and easy the installation of OpenBSD is, this was an entirely painless process\. ## SSH config While the Netcup interface does provide a remote desktop, it's not super pleasant to use\. Not only is it excruciatingly slow, it is also very particular about when it accepts input \(you need to click into the screen and it seems to occasionally lose focus on its own\), so I quickly decided to look into setting up SSH instead\. I didn't have to look too far,[this blogpost](https://seeminglyrandom.net/posts/setup-openbsd-and-hardening-ssh/)was perfect for my needs\. It describes both how one can bring a system up to speed and also how to set up SSH in a way that's secure, by disabling dubious ciphers\. I generated a brand new keypair for the VPS, because reusing the same key for all your projects is a great way to be pwned\. And after quickly logging in with my password and issuing the right`ssh\-copy\-id`call, I disabled password login\. Afterwards I added the a new entry to my`~/\.ssh/config`file, so I only need to issue`ssh vps`and it handles the rest\. Much more handy than needing to do things manually: The first three lines aren't strictly necessary, but they make long connections a lot more tolerable\. ``` AddKeysToAgent yes ServerAliveInterval 15 ServerAliveCountMax 3 Host vps HostName [IPv4 of the VPS] User [username] IdentityFile ~/.ssh/id_vps ``` As a final touch, I symlinked`openrsync`to`rsync`\. This was necessary because my main Linux machine's`rsync`expects to find an executable with the same name on the VPS when I try to copy my files over\. Other than this hack, it's all very clean with transparent SSH handling\. I can just do`rsync \-av \-\-delete source vps:destination`and it handles the rest\. ## Upgrading OpenBSD to 8\.0\-beta > **Note from the future:**This section here is anachronistic, as it's placed before I discuss any of my server config, but was actually written after I've finished and then completely rewrote said config from scratch\. The reason for this is that, while I originally intended to go with the often\-recommended`relayd`to handle TLS connections and attach HTTP headers to my responses \(httpd\.rocks has a[tutorial](https://httpd.rocks/relayd.html)for this too\!\), I stumbled upon[this](https://reddit.com/r/openbsd/comments/1vnn5ut/dumb_question_httpd_and_the_need_for_relayd/)Reddit post while solving an unrelated issue, which mentioned that`httpd`was soon to receive support for setting HTTP headers itself \(and it is already capable of handling TLS\), making`relayd`pretty much overkill for my purposes\. It is considered both good practice and a painless way to enhance the performance of your site to set a couple of HTTP headers that inform the browser about how it may cache your content and that it should leak as little info as possible\. While OpenBSD 7\.9's`httpd`provides no built\-in mechanism for this \(and thus requires an additional proxy service\), the \(currently beta\) 8\.0 release[does](https://man.openbsd.org/httpd.conf#header)in the form of the`header set`directive\. To be able to use this, I had to upgrade my system from`7\.9\-release`to`8\.0\-beta`\. While this may sound difficult at first glance, it turned out to be little more than issuing`sysupgrade \-s`, waiting until the system downloaded the newest OS release snapshot, and then automagically installed it, without me having to touch anything at all\.[1](https://nemin.hu/vps/index.html#fn1) After that I only had two more super easy housekeeping tasks to do: - I needed to issue`pkg\_add \-u`to upgrade all my packages to their 8\.0\-compatible versions\. This went through without incident\. - I also needed to call`sysmerge`to update any system configuration files that may have changed between the two releases\. In my case this was only`sshd\_config`, which introduced post\-quantum ciphers by default and changed`PermitRootLogin`to`prohibit\-password`\. As neither of these affected my usage of`sshd`, I opted to accept them without manual intervention\. With the foundations done, it was time to actually have the machine do its intended job\. Frankly, however, any sort of detailed guide I could write here would be a pale and imprecise imitation of httpd\.rocks, so I'll keep things succinct and just comment on stuff I find interesting to point out\. If you find my journey at all inspiring, I suggest checking the aforementioned guide out as well\. With this disclaimer out of the way, let's get into it: ## Firewall ``` ext_if="vio0" set limit states 100000 # Maximum state table entries set timeout interval 30 # Interval between state purges set optimization "normal" # General optimization for connection tracking set skip on lo # Antispoof antispoof log quick for $ext_if match in all scrub (no-df) # Block all by default block all # Block table for abusive hosts table <block_table> persist file "/etc/pflists/block_table" table <ssh_naughty> persist file "/etc/pflists/ssh_naughty" block in quick from <block_table> block in quick from <ssh_naughty> # Block bogus destination addresses table <martians> { 0.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24 \ 192.0.2.0/24 224.0.0.0/3 192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 \ 203.0.113.0/24 \ ::/128 ::/96 ::1/128 ::ffff:0:0/96 100::/64 2001:10::/28 2001:2::/48 \ 2001:db8::/32 3ffe::/16 fec0::/10 fc00::/7 } block in quick on egress from any to <martians> # Allow DNS and NTP pass out on $ext_if proto {udp tcp} to any port 53 pass out on $ext_if proto udp to any port 123 # Allow SSH pass in on $ext_if proto tcp from any to any port ssh synproxy state \ (max-src-conn 10, max-src-conn-rate 5/30, overload <ssh_naughty> flush global) pass out on $ext_if proto tcp from any to any port ssh keep state # Allow web server stuff pass in on $ext_if proto tcp from any to any port {80 443} synproxy state \ (max-src-conn 100, max-src-conn-rate 30/5, overload <block_table> flush global) pass out on $ext_if proto tcp from any to any port {80 443} keep state ``` The firewall does the following: - Antispoofing and packet scrubbing, - `block all`to be better safe than sorry, - A blocklist to drop aggressive crawlers and one to SSH brute\-forcers, - And allowing some select services through for basic functionality: DNS, NTPD, SSH, and finally our webserver, with the latter two services also having anti\-flood measures enabled to put malicious clients on the blocklists\. While both blocklists forbid IPs on them from connecting, they have one important difference: I've set up my crontab so that it periodically calls`pfctl \-t block\_table \-T expire 8400`\. This command removes clients that have been on the`block\_table`list for a while, because it is entirely possible that it was merely a bumbling user, who accidentally reloaded the page one too many times with their cache turned off or that it was a fake IP address, that someone used\. The other list,`ssh\_naughty`, isn't ever cleared\. I only intend to allow myself to log in, so if a client is hammering the VPS' SSH daemon, then I cannot give it the same presumption of innocence\. And to make sure the list doesn't disappear into smoke in case I restart my system, I followed the advice of[this BSDHowTo article](https://www.bsdhowto.ch/savepftables.html)and added the following into`/etc/rc\.shutdown`: ``` pfctl -t block_table -T show > /etc/pflists/block_table pfctl -t ssh_naughty -T show > /etc/pflists/ssh_naughty chmod 0600 /etc/pflists/block_table chmod 0600 /etc/pflists/ssh_naughty ``` This ensures that if my system goes down normally \(i\.e\. for an update or miscellaneous restart\), the OS will automatically dump the block\-lists into files, which are then reloaded on the next startup by the`file`directive in`pf\.conf`\. I'm considering also running this script as a cronjob in case of an unexpected hard reset, but perhaps that's not something one has to actively worry about when it comes to VPS\-es\. ## TLS certificates ``` authority letsencrypt { api url "https://acme-v02.api.letsencrypt.org/directory" account key "/etc/acme/letsencrypt-privkey.pem" } domain nemin.hu { alternative names { www.nemin.hu } domain key "/etc/ssl/private/nemin.hu.key" domain full chain certificate "/etc/ssl/nemin.hu.crt" sign with letsencrypt } domain oddwords.hu { alternative names { www.oddwords.hu } domain key "/etc/ssl/private/oddwords.hu.key" domain full chain certificate "/etc/ssl/oddwords.hu.crt" sign with letsencrypt } ``` I use OpenBSD's built\-in`acme\-client\(1\)`, which interfaces with[Let's Encrypt](https://letsencrypt.org/)to get TLS certificates for my two sites\. One thing I noticed, while still messing with my config is that having two \(or more\) sites on one host makes troubleshooting certificate issues quite tricky, because browsers will*helpfully*try to use your other site's certificate instead, which will then cause a failure as the browser realizes the name of the site you're trying to reach and the name stored in the certificate are completely different\. Therefore, if you're facing issues while hosting multiple sites and have the opportunity, I genuinely would suggest disabling all but the problem server until you figure out the solution\. ## Web server ``` types { include "/usr/share/misc/mime.types" } prefork 10 no banner # # Nemin.hu # server "nemin.hu" { listen on * tls port 443 root "/htdocs/nemin.hu" hsts {preload, subdomains} gzip-static log style combined location "/.well-known/acme-challenge/*" { root "/acme" request strip 2 } # Drop malicious requests location "/.aws*" {block drop} location "/.env*" {block drop} location "/*.cgi*" {block drop} location "/cgi-bin/*" {block drop} location "/*.php*" {block drop} location "/*wp-*" {block drop} # Set headers header set "Cache-Control" "public, max-age=86400" always header set "X-Content-Type-Options" "nosniff" always header set "Referrer-Policy" "no-referrer" always header set "Permissions-Policy" "interest-cohort=()" always header set "X-Frame-Options" "SAMEORIGIN" always tls { certificate "/etc/ssl/nemin.hu.crt" key "/etc/ssl/private/nemin.hu.key" } } server "nemin.hu" { listen on * port 80 location "/.well-known/acme-challenge/*" { root "/acme" request strip 2 } # Drop malicious requests # ... # Set headers # ... block return 301 "https://nemin.hu$REQUEST_URI" } server "www.nemin.hu" { listen on * port 80 listen on * tls port 443 location "/.well-known/acme-challenge/*" { root "/acme" request strip 2 } # Drop malicious requests # ... # Set headers # ... tls { certificate "/etc/ssl/nemin.hu.crt" key "/etc/ssl/private/nemin.hu.key" } block return 301 "https://nemin.hu$REQUEST_URI" } # # Oddwords.hu # # More or less the same as above, just with the URL substituted... ``` The`httpd`config is fairly simple, though it has a couple interesting notes I'll lay out below\. The second and third block simply handle redirecting`www\.`and bare HTTP requests to`https://nemin\.hu`\. All blocks are set up to handle ACME challenges, because the client will try both`nemin\.hu`and`www\.nemin\.hu`\. The first block is the one that actually serves content\. It's also very straightforward, but there are two calls that I think are worth specifically mentioning: - `gzip\-static`allows your server to send gzip\-encoded HTML instead of uncompressed data\. Notably it will**not**compress files itself, so you have to do that yourself, but the[blog post](https://dataswamp.org/~solene/2022-04-22-openbsd-httpd-gzip.html)where I learned about this feature also explains how easy it is to create these archives yourselves\. \(It's literally just calling`gzip \-k`on them\.\) And if that's still too much manual work,[this](https://citizen428.net/blog/self-hosting-static-site-openbsd-httpd-relayd/)other post describes a one\-liner that allows you to recursively turn everything \(but images, which don't benefit from compression\) into a`\.gz`file: ``` find public/ -type f ! -name '*.png' -exec gzip -9k "{}" \; ``` Obviously you should adapt this to your needs\. For instance, for me`public/`is`site/`and instead of using PNG files, I use AVIF \(which also don't really need zipping\)\. - The lines starting with`location`match for some files crawlers like to frequent\. Since this is a static website, it's not like they'd hit anything in the first place, however, by silently dropping their connections, we waste their time and resources\. These filtering blocks are repeated everywhere, because I don't want to pointlessly redirect requests that will be dropped anyway\. - Finally, the lines starting with`header`allow us to set HTTP headers on our responses\. This is the reason why I ugpraded to OpenBSD 8\.0 earlier, as 7\.9 doesn't yet have this feature and would've required us to use`relayd`or another proxy\. I'm not sure if it's strictly necessary to include these headers on calls that only redirect to the HTTPS URLs, but I figured it can't hurt and you only really have to set it up once\. I kind of wish there was a more involved macro system for`httpd\.conf`, that would allow shortening the block lists to one definition that may be reused in all sections, but it's not that bad\. > **Note from the future:**This kept me up at night, so I went and[figured out a way](https://nemin.hu/httpd-macros)to make things terser\. ### Be careful with MIMEs Originally, when I first wrote my config, I didn't include the first line\. None of the tutorials I've read mentioned it and the page loaded just fine, so I figured all is well\. Except everything wasn't well, because I noticed the pictures on the webpage loading incredibly slowly\. Up to 20\-30 seconds, despite the fact that due to them being highly\-compressed AVIF images, they were rarely over 300KB in size\. A quick googling later I was able to pin down the problem: Without informing the server about the various[MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types), it absolutely struggled to send over anything\. Fixing this is simply adding the following to your config outside any of the server blocks: ``` types { include "/usr/share/misc/mime.types" } ``` And here is where I'd like to repeat a very old and very true adage: One should absolutely read the OpenBSD man pages\. It is easy to skip over them if one is used to Linux, because quite often the documentation that comes with your packages might be sub\-par or simply not detailed enough to help you fix your issues without going on a wild goose chase on the Internet\. However, with OpenBSD, this is different\. The manual is super easy to read and understand and even obscure things are often mentioned, including the line seen above: ``` Types Configure the supported media types. httpd(8) will set the Content-Type of the response header based on the file extension listed in the types section. If not specified, httpd(8) will use built-in media types for text/css, text/html, text/plain, image/gif, image/png, image/jpeg, image/svg+xml, and application/javascript. The types section must include one or more lines of the following syntax, enclosed in curly braces: type/subtype name [name ...] Set the media type and subtype to the specified extension name. One or more names can be specified per line. Each line may end with an optional semicolon. Later lines overwrite earlier lines. include file Include types definitions from an external file, for example /usr/share/misc/mime.types. ``` As you can see, the man page clearly states that AVIF files aren't included by default, so no wonder the server choked on them\. And with this line added, everything loads almost instantly\. ## Findings; or, Bots Love Your Obscure Content\! While I was toying around with loading my site and checking the access logs, I noticed that, beyond my own IP, there were a lot of other clients connecting too\. At first I was very happy to see this\. Considering I've never had analytics before, I had zero idea how many people actually click on my pages, so seeing the buzzing activity felt very validating… That is until I actually read the user agents and realized about 40% of these were AI scrapers, another 40% crawler bots, and only 20% were clients I couldn't decisively categorize as either\. ![](https://nemin.hu/vps/logs.avif)An excerpt from my access logs\. All of these were credential scanner/stealer bots\.I must admit this made me a little sad, but then I really shouldn't have expected anything different\. What I found really amusing is that a lot of these crawlers were trying to crack the site by scanning for files such as`\.aws`,`\.env`, and`wp\-\*`, which they won't find, because it's a static site\. If you remember in the earlier section I added a bunch of lines starting with`location`in my`httpd\.conf`file\. This is why\. When the crawlers try to read files matching these paths, their requests will be silently dropped\. I also added a simple`robots\.txt`, that disallows all the bigger AI crawlers from the site, but they don't seem to respect it\. For now I've let things be, but perhaps eventually I'll add their user agents into the blocklist of my config\. > **Note from the future:**I'm adding this comment three days later\. My SSH blocklist already has 77 clients on it\. The internet is harsh\. ## Conclusion ![](https://nemin.hu/vps/term.avif)I call my main machine`luna`, so calling a machine that's both very far away and tiny`ceres`felt very appropriate\.My job title may technically be DevOps engineer, but I'm really more of an "average" backend programmer and even when I do touch infra, it's a lot different to tweak tiny parts of an already well\-established stack, with several colleagues to help you and several backups to reach for if something went wrong\. So I went into this project with some slight apprehension and with a feeling that any mistakes would leave me to poke around in the dark\. However, reality turned out to be far less menacing\. Migrating to a VPS ended up much quicker and easier than I assumed and I also found it an excellent learning experience, with plenty of further opportunities to branch out to when I feel like it\. So if hosting your own site sounds exciting for you, nowadays it's easier than ever to try and it won't cost you much\. Thanks for reading\! --- 1. From what I could find, the jury seems to be[still](https://dataswamp.org/~solene/2020-03-27-openbsd-current.html)[somewhat out](https://lecorbeausvault.wordpress.com/2022/02/09/using-openbsd-current-is-easy-should-you/)on whether it's "safe" to use snapshots on a server\. Proponents mention that the OS developers generally use the newest available version and are forbidden from breaking it and also that by using snapshots most of the obvious issues should be long ironed out\. Critics cite the need to update the system weekly or even more frequently and the fact that, though rare, system breakages may still happen a couple times a year\. Considering my two sites aren't mission critical in any shape or form and that the VPS contains nothing that couldn't be restored from a local backup, I decided to go with it\. If you're more risk averse than me, maybe wait until 8\.0 drops, which should happen about 3 months after the writing of this article \(November of 2026\)\.[↩︎](https://nemin.hu/vps/index.html#fnref1)

Similar Articles

Run OpenBSD on DigitalOcean for $4/month

Lobsters Hottest

The article provides a step-by-step guide on setting up OpenBSD on DigitalOcean for $4 per month, covering the process from downloading the OS image to creating and configuring a droplet.

One year with Codeberg

Lobsters Hottest

GNU Guix reflects on one year since migrating to Codeberg for source code hosting and collaboration, discussing the decision process, challenges, and outcomes.