<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Määäx</title>
    <description>The latest articles on DEV Community by Määäx (@maaax).</description>
    <link>https://dev.to/maaax</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4038703%2F5efa5ba7-988d-4400-a7c6-e70ee13ac55a.png</url>
      <title>DEV Community: Määäx</title>
      <link>https://dev.to/maaax</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maaax"/>
    <language>en</language>
    <item>
      <title>My local AI ran on the CPU for a week and I didn't notice</title>
      <dc:creator>Määäx</dc:creator>
      <pubDate>Fri, 07 Aug 2026 16:41:51 +0000</pubDate>
      <link>https://dev.to/maaax/my-local-ai-ran-on-the-cpu-for-a-week-and-i-didnt-notice-5h1m</link>
      <guid>https://dev.to/maaax/my-local-ai-ran-on-the-cpu-for-a-week-and-i-didnt-notice-5h1m</guid>
      <description>&lt;p&gt;My first Ollama setup felt fast. One evening, one model, one question, a clean answer streaming back at a speed that made cloud AI feel unnecessary.&lt;/p&gt;

&lt;p&gt;A few days later, with a bigger model and a longer question, the cursor crawled. No error. No warning. Just slow, in a way that made me suspect the model, the network, my own question — everything except the actual cause, which turned out to be almost embarrassingly mundane.&lt;/p&gt;

&lt;p&gt;Here's what was actually going on, and the two things that caused it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Ollama runs as its own service on Proxmox, with the GPU passed through instead of running on the CPU alone. The whole point of local AI, for me, is that no request ever leaves the house — but without usable speed, that's a nice principle nobody actually uses day to day.&lt;/p&gt;

&lt;p&gt;In front of it sits a web UI for chatting from the browser; behind it, the Ollama API for whatever I automate later. Access works exactly like &lt;a href="https://dev.to/maaax/zero-open-ports-my-homelab-is-reachable-from-anywhere-and-exposes-nothing-4ff8"&gt;the rest of the homelab&lt;/a&gt;: the identity provider in front, reachable only through the tunnel, &lt;a href="https://dev.to/maaax/i-put-sso-in-front-of-eleven-self-hosted-services-then-locked-myself-out-of-all-of-them-4ogm"&gt;locked down the same way as everything else&lt;/a&gt;. No port opened just because it's "only my own chat."&lt;/p&gt;

&lt;p&gt;The real work wasn't standing the service up. It was sizing it correctly — which model actually fits the GPU I have, what context length is realistic, and making sure the GPU Proxmox reports as "available" is the one Ollama actually gets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 1: a model that almost fit — and Ollama never said a word about it
&lt;/h2&gt;

&lt;p&gt;VRAM is a hard limit, not a soft warning. A model that fits entirely on the GPU runs there and runs fast. A model that &lt;em&gt;almost&lt;/em&gt; fits gets partially offloaded to the CPU by Ollama, silently, with no error and nothing in the response that flags it happened.&lt;/p&gt;

&lt;p&gt;That's what makes it dangerous. It doesn't feel like a failure — it feels like "a bit sluggish today," and a bit sluggish points you everywhere except the graphics card sitting there fully idle.&lt;/p&gt;

&lt;p&gt;It got worse when I raised &lt;code&gt;num_ctx&lt;/code&gt; — the maximum context length, how much text the model can hold in its head at once — because I wanted longer conversations. A bigger context needs memory on top of the model itself, and that extra chunk was exactly what pushed the whole thing past the GPU's limit, even though the model alone would have fit fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; model size and context length share the same VRAM budget, not separate ones. After changing either one, check actual GPU utilization — not just whether the answer "feels" fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 2: another VM already had the GPU, and nothing told me
&lt;/h2&gt;

&lt;p&gt;GPU passthrough on Proxmox isn't multi-tenant. A passed-through card belongs to exactly one VM or container, never several at once.&lt;/p&gt;

&lt;p&gt;I'd briefly set up a second VM using the same card for something unrelated, then just shut that VM down afterward instead of removing its passthrough config. On the next host reboot, that VM grabbed the card again automatically, before the Ollama container even started — and Ollama fell back to plain CPU inference without complaint, because from its point of view there simply was no GPU.&lt;/p&gt;

&lt;p&gt;No crash, no warning. Just a service running roughly ten times slower than it should for the better part of a week, until I noticed while cleaning up the VM list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; GPU pinning is exclusive and renegotiated on every boot. After a host restart, it's worth a quick check of which VM actually holds the card before blaming the AI service for being slow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup, condensed
&lt;/h2&gt;

&lt;p&gt;Same result, without the quiet CPU week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Know the VRAM ceiling before pulling a model&lt;/strong&gt;, not after. How much graphics memory is actually available, and how much does the model need at the quantization you picked?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set &lt;code&gt;num_ctx&lt;/code&gt; deliberately, not generously.&lt;/strong&gt; Every increase eats memory the model itself needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check actual GPU utilization after any change&lt;/strong&gt; — not the response speed you &lt;em&gt;feel&lt;/em&gt;, the number the hardware reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assign GPU passthrough to exactly one VM&lt;/strong&gt;, and fully remove test configurations on any other VM — don't just power it off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-check which VM holds the card after every host reboot&lt;/strong&gt;, before assuming the AI service itself is the problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep access locked down the same way as everything else&lt;/strong&gt; — identity provider in front, reachable only through the tunnel, no exception just because it's "only the chat."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;The gap between a local model that feels cloud-fast and one that crawls rarely comes down to the model itself — usually it's VRAM running out quieter than expected, or a card another VM already claimed. The reason for doing this at all hasn't changed: every request stays on my own network, nothing leaves the house, and that's worth the occasional re-tuning.&lt;/p&gt;




&lt;p&gt;I write about self-hosting in more depth — one homelab component per issue, always including what broke — in my German-language newsletter &lt;em&gt;Souveränes Homelab&lt;/em&gt;: &lt;a href="https://souveraenes-homelab.beehiiv.com" rel="noopener noreferrer"&gt;souveraenes-homelab.beehiiv.com&lt;/a&gt;. The English write-ups land here.&lt;/p&gt;

&lt;p&gt;Määäx&lt;/p&gt;

</description>
      <category>ai</category>
      <category>selfhosted</category>
      <category>homelab</category>
      <category>ollama</category>
    </item>
    <item>
      <title>Zero open ports — my homelab is reachable from anywhere and exposes nothing</title>
      <dc:creator>Määäx</dc:creator>
      <pubDate>Fri, 07 Aug 2026 13:14:41 +0000</pubDate>
      <link>https://dev.to/maaax/zero-open-ports-my-homelab-is-reachable-from-anywhere-and-exposes-nothing-4ff8</link>
      <guid>https://dev.to/maaax/zero-open-ports-my-homelab-is-reachable-from-anywhere-and-exposes-nothing-4ff8</guid>
      <description>&lt;p&gt;My router forwards exactly one port to the internet. UDP, for WireGuard. That's it.&lt;/p&gt;

&lt;p&gt;No port for the file storage. No port for the router in front of my services. No port for anything. A port scan from outside finds one thing it could try itself against, and that one thing only talks to devices holding the right key.&lt;/p&gt;

&lt;p&gt;Getting there took two evenings of debugging, a phone battery that died twice as fast as it should have, and a tunnel that cheerfully displayed "connected" while passing zero packets. Here's all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment the forwarding list got too long
&lt;/h2&gt;

&lt;p&gt;After sorting out single sign-on (that's &lt;a href="https://dev.to/maaax/i-put-sso-in-front-of-eleven-self-hosted-services-then-locked-myself-out-of-all-of-them-4ogm"&gt;its own story&lt;/a&gt; — the short version: I locked myself out of my entire homelab), one login covered everything. But the port forwarding list on my router still read like an invitation.&lt;/p&gt;

&lt;p&gt;File storage forwarded on 443. A second service on a custom port, because the first one had taken the obvious one. An SSH port I'd opened "just quickly" for an emergency and then forgotten to close.&lt;/p&gt;

&lt;p&gt;Every open forward is a bet. I'm betting the service behind it is always patched. I'm betting there's no zero-day. I'm betting I never fat-finger a firewall rule.&lt;/p&gt;

&lt;p&gt;Three bets I didn't want to keep taking. So: forwards out entirely, and exactly one encrypted way in instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;The idea is simple, and that's the strength of it.&lt;/p&gt;

&lt;p&gt;The router has one open UDP port for WireGuard and nothing else. Anyone who wants to reach my homelab first builds a WireGuard tunnel to the router — and is then, as far as the network is concerned, sitting in the same network as a device plugged in at home. The routing layer in front of my services (with the identity provider behind it) is reachable at its internal address exactly as before, but &lt;strong&gt;only through the tunnel&lt;/strong&gt;, never from the open internet.&lt;/p&gt;

&lt;p&gt;Every device — laptop, phone, my partner's tablet — gets its own key pair and its own peer entry in the router's WireGuard config. No shared credentials. If a device is lost or sold, I delete that one entry and nothing else is affected.&lt;/p&gt;

&lt;p&gt;Same thinking as with SSO: not one shared secret for everyone, but an individual key per access path that can be revoked on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 1: I accidentally built a full tunnel and broke my phone's internet
&lt;/h2&gt;

&lt;p&gt;Setting up the first client config on my phone, I set &lt;code&gt;AllowedIPs&lt;/code&gt; to &lt;code&gt;0.0.0.0/0&lt;/code&gt;, because that's what the first guide I found said to do.&lt;/p&gt;

&lt;p&gt;What that actually means: not just the traffic to my homelab, but &lt;strong&gt;every last bit&lt;/strong&gt; of the phone's internet traffic now went through the tunnel to my router at home — and only then back out to the internet from there.&lt;/p&gt;

&lt;p&gt;On mobile data, the result was noticeably slower browsing, because every request took a detour through my apartment first, and a battery that drained about twice as fast, because the tunnel stayed up permanently for things that had nothing to do with my homelab.&lt;/p&gt;

&lt;p&gt;It took me two days to notice that "my phone's internet has been sluggish lately" and "I set up WireGuard yesterday" were the same event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; &lt;code&gt;AllowedIPs&lt;/code&gt; is the knob between split tunnel (only traffic destined for the homelab network goes through) and full tunnel (literally everything). For day-to-day access to your own services you almost always want split tunnel — put your own subnet in there, not &lt;code&gt;0.0.0.0/0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Full tunnel is a real and legitimate configuration. It's just a deliberate choice, not a default you copy out of a guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 2: "connected", and not a single packet got through
&lt;/h2&gt;

&lt;p&gt;Out on mobile data — behind the carrier-grade NAT most mobile networks use — WireGuard completed the handshake cleanly. Then, after a few minutes of sitting idle, the connection outward was effectively dead. The app still said "active". No ping, no page load, nothing.&lt;/p&gt;

&lt;p&gt;No error. No message. Just silence.&lt;/p&gt;

&lt;p&gt;I blamed the router first. Then DNS. Then, eventually, I found the detail that the first guides I'd read barely mentioned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile carrier NAT forgets open connections far faster than a home router does, and WireGuard, by default, sends nothing to keep the mapping warm when there's no data to move.&lt;/strong&gt; No traffic, no packets, and the NAT entry quietly expires. The client has no idea — from its point of view the tunnel is still established.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; set &lt;code&gt;PersistentKeepalive&lt;/code&gt; to a low value (25 seconds is the common choice) for any client that sits behind NAT — which in practice is every mobile device. It sends a tiny packet during idle periods so the NAT mapping stays open and the tunnel is genuinely reachable, not just "connected according to the display".&lt;/p&gt;

&lt;p&gt;That distinction — &lt;em&gt;reachable&lt;/em&gt; versus &lt;em&gt;shows as connected&lt;/em&gt; — is worth internalizing. It shows up in a lot more places than WireGuard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup, condensed
&lt;/h2&gt;

&lt;p&gt;Same result, without the two evenings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open exactly one port.&lt;/strong&gt; UDP for WireGuard on the router. Everything else that was previously forwarded: closed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One key pair per device.&lt;/strong&gt; No shared setup, every device gets its own peer entry so individual access can be revoked individually.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope &lt;code&gt;AllowedIPs&lt;/code&gt; deliberately to your homelab subnet&lt;/strong&gt; (split tunnel), not &lt;code&gt;0.0.0.0/0&lt;/code&gt; — unless routing all your traffic is what you actually want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set &lt;code&gt;PersistentKeepalive = 25&lt;/code&gt; on every device behind NAT&lt;/strong&gt; — that's essentially every phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand over internal DNS&lt;/strong&gt;, so hostnames on the homelab network resolve over the tunnel too, not just raw IPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test from real mobile data&lt;/strong&gt;, not from your own Wi-Fi. Mistake 2 only surfaces out there; at home it would never have shown up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the routing layer and the identity provider exactly as they are.&lt;/strong&gt; The tunnel replaces the way &lt;em&gt;in&lt;/em&gt;. It does not replace authentication behind it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last point matters. A tunnel is a network control, not an access control. Anything on the other side that was worth protecting before is still worth protecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;The difference isn't "faster" or "more convenient." It's invisible, and that's the whole point.&lt;/p&gt;

&lt;p&gt;From the outside, my homelab shows no service port at all anymore — just one encrypted tunnel endpoint. There's nothing there for an automated scan to work with, and getting through requires a key I handed out myself, to a device I can revoke individually.&lt;/p&gt;

&lt;p&gt;The old setup wasn't reckless. It was just a pile of small bets I'd stopped keeping track of.&lt;/p&gt;




&lt;p&gt;I write about self-hosting in more depth — one homelab component per issue, always including what broke — in my German-language newsletter &lt;em&gt;Souveränes Homelab&lt;/em&gt;: &lt;a href="https://souveraenes-homelab.beehiiv.com" rel="noopener noreferrer"&gt;souveraenes-homelab.beehiiv.com&lt;/a&gt;. The English write-ups land here.&lt;/p&gt;

&lt;p&gt;Määäx&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>homelab</category>
      <category>devops</category>
    </item>
    <item>
      <title>I put SSO in front of eleven self-hosted services — then locked myself out of all of them</title>
      <dc:creator>Määäx</dc:creator>
      <pubDate>Fri, 07 Aug 2026 13:10:42 +0000</pubDate>
      <link>https://dev.to/maaax/i-put-sso-in-front-of-eleven-self-hosted-services-then-locked-myself-out-of-all-of-them-4ogm</link>
      <guid>https://dev.to/maaax/i-put-sso-in-front-of-eleven-self-hosted-services-then-locked-myself-out-of-all-of-them-4ogm</guid>
      <description>&lt;p&gt;Eleven self-hosted services. Eleven logins. And — being honest here — at least four of them had a password I'd have described as "good enough, I'm the only one who gets in anyway."&lt;/p&gt;

&lt;p&gt;That sentence is the beginning of the end.&lt;/p&gt;

&lt;p&gt;There was one evening that broke it for me. I wanted to move a single photo from Nextcloud into Paperless. One handgrip. It cost me three different passwords, plus the container dashboard on top, because something in the stack was acting up and I needed to see why. A homelab doesn't grow according to plan — it sprawls. And at some point user management stops being a detail and turns out to be the foundation you forgot to pour.&lt;/p&gt;

&lt;p&gt;So I made a decision a lot of people find excessive: &lt;strong&gt;before&lt;/strong&gt; finishing the rest of the homelab, I put a central identity layer in front of it. Single sign-on with Authentik. One login for everything.&lt;/p&gt;

&lt;p&gt;Overkill for a home network? I didn't think so. But it did lock me out of my own house, and it did cost me two evenings of chasing a bug that turned out to be one missing thing. Both are worth writing down, because both are the kind of mistake the tutorials skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually built
&lt;/h2&gt;

&lt;p&gt;Authentik is a self-hosted identity provider. The mental picture that works for me: it's the office door with the one badge reader, and every room is behind it.&lt;/p&gt;

&lt;p&gt;It speaks the usual languages — OpenID Connect and SAML for apps that support them, and &lt;strong&gt;forward auth&lt;/strong&gt; through the router that sits in front of my services for everything that doesn't. In a homelab, "everything that doesn't" is most of it.&lt;/p&gt;

&lt;p&gt;Concretely it runs as a Docker stack: Authentik server, worker, PostgreSQL, Redis. In front of that sits the edge router (Traefik-style routing — Nginx or Caddy do the same job) that takes every request for a protected service and asks Authentik first: &lt;em&gt;is this person allowed in?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not signed in → you land on the login page. Signed in → you get waved through, and the app is told over a header who just walked in.&lt;/p&gt;

&lt;p&gt;The end state: one login for eleven services, 2FA in exactly one place, and when I want to give someone — partner, family — access to exactly one service, I do it through a group instead of handing out another shared password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 1: I locked myself out. Completely.
&lt;/h2&gt;

&lt;p&gt;When I set up the forward-auth rule on the router, I was thorough. Too thorough. I put &lt;em&gt;everything&lt;/em&gt; behind Authentik — including Authentik's own login page.&lt;/p&gt;

&lt;p&gt;Read that again slowly, because that's exactly how long it took me to see it: &lt;strong&gt;in order to sign in, I had to be signed in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A perfect infinite loop. I sat in front of a redirect pointing at itself and could not reach a single one of the eleven services. Not the dashboard, not the file storage, not the thing I'd been trying to fix in the first place.&lt;/p&gt;

&lt;p&gt;What saved me was that I could still SSH into the host and pull the routing rule for the Authentik host back out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson has two halves, and the second one is the important one:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The identity provider and its own login route must never sit behind the auth it provides. Exclude it explicitly.&lt;/li&gt;
&lt;li&gt;You always need a way past the front door. An SSH path to the host that does &lt;em&gt;not&lt;/em&gt; depend on the exact system you're currently rebuilding. Check that it's open &lt;strong&gt;before&lt;/strong&gt; you touch the auth layer, not after.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That second point generalizes far past SSO. Any time you're putting a new gate in front of everything, the first question is: what's my way in if the gate is broken?&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 2: SSO "worked", and every app thought I was a guest
&lt;/h2&gt;

&lt;p&gt;Once the login finally stood up, some services worked immediately. One let me through — and then showed the user as an unnamed guest. Everywhere. Every page, no name, no groups, no permissions that matched who I actually was.&lt;/p&gt;

&lt;p&gt;I spent two evenings on this. I suspected the app's config. I suspected Authentik's mappings. I re-read the docs twice.&lt;/p&gt;

&lt;p&gt;The cause was banal and, I've since learned, extremely common: &lt;strong&gt;the router validated Authentik's answer, but never passed the headers with username and groups on to the app.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the application received a clean "authenticated: yes" — and no name. With no name, it did the only sensible thing it could and fell back to its guest user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; forward auth is two stages, and they fail independently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stage one — &lt;em&gt;may this request in?&lt;/em&gt; That's the auth check. When this breaks, you notice instantly, because nothing loads.&lt;/li&gt;
&lt;li&gt;Stage two — &lt;em&gt;who is it?&lt;/em&gt; Those are the headers passed from the auth endpoint to the actual application. When this breaks, everything looks like it works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If SSO is "sort of half working" — you get in, but the app doesn't know you — it's almost always stage two. Go look at your header pass-through before you look at anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I'd set it up today
&lt;/h2&gt;

&lt;p&gt;Same result, without the lockout and without the two evenings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start Authentik in isolation first.&lt;/strong&gt; Server, worker, database, cache as their own stack. Test the login while &lt;em&gt;nothing&lt;/em&gt; sits in front of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure a second way in.&lt;/strong&gt; SSH to the host, independent of SSO. That's your fire exit. Verify it's open before every auth change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect exactly one unimportant service.&lt;/strong&gt; Not all eleven. One test service where you get the forward-auth pattern right in a place where breaking it costs you nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify both stages separately.&lt;/strong&gt; Does a signed-out visitor land on the login page (stage: &lt;em&gt;may they in?&lt;/em&gt;), and does the service then show the &lt;em&gt;correct&lt;/em&gt; username (stage: &lt;em&gt;who?&lt;/em&gt; ↔ the headers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclude the identity provider itself.&lt;/strong&gt; Explicitly carve the Authentik login route out of the auth rule. Otherwise: loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only then roll out&lt;/strong&gt; — service by service, signing out and back in after each one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2FA and groups last.&lt;/strong&gt; Second factor at the one door, access through groups instead of shared passwords.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;The real gain wasn't convenience, which surprised me.&lt;/p&gt;

&lt;p&gt;It was that for the first time I had &lt;em&gt;one&lt;/em&gt; place where I can see and control who gets into my homelab — and that I can revoke someone's access with one click, without having to remember eleven separate places where I once gave it to them.&lt;/p&gt;

&lt;p&gt;Building SSO before the rest feels like pouring a foundation when you'd rather already be putting up walls. That's exactly why it holds later.&lt;/p&gt;




&lt;p&gt;I write about self-hosting in more depth — one homelab component per issue, always including what broke — in my German-language newsletter &lt;em&gt;Souveränes Homelab&lt;/em&gt;: &lt;a href="https://souveraenes-homelab.beehiiv.com" rel="noopener noreferrer"&gt;souveraenes-homelab.beehiiv.com&lt;/a&gt;. The English write-ups land here.&lt;/p&gt;

&lt;p&gt;Määäx&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>homelab</category>
      <category>docker</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
