EST. 2021  •  OSLO, NORWAY

Skui.io

Homelab & Self-Hosting

← Back
blog

The Ports I Didn't Open

APIs are the quiet backbone of everything — and the reason my homelab can report home to ethica.no every minute without exposing a single inbound port.

The Ports I Didn't Open

The quiet backbone
#

Strip away the frontends, and almost everything interesting that happens between computers today is an API call. Your banking app is a wrapper around one. The weather widget on your phone, the “login with” button, every CI pipeline, every smart-home gadget that mysteriously still works — all of it is machines asking other machines for JSON.

That’s not a complaint. It might be the most useful convention we’ve collectively settled on: a URL, a verb, a token, a payload. Small, boring contracts. And boring is exactly what lets ten thousand systems that have never heard of each other cooperate without a meeting.

I want to show what that buys you in practice, with the smallest example I own: how my homelab tells ethica.no how it’s feeling — without me opening a single port to make it happen.

Monitoring wants a door
#

The naive way to watch a server from the outside is to give the outside a way in. Port-forward the dashboard. Punch a hole for the metrics endpoint. Slap dynamic DNS on the router and hope.

Every one of those holes is a doorbell, and the internet rings all of them, all day. Put anything on a public port and the scanners find it within minutes — not because you’re interesting, but because scanning the entire IPv4 space is cheap and automated. Now your monitoring setup, the thing that was supposed to make you safer, is attack surface. It needs patching, auth, rate limiting, and attention. Forever.

For years that felt like the cost of visibility. It isn’t.

Flip the arrow
#

The fix is embarrassingly simple: nothing gets in — the machine reports out.

On each server I care about sits a small agent. One Python file, standard library only, nothing to pip install. Every sixty seconds it collects what matters — CPU, memory, disk, whether a process is alive, whether a local service answers HTTP — and pushes the result to ethica.no:

POST /api/company/metrics HTTP/1.1
Host: ethica.no
X-Company-Token: ****************
Content-Type: application/json

{"name": "proxmox-01", "kind": "host", "status": "ok", "cpu": 3.2, "mem": 61.4}

That’s the entire integration. The server side validates the token, stores the numbers, and renders them on a dashboard and a public status page. The homelab side needs exactly one thing to work: outbound HTTPS. Which is the one thing that works from everywhere — behind NAT, behind CGNAT, on a locked-down customer network, from hotel wifi.

My firewall has no inbound rules for any of this. From the outside, the lab looks exactly like it did before: closed.

Why this shape wins
#

No new attack surface. An exposed dashboard is reachable by everyone on earth, around the clock. An outbound POST exposes nothing. There is no port to scan, no login page to brute-force, no zero-day to catch me on a slow patch week.

The credential is small and revocable. The agent authenticates with a scoped token, generated in the admin panel, rotated with a click. Compare that to the old alternative — VPNs and trusted networks — where access is something you architect, not something you revoke.

The contract is tiny. A handful of JSON fields. I can test it with curl, version it without ceremony, and read the whole thing in one sitting. When something breaks, the surface area of “what could be wrong” is a dozen lines.

It composes. The same endpoint doesn’t care if the report comes from a Proxmox node, a container, or a Raspberry Pi in a closet. New machine? Copy three files, paste a token, start a systemd service. Done.

None of this is my invention, to be clear. It’s the same reason every agent-based product you’ve ever used — backup clients, monitoring SaaS, EDR — phones home instead of listening. It’s the pattern that survives contact with real networks.

Once you see it
#

You start noticing the arrow direction everywhere. The Homelab Status widget on this site’s front page — Mainframe uptime, Nextcloud, Bitwarden, the subdomains — is fed exactly this way. A tiny read-only collector gathers container states and host uptime, then POSTs a snapshot to the site’s API every thirty seconds, authenticated with a token. The widget simply renders the last report that arrived; the web app never touches a Docker socket, and no metrics port is exposed anywhere. That little Updated timestamp at the bottom is literally “when the last POST landed.”

My CI pipelines report deploy status the same way — the build server pushes, nobody polls it. The pattern generalizes into a posture I’ve landed on for everything I run:

  • Prefer outbound reporting to inbound exposure.
  • Prefer a small, documented JSON contract to a clever protocol.
  • Prefer tokens you can revoke to networks you have to trust.

That’s it. That’s the whole architecture. APIs get sold as an integration story — connect anything to anything! — but the quieter win is that a good API lets systems cooperate without trusting each other’s networks. The contract does the work the firewall hole used to do, minus the part where the firewall has a hole in it.

The web’s most underrated security feature is a well-designed POST request.


What’s listening on your public ports right now — and does it need to be? If you’ve got a setup that still relies on port-forwarding something inward, I’d genuinely like to hear what’s keeping it that way. You know where to find me.