{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiggew6veutfc6e52zwf7q4qku3rwvr2nwuxfv2bx6hxqh2g74w3um",
"uri": "at://did:plc:iw5jw32bqqdf4bkbhotvf23m/app.bsky.feed.post/3mpdio7mjias2"
},
"description": "A static OpenBSD site, a failed Newt build, and the simple fix: use the existing Linux connector instead of forcing the wrong tool into the wrong place.",
"path": "/newt-and-openbsd/",
"publishedAt": "2026-06-28T07:54:53.000Z",
"site": "https://pablomurad.com",
"textContent": "# The scenario\n\nThe idea was simple: spin up an OpenBSD VM on Proxmox to host a small, static site generated with an SSG. No monstrous stack, no Docker, no panel full of abstractions in the middle. Just a clean and predictable system, with httpd serving static files, the way OpenBSD likes it.\n\nOn the public side, I already had a structure running on a VPS: Pangolin, Traefik, and Gerbil. Their job is to receive internet traffic and forward it to internal services on my network. And the most natural connector for that, inside the Pangolin ecosystem, is Newt.\n\nThe domain would be theskull.org. The OpenBSD VM became mercurio, with the IP 192.168.50.73 on the local network. There was also calisto, an existing machine on the network, at 192.168.50.46. That detail seemed minor at first and ended up being the key to everything.\n\n# The first decision: was OpenBSD a good idea?\n\nFor Docker, no. For a static site, yes. That distinction matters because a lot of people mix the two things and then blame the operating system. OpenBSD is a poor choice for stacking modern containers, ready-made stacks, and compose files for everything. But for serving a static site with httpd, pf, and a small configuration, it is excellent.\n\nI created a very modest VM: one CPU, little memory, a simple disk, and VirtIO networking. No X, no frills. The system came up, I left sshd enabled, created the right user, adjusted permissions with doas, and moved on to what seemed to be the main step: installing Newt.\n\n# The wrong attempt: installing Newt directly on OpenBSD\n\nPangolin offers a nice and tempting command to install Newt, the kind of command that usually solves everything on Linux in ten seconds:\n\ncurl -fsSL https://static.pangolin.net/get-newt.sh | bash\n\nBut I was on OpenBSD, and that is where things stopped. Newt works very well in supported environments, especially Linux. On OpenBSD, it is not that straightforward. Since the official installer did not solve it, I tried to compile it manually from the repository.\n\npkg_add curl git go gmake\n\ngit clone https://github.com/fosrl/newt.git\n\ncd newt\n\ngo build -o newt .\n\nThe compilation did start. Go downloaded the dependencies, flooded the screen with modules, and almost gave me hope. Then it stopped on a very clear error:\n\npackage github.com/fosrl/newt\n\nimports github.com/fosrl/newt/clients/permissions: build constraints\n\nexclude all Go files in /home/skull/newt/clients/permissions\n\nIt was not a missing package or a typo. It was drier than that: in that part of the code, the available Go files were excluded by OpenBSD build constraints. There was no valid implementation being compiled for the platform. It was not the time to force it by shouting at the terminal.\n\nThe failure was not technical. It was architectural. I had gotten stuck on the idea that the connector needed to run inside the OpenBSD VM itself. But Newt does not need to be on the same server as the final service. It only needs to be somewhere that can see the internal service.\n\nThat was when it clicked: on another occasion, I had already connected internal services to Pangolin, and I suspected that this went through a machine called calisto. So I checked.\n\nhostname\n\nuname -a\n\nip a\n\nwhich newt\n\nps aux | grep -i newt | grep -v grep\n\nsystemctl status newt --no-pager\n\nIt was all there. Calisto was Debian, IP 192.168.50.46, running Newt as a systemd service. The active process was pointing to the Pangolin endpoint at pango.forsak.ing. The tunnel already existed. I was trying to build a new bridge over a river that already had one.\n\n# The correct architecture\n\nThe solution became much cleaner when I stopped trying to push Newt into OpenBSD. The right design was this:\n\nInternet\n\n-> Pangolin / Traefik / Gerbil on the VPS\n\n-> Newt running on calisto\n\n-> http://192.168.50.73:80 on mercurio/OpenBSD\n\nCalisto was already the network connector and could reach mercurio through the LAN. So Pangolin only needed a resource pointing to mercurio's internal IP, using calisto as the site. Direct, without trying to turn OpenBSD into Linux.\n\nOn mercurio, the OpenBSD side was the easiest part. I created the site directory inside the standard httpd environment:\n\nmkdir -p /var/www/htdocs/mercurio\n\nI put in a test index.html and a minimal configuration in /etc/httpd.conf:\n\nserver \"default\" {\n\nlisten on * port 80\n\nroot \"/htdocs/mercurio\"\n\n}\n\nI validated the configuration:\n\nhttpd -n\n\nAnd enabled the service:\n\nrcctl enable httpd\n\nrcctl start httpd\n\nFrom mercurio, the server was up. From calisto, the test also passed:\n\ncurl -I http://192.168.50.73\n\nIt returned 200 OK, with Server: OpenBSD httpd. That was the sign that the path from calisto to mercurio was working.\n\nThen came the most irritating part. Opening https://theskull.org in the browser, the page loaded without an error, the certificate was valid, and everything was white. It is the kind of bug that tricks you, because it looks like DNS, looks like proxying, looks like Pangolin, looks like anything except the obvious.\n\nI tested it with curl for real instead of just staring at the browser:\n\ncurl -v http://192.168.50.73/\n\ncurl -v https://theskull.org/\n\nIn both cases it returned 200 OK. TLS was right, theskull.org pointed to the correct VPS IP (84.247.140.162), and the traffic reached OpenBSD. But one line revealed the crime:\n\nContent-Length: 0\n\nIt was not DNS, Pangolin, Traefik, Gerbil, or Newt. OpenBSD was serving a valid response, but it was empty. The white page was, literally, a page with no content.\n\n# The final fix\n\nThe fix was to recreate index.html properly inside the directory served by httpd:\n\n<!doctype html>\n\n<html>\n\n<head>\n\n<meta charset=\"utf-8\">\n\n<title>The Skull</title>\n\n</head>\n\n<body>\n\n<h1>The Skull is alive on OpenBSD</h1>\n\n<p>Served by the mercurio VM through OpenBSD httpd.</p>\n\n</body>\n\n</html>\n\nI restarted httpd, repeated the tests, and then the content appeared. theskull.org started loading the HTML served by mercurio, passing through calisto and Pangolin.\n\nThe most obvious lesson: not everything needs to run on the same machine. Newt did not have to be on OpenBSD. It had to be on a machine that could see OpenBSD, and calisto was already doing that job.\n\nThe more important lesson is another one. When a tool does not properly support a platform, insisting on it becomes technical fetishism. Compiling Newt on OpenBSD seemed elegant, but it was unnecessary. The right move was to use OpenBSD for what it does well: serve the site in a simple and clean way.\n\nAnd then there is that lesson that always comes back to humiliate me: test layer by layer. DNS, TLS, proxy, connector, internal service, in that order. In the end, the error was in the dumbest possible place: a file served by httpd with zero bytes of useful content.\n\nMercurio remained a minimalist OpenBSD VM, serving a static site with httpd. Calisto remained the Newt connector for the network. And Pangolin kept handling public exposure, with Traefik and the certificate working.\n\nThe final architecture stayed small and easy to maintain. OpenBSD did not become the host for a stack that does not suit it, Linux kept the connector that was already working, and theskull.org started coming from the internet, crossing Pangolin, entering through calisto, and ending on a page served by OpenBSD httpd.\n\nThe solution was not to install more things. It was to stop installing things where they did not need to be.",
"title": "Newt and OpenBSD",
"updatedAt": "2026-06-28T07:54:53.851Z"
}