This Website is Self-Hosted

Server Hardware

This website is being served, right now1, by an old 2015 Macbook Pro with broken screen, running Debian 12.

Macbook Server Rack

This post will outline not only how I went about doing so but also how you, a reader, might as well. Some is old-Macbook-specific, but any non-Apple laptop should be even easier.

Flashing Debian to a macbook is slightly different than you may be familiar with on would-be-Windows systems. You can get a Debian ISO Here, as you normally would (best practice is to torrent it and lighten their server load). Before you get rid of MacOS, open up the terminal and run this command:

hdiutil convert /path/to/downloaded.iso -format UDRW -o /desired/path/to/image.img

hdiutil is proprietary and closed-source, so you literally can’t do this without a Mac. After that, you should be able to write to a USB with the dd util as usual, reboot your Macbook while holding the “Option” key to get a boot menu, and follow the installation steps for your distribution.

To bypass the requirement of a power and external display connection, I had to configure systemd to ignore lid-switch events. In your /etc/systemd/logind.conf, setting HandleLidSwitch=ignore accomplishes exactly this. With the screen closed, and especially if you plan on laying the computer flat (don’t), you’ll want this open-source fan control daemon for the Macbook Pro. Without it, and laying flat, mine was hot to the touch… Oops!

Last, I used this USB Ethernet adapter to connect to my network–it’s a server, after all, so you can’t just settle for WiFi. Warning: the Apple brand Thunderbolt 2 Ethernet adapter does not work with default linux kernel modules (thunderbolt and thunderbolt-net kernel modules don’t seem to suffice as drivers for the ethernet adapter, at least as a network interface).

Local Network

I plan to write more about my home network setup, but I’ll gloss over the details and say that, in general, you’ll also want to ditch your ISP-provided cable modem/router/access point to really do this right. First, you’ll to assign a static IP address to your server. In general, you’ll do this in your router settings (http://192.168.0.1 by default). Wherever you find occurences of DHCP, you’ll find an option to configure a permanent (static) local IP address for your server. I’d recommend something memorable and outside the typical DHCP range (192.168.0.100 through 192.168.0.199), like 192.168.0.99. Whatever you choose to set, jot it down for the next step. If there isn’t an option for “port forwarding” or “virtual servers” setting option, you’ll need to upgrade to a router that has one. Otherwise, create port forwarding rules for ports 80 and 443 to the respective port on whatever IP address you set for your server.

Maybe you’ve already spotted a problem: the router settings page I directed you toward just a moment ago is also an external-facing website on (at least) port 80. So you’ll also probably need to configure your admin web GUI to use a different, unused port in order to avoid conflict (and be able to access both services). Later, you can set up local DNS services and a reverse proxy to access your admin web GUI without needing to remember a random port, but that’s for another post.

Frontend

The website itself is built with Hugo, an open-source, wicked fast static site generator written in Go, and I’m serving it with Nginx. My theme is designed to roughly match my catpuccin Zsh/Tmux/Neovim rice, with added terminal-style navigation controls. Pretty basic website–moving on!

Video Streaming

While it may look like a simple, familiar feature, the video on my about page is actually streamed in real time from my Macbook server. I deployed the site from my 2008 Thinkpad X61, when the video was initially served in its entirety, and the experience was awful. My Intel X3100 graphic chip was laughably ill-equipped to transcode HD video on the fly for its super weird 4:3 aspect ratio screen, and I was getting like 3 FPS. I know most people wont be accessing my site from a collector’s item, but laggy video sucks, and I saw no reason to settle if I could help it. So, I wrote a script using ffmpeg to pre-transcode the video and create a streaming playlist and manifest, which my Nginx HTTP server is configured to serve at an hls (“HTTP live streaming”) endpoint. Now, even on my beloved old X61, I can watch the video full screen with passably good resolution (I plan to add more resolution options but it is honestly not a very high priority, especially since the resolution is plenty for mobile too).

Cloudflare Proxying

The last piece of the puzzle, which I’d absolutely recommend, is Cloudflare proxying. The basic package is free for hobby projects (which is all I needed), so you’re still at an all-in hosting cost of $0.00, and it could potentially save you a lot of headache. Of course, it does complicate the setup, so it might also give you a headache, but that’s part of the fun, right? Anyways… Since you’re using the current IP address of your house to serve the site, you might not want that directly exposed for DNS lookups. That’s sort of like sharing your approximate home address on the internet, which you may not want. The proxying part means that Cloudflare will obfuscate your actual IP, so DNS lookups will show this IP address:

Cloudflare IP

And not the actual coordinates of, like, my house.

Cloudflare also gives you an additional firewall layer that helps protect against DDoS, known attackers, and other potential threats (this sounds like an ad, and I wish they were paying me, but nope). You should of course have protections against these and other kinds of security threats on your home network and the server itself (maybe more on that in a future post), but DDoS protection specifically is very hard to do. You also get Cloudflare’s CDN (content delivery network) caching, so your server only actually gets hit (especially as a static site) when a cache isn’t hit first. Last, but not least, instead of using Certbot/LetsEncrypt to setup SSL/TLS, Cloudflare will generate a 10-15 year expiry certificate, so that your site can be served over HTTPS and not evoke annoying browser warning for your visitors (which, honestly, is so stupid because it’s a static site with no backend/auth). A couple of lines in your Nginx site config and you’re good! No certbot errors!

Dynamic DNS

Now, this doesn’t have to be done using Cloudflare (and I was originally going it through AWS Route 53), but you’re gonna need to be using a nameserver provider with API access. This is because, unlike a traditional server on something like AWS or Heroku, your home IP address is changing constantly; it is a dynamic IP address. As a result, your website, which people find by making a DNS query for, in my case, “lnrssll.com” (dig lnrssll.com if you want to see how this works), is going to have to constantly move and point to different IP addresses. Obviously, you don’t want to have to do this yourself 2, so a great (and free) way to get DDNS (Dynamic DNS) is by setting up a cron job. I use a script that runs every 5 minutes to fetch my current public IP address and send an API request to Cloudflare to update my A record (the key-value record matching domain name to IP address). You might be thinking, “well, Lane, if it doesn’t actually change every 5 minutes, shouldn’t you just use the API to see if changed, and then update the A record only if necessary?”, but this actually means making more API calls. Somehow this fact is frustrating to me. I’m not really sure what I’m optimizing for here, but it probably isn’t Cloudflare’s server resource usage, so I’ve settled on “parsimonious scripting,” and that means no conditional checks.

Conclusion

Now, this may all feel like an exercise in frivolity, since there are so many cloud hosting providers out there, including free ones like Vercel, which even automate Github CI/CD for you (I use a bash script). To be honest, it would be way easier to have just hosted this elsewhere, and the $5 or whatever per month it would probably cost implies I don’t understand opportunity cost or the time-value of money, but I have a ton of small projects like this site, and some of them could stand to be a little more resource intensive than the cheapest tier on AWS offers. So, rather than trying to host 20 services on 10-20 t3 instances for like $3/mo a piece, I can host as much crap as I want, for free, on old hardware I don’t (and literally can’t, because of the screen) use anymore… and so can you! The free plans are also a recipe for future migration headache.

Financial point aside, I think self-hosting is an essential part of a healthy, open internet. Economies of scale are cool and stuff, but platform consolidation in tech lends itself to massive systemic fault intolerance and overreliance on centralized entities. Buy a domain, put up a site, and code whatever the f*** you want!3


  1. Technically, it’s probably being served by Cloudflare’s cache, but guess where it was served to that from? ↩︎

  2. imagine logging into godaddy every few mornings after seeing your site is down and having to navigate their UI to change a DNS record lol ↩︎

  3. your ISP can still ban you, sorry. ↩︎

Lane Russell

My personal weblog


How it works and how to do it

By Lane, 2023-11-05


On this page: