<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Networking on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/tags/networking/</link><description>Recent content in Networking on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 16 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/tags/networking/index.xml" rel="self" type="application/rss+xml"/><item><title>Gardener on Cleura</title><link>https://backend-engineering-strategy-tools.github.io/site/projects/gardener/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/projects/gardener/</guid><description>&lt;p&gt;Getting hands-on with &lt;a class="link" href="https://gardener.cloud/" target="_blank" rel="noopener"
 &gt;Gardener&lt;/a&gt; on &lt;a class="link" href="https://cleura.com/" target="_blank" rel="noopener"
 &gt;Cleura&lt;/a&gt; — a European OpenStack cloud — ahead of using it professionally. The focus is on the networking and traffic ingress side: how does a Gardener shoot cluster on OpenStack expose services, what does the LoadBalancer path actually look like, and when does ingress apply versus when it does not.&lt;/p&gt;
&lt;p&gt;The test application is a &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/projects/minecraft/" &gt;Minecraft server with Velocity proxy&lt;/a&gt; — useful precisely because it is raw TCP rather than HTTP, which forces the full LoadBalancer path rather than an ingress shortcut.&lt;/p&gt;
&lt;p&gt;→ &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/gardener/" &gt;Gardener on Cleura — technical notes&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="steps"&gt;Steps
&lt;/h2&gt;&lt;h3 id="1--shoot-cluster"&gt;1 — Shoot cluster
&lt;/h3&gt;&lt;p&gt;Provision a Gardener shoot cluster on Cleura. Cleura wraps Gardener behind their own REST API — &lt;code&gt;gardenctl&lt;/code&gt; and the Gardener Terraform provider require the garden cluster kubeconfig, which Cleura does not expose. Cluster lifecycle goes through their REST API instead.&lt;/p&gt;
&lt;p&gt;→ &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/gardener/#provisioning-a-shoot-cluster-on-cleura" &gt;Provisioning via Cleura REST API&lt;/a&gt;&lt;br&gt;
→ &lt;a class="link" href="https://github.com/cleura/docs/issues/533" target="_blank" rel="noopener"
 &gt;Cleura docs issue #533 — IaC and gardenctl access&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="2--minecraft-via-standard-loadbalancer"&gt;2 — Minecraft via standard LoadBalancer
&lt;/h3&gt;&lt;p&gt;Deploy &lt;a class="link" href="https://github.com/itzg/docker-minecraft-server" target="_blank" rel="noopener"
 &gt;&lt;code&gt;itzg/minecraft-server&lt;/code&gt;&lt;/a&gt; as a StatefulSet with a plain &lt;code&gt;LoadBalancer&lt;/code&gt; service for TCP 25565 — the direct Octavia path, no Gateway involved. Gets the server running quickly and confirms TCP exposure works on Cleura independently.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Internet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TCP 25565
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Octavia LB (direct LoadBalancer service)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Minecraft Pod (itzg/minecraft-server)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PVC (Cinder)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Manifests: &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/scripts/mc/stateful_set.yaml" &gt;stateful_set.yaml&lt;/a&gt; · &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/scripts/mc/service.yaml" &gt;service.yaml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Apply directly from this repo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -k &lt;span style="color:#e6db74"&gt;&amp;#34;https://github.com/Backend-Engineering-Strategy-Tools/site//static/scripts/mc?ref=main&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check the rollout and grab the external IP:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get all -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;mc-example
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get svc mc-example &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.status.loadBalancer.ingress[0].ip}:{.spec.ports[0].port}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="25--migrate-to-helm-chart"&gt;2.5 — Migrate to Helm chart
&lt;/h3&gt;&lt;p&gt;Swap the raw manifests for the &lt;a class="link" href="https://github.com/itzg/minecraft-server-charts" target="_blank" rel="noopener"
 &gt;&lt;code&gt;itzg/minecraft-server-charts&lt;/code&gt;&lt;/a&gt; Helm chart — actively maintained, covers server type, persistence, RCON, backups, and extra ports (BlueMap, Dynmap). The raw YAML stays useful as a reference for the underlying shape.&lt;/p&gt;
&lt;p&gt;Manifests: &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/scripts/mc-helm/values.yaml" &gt;values.yaml&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo add minecraft-server-charts https://itzg.github.io/minecraft-server-charts/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm upgrade --install mc minecraft-server-charts/minecraft &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -f https://backend-engineering-strategy-tools.github.io/site/scripts/mc-helm/values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check the rollout and grab the external IP:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get all -l app.kubernetes.io/instance&lt;span style="color:#f92672"&gt;=&lt;/span&gt;mc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get svc mc-example -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.status.loadBalancer.ingress[0].ip}:{.spec.ports[0].port}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3--envoy-gateway"&gt;3 — Envoy Gateway
&lt;/h3&gt;&lt;p&gt;Deploy &lt;a class="link" href="https://gateway.envoyproxy.io/" target="_blank" rel="noopener"
 &gt;Envoy Gateway&lt;/a&gt; into the shoot cluster — the CNCF implementation of the &lt;a class="link" href="https://gateway-api.sigs.k8s.io/" target="_blank" rel="noopener"
 &gt;Kubernetes Gateway API&lt;/a&gt;. The NGINX Ingress Controller is deprecated; Gateway API is the forward path with a standardised spec for both HTTP and TCP.&lt;/p&gt;
&lt;p&gt;Envoy Gateway exposes a single &lt;code&gt;LoadBalancer&lt;/code&gt; service via Octavia. Everything routes through it.&lt;/p&gt;
&lt;h3 id="4--httproute-certificates-and-bluemap"&gt;4 — HTTPRoute, certificates, and BlueMap
&lt;/h3&gt;&lt;p&gt;Deploy &lt;a class="link" href="https://bluemap.bluecolored.de/" target="_blank" rel="noopener"
 &gt;BlueMap&lt;/a&gt; — a Minecraft mod that renders the world as a live 3D web map served over HTTP. Route it through the Gateway with a &lt;code&gt;HTTPRoute&lt;/code&gt; and wire &lt;a class="link" href="https://cert-manager.io/" target="_blank" rel="noopener"
 &gt;cert-manager&lt;/a&gt; to provision a Let&amp;rsquo;s Encrypt certificate.&lt;/p&gt;
&lt;p&gt;A real HTTP service with a real use, not a throwaway test page. Validates the full HTTP + TLS path before touching the game server.&lt;/p&gt;
&lt;h3 id="5--migrate-to-tcproute"&gt;5 — Migrate to TCPRoute
&lt;/h3&gt;&lt;p&gt;Migrate the TCP service to a &lt;code&gt;TCPRoute&lt;/code&gt; through Envoy Gateway. &lt;code&gt;TCPRoute&lt;/code&gt; is in the Gateway API experimental channel — this step validates that a single Gateway handles both HTTP and raw TCP.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Internet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Octavia LB (one Gateway LoadBalancer)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Envoy Gateway
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+------------------------------+------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTTPRoute → BlueMap TCPRoute → Minecraft
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="6--velocity-if-needed"&gt;6 — Velocity (if needed)
&lt;/h3&gt;&lt;p&gt;Add &lt;a class="link" href="https://papermc.io/software/velocity" target="_blank" rel="noopener"
 &gt;Velocity&lt;/a&gt; as a TCP proxy in front of the Minecraft server if multi-server routing becomes relevant — lobby, modded, survival as separate backends. Skip if a single server is enough.&lt;/p&gt;
&lt;p&gt;→ &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/projects/minecraft/" &gt;Minecraft project&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="7--plugin-pipeline"&gt;7 — Plugin pipeline
&lt;/h3&gt;&lt;p&gt;A colleague is building a Minecraft plugin. The goal is a &lt;a class="link" href="https://dagger.io/" target="_blank" rel="noopener"
 &gt;Dagger&lt;/a&gt; pipeline with GitHub Actions — the same build running locally and in CI, covering the JVM toolchain and packaging steps.&lt;/p&gt;
&lt;h3 id="8--ai"&gt;8 — AI
&lt;/h3&gt;&lt;p&gt;Something with NPC behaviour, a bot, or plugin-side automation. Low priority, high fun.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="iac-gap"&gt;IaC gap
&lt;/h2&gt;&lt;p&gt;Cleura does not expose the garden cluster kubeconfig. That one limitation closes off the entire Gardener tooling ecosystem: &lt;code&gt;gardenctl&lt;/code&gt; requires it, the &lt;a class="link" href="https://registry.terraform.io/providers/gardener/gardener" target="_blank" rel="noopener"
 &gt;Gardener Terraform provider&lt;/a&gt; requires it, and any Crossplane provider built on the Gardener API would require it too. There is no HCL path here.&lt;/p&gt;
&lt;p&gt;What remains is Cleura&amp;rsquo;s own REST API — which is fine for interactive use but falls short the moment you want to drive cluster lifecycle from a pipeline. A bash script wrapping &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; works, and that is what &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/scripts/cleura-shoot.sh" &gt;cleura-shoot.sh&lt;/a&gt; does, but it is a workaround rather than a solution. No state, no plan, no diff — just imperative API calls.&lt;/p&gt;
&lt;p&gt;Options if this needs to graduate beyond a script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Crossplane &lt;code&gt;provider-http&lt;/code&gt;&lt;/strong&gt; — can wrap the REST API declaratively, but has no native polling or deletion hooks, so the reconciliation story is awkward&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom Terraform provider&lt;/strong&gt; — full &lt;code&gt;plan&lt;/code&gt;/&lt;code&gt;apply&lt;/code&gt; semantics, but requires writing a Go provider from scratch&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pulumi dynamic provider&lt;/strong&gt; — similar effort, Python or TypeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A feature request for gardenctl access or a native IaC provider has been filed with Cleura (→ &lt;a class="link" href="https://github.com/cleura/docs/issues/533" target="_blank" rel="noopener"
 &gt;cleura/docs#533&lt;/a&gt;). Until something changes there, the bash script is as good as it gets.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="status"&gt;Status
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Step&lt;/th&gt;
 &lt;th&gt;Status&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1 — Shoot cluster on Cleura&lt;/td&gt;
 &lt;td&gt;done&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2 — Minecraft via LoadBalancer (itzg)&lt;/td&gt;
 &lt;td&gt;planned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2.5 — Migrate to Helm chart&lt;/td&gt;
 &lt;td&gt;planned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3 — Envoy Gateway&lt;/td&gt;
 &lt;td&gt;planned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4 — HTTPRoute + cert-manager + BlueMap&lt;/td&gt;
 &lt;td&gt;planned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5 — Migrate to TCPRoute&lt;/td&gt;
 &lt;td&gt;planned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6 — Velocity&lt;/td&gt;
 &lt;td&gt;planned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7 — Plugin pipeline (Dagger)&lt;/td&gt;
 &lt;td&gt;planned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;8 — AI&lt;/td&gt;
 &lt;td&gt;planned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Building this out — notes will expand as each step lands.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Gardener on Cleura</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/gardener/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/gardener/</guid><description>&lt;p&gt;&lt;a class="link" href="https://gardener.cloud/" target="_blank" rel="noopener"
 &gt;Gardener&lt;/a&gt; is a Kubernetes-as-a-Service framework that runs on Kubernetes and manages the lifecycle of other clusters declaratively. Rather than managing control planes by hand, Gardener treats clusters as a resource — defined, created, upgraded, and deleted via the Gardener API.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="concepts"&gt;Concepts
&lt;/h2&gt;&lt;p&gt;Gardener uses three layers:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Layer&lt;/th&gt;
 &lt;th&gt;What it is&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Garden cluster&lt;/td&gt;
 &lt;td&gt;Runs Gardener itself — the management control plane&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Seed cluster&lt;/td&gt;
 &lt;td&gt;Hosts the control planes of shoot clusters (as pods)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Shoot cluster&lt;/td&gt;
 &lt;td&gt;The cluster you actually use — nodes run on the target cloud&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The shoot cluster&amp;rsquo;s API server does not run on the shoot nodes. It runs as a pod inside the seed cluster. From the outside it behaves like any other Kubernetes cluster; internally the control plane is isolated from the data plane.&lt;/p&gt;
&lt;p&gt;Shoot clusters are defined as &lt;code&gt;Shoot&lt;/code&gt; resources applied to the garden cluster:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;core.gardener.cloud/v1beta1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Shoot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-cluster&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;garden-my-project&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cloudProfileName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;openstack&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;region&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;sto2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;provider&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;openstack&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;workers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker-pool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;machine&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;l2.c2r4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;minimum&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;maximum&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kubernetes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1.30&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networking&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;calico&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;pods&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100.128.0.0&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;/11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nodes&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10.250.0.0&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;/16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;services&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100.112.0.0&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;/13&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="shoot-cluster-on-cleura"&gt;Shoot cluster on Cleura
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://cleura.com/" target="_blank" rel="noopener"
 &gt;Cleura&lt;/a&gt; is a European OpenStack provider. Gardener provisions shoot nodes as OpenStack VMs via the OpenStack machine controller.&lt;/p&gt;
&lt;p&gt;Key integrations:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component&lt;/th&gt;
 &lt;th&gt;Implementation&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Node provisioning&lt;/td&gt;
 &lt;td&gt;OpenStack VMs via Gardener machine controller&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Load balancers&lt;/td&gt;
 &lt;td&gt;Octavia via cloud-controller-manager&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Block storage&lt;/td&gt;
 &lt;td&gt;Cinder via CSI driver&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DNS&lt;/td&gt;
 &lt;td&gt;Manual or external-dns&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CNI&lt;/td&gt;
 &lt;td&gt;Calico (default) or configurable&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Gardener on Cleura does not provide an ingress controller or API gateway — these are brought in separately.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="networking"&gt;Networking
&lt;/h2&gt;&lt;p&gt;Gardener manages the cluster network configuration as part of the shoot spec. Pod, node, and service CIDRs are defined at cluster creation and must not overlap with the OpenStack network.&lt;/p&gt;
&lt;p&gt;On Cleura, nodes get OpenStack floating IPs for egress. Pod-to-pod traffic stays within the cluster overlay network (Calico by default). Traffic entering from outside the cluster goes through a &lt;code&gt;LoadBalancer&lt;/code&gt; service — either directly for raw TCP, or via a gateway controller for HTTP.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="ingress--classic-vs-gateway-api"&gt;Ingress — classic vs Gateway API
&lt;/h2&gt;&lt;p&gt;The classic Kubernetes &lt;code&gt;Ingress&lt;/code&gt; resource is HTTP-only, has no TCP support, and its feature set varies across implementations via non-standard annotations. The NGINX Ingress Controller — the most widely used implementation — is deprecated; NGINX now focuses on their &lt;a class="link" href="https://github.com/nginxinc/nginx-gateway-fabric" target="_blank" rel="noopener"
 &gt;Gateway API implementation&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;The &lt;a class="link" href="https://gateway-api.sigs.k8s.io/" target="_blank" rel="noopener"
 &gt;Kubernetes Gateway API&lt;/a&gt; is the forward path — a set of CRDs (&lt;code&gt;Gateway&lt;/code&gt;, &lt;code&gt;HTTPRoute&lt;/code&gt;, &lt;code&gt;TCPRoute&lt;/code&gt;, &lt;code&gt;TLSRoute&lt;/code&gt;) with a standardized spec and first-class support for both HTTP and TCP.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Resource&lt;/th&gt;
 &lt;th&gt;Protocol&lt;/th&gt;
 &lt;th&gt;API&lt;/th&gt;
 &lt;th&gt;Status&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Ingress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;HTTP only&lt;/td&gt;
 &lt;td&gt;Kubernetes&lt;/td&gt;
 &lt;td&gt;Stable, legacy&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;HTTPRoute&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;HTTP/HTTPS&lt;/td&gt;
 &lt;td&gt;Gateway API&lt;/td&gt;
 &lt;td&gt;Stable&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;TCPRoute&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Raw TCP&lt;/td&gt;
 &lt;td&gt;Gateway API&lt;/td&gt;
 &lt;td&gt;Experimental&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;TLSRoute&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;TLS passthrough&lt;/td&gt;
 &lt;td&gt;Gateway API&lt;/td&gt;
 &lt;td&gt;Experimental&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="envoy-gateway"&gt;Envoy Gateway
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://gateway.envoyproxy.io/" target="_blank" rel="noopener"
 &gt;Envoy Gateway&lt;/a&gt; is the CNCF implementation of the Kubernetes Gateway API using &lt;a class="link" href="https://www.envoyproxy.io/" target="_blank" rel="noopener"
 &gt;Envoy&lt;/a&gt; as the data plane. It supports &lt;code&gt;HTTPRoute&lt;/code&gt;, &lt;code&gt;TCPRoute&lt;/code&gt;, and &lt;code&gt;TLSRoute&lt;/code&gt; through a single &lt;code&gt;Gateway&lt;/code&gt; resource — one entry point, both protocols.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Octavia LB ← one LoadBalancer service per Gateway listener
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Envoy Gateway pod
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+------------------+------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTTPRoute → ClusterIP pods TCPRoute → ClusterIP pods
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Envoy Gateway is deployed into the shoot cluster and exposes a &lt;code&gt;LoadBalancer&lt;/code&gt; service via Octavia, the same as any other service. The Gateway API resources then declare what routes through it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="tcproute--declaring-tcp-services"&gt;TCPRoute — declaring TCP services
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;TCPRoute&lt;/code&gt; attaches to a &lt;code&gt;Gateway&lt;/code&gt; listener and routes raw TCP traffic to a backend service. This is how a non-HTTP workload (e.g. a game server, a database proxy, a custom protocol service) gets exposed through the Gateway API rather than a standalone &lt;code&gt;LoadBalancer&lt;/code&gt; service.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gateway.networking.k8s.io/v1alpha2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TCPRoute&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-tcp-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;parentRefs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-gateway&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;sectionName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tcp-listener&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rules&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;backendRefs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1234&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The corresponding &lt;code&gt;Gateway&lt;/code&gt; listener:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Gateway&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-gateway&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gatewayClassName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;envoy-gateway&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listeners&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tcp-listener&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;protocol&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TCP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1234&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http-listener&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;protocol&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;HTTP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One Gateway, both protocols declared explicitly. The &lt;code&gt;TCPRoute&lt;/code&gt; API is in the experimental channel and requires opting in when installing Envoy Gateway.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="httproute--http-services"&gt;HTTPRoute — HTTP services
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;HTTPRoute&lt;/code&gt; handles HTTP and HTTPS traffic with routing by hostname, path, header, or method — without annotations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;HTTPRoute&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-http-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;parentRefs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-gateway&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;sectionName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http-listener&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hostnames&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;my-app.example.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rules&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;matches&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;path&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;PathPrefix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;backendRefs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="loadbalancer--direct-tcp-via-octavia"&gt;LoadBalancer — direct TCP via Octavia
&lt;/h2&gt;&lt;p&gt;For cases where a &lt;code&gt;TCPRoute&lt;/code&gt; is not appropriate (or the Gateway API experimental channel is not enabled), a &lt;code&gt;LoadBalancer&lt;/code&gt; service provisions an Octavia LB directly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-tcp-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;LoadBalancer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;selector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1234&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;targetPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1234&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;protocol&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TCP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Annotations control Octavia behaviour — timeouts, health check parameters, internal vs external. These are provider-specific and not standardised across OpenStack deployments.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="storage"&gt;Storage
&lt;/h2&gt;&lt;p&gt;Cinder block volumes are available via the CSI driver. A &lt;code&gt;PersistentVolumeClaim&lt;/code&gt; provisions a Cinder volume automatically using the cluster&amp;rsquo;s default storage class.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;accessModes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;ReadWriteOnce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resources&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;requests&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;storage&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;20Gi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cinder volumes are &lt;code&gt;ReadWriteOnce&lt;/code&gt; — they attach to a single node. For stateful workloads, use &lt;code&gt;StatefulSet&lt;/code&gt; rather than &lt;code&gt;Deployment&lt;/code&gt; to get stable volume binding across pod restarts.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="provisioning-a-shoot-cluster-on-cleura"&gt;Provisioning a shoot cluster on Cleura
&lt;/h2&gt;&lt;p&gt;Cleura wraps Gardener behind their own REST API at &lt;code&gt;rest.cleura.cloud&lt;/code&gt;. The garden cluster kubeconfig is not exposed — &lt;code&gt;gardenctl&lt;/code&gt; does not work directly. Cluster lifecycle is managed through HTTP calls.&lt;/p&gt;
&lt;h3 id="authentication"&gt;Authentication
&lt;/h3&gt;&lt;p&gt;Every call requires a token obtained once per session:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -s -X POST https://rest.cleura.cloud/auth/v1/tokens &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;auth&amp;#34;: {&amp;#34;login&amp;#34;: &amp;#34;you@example.com&amp;#34;, &amp;#34;password&amp;#34;: &amp;#34;yourpass&amp;#34;}}&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | jq &lt;span style="color:#e6db74"&gt;&amp;#39;{token: .token}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pass &lt;code&gt;X-AUTH-LOGIN&lt;/code&gt; and &lt;code&gt;X-AUTH-TOKEN&lt;/code&gt; headers on all subsequent calls.&lt;/p&gt;
&lt;h3 id="bootstrap-once-per-projectregion"&gt;Bootstrap (once per project/region)
&lt;/h3&gt;&lt;p&gt;Before creating any clusters, the project must be bootstrapped — this wires up the OpenStack credentials that Gardener uses to provision nodes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -X POST &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; https://rest.cleura.cloud/gardener/v1/public/secret/kna1/&lt;span style="color:#f92672"&gt;{&lt;/span&gt;projectId&lt;span style="color:#f92672"&gt;}&lt;/span&gt;/bootstrap &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-AUTH-LOGIN: ...&amp;#34;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-AUTH-TOKEN: ...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Safe to call repeatedly; idempotent.&lt;/p&gt;
&lt;h3 id="create-a-shoot-cluster"&gt;Create a shoot cluster
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -X POST &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; https://rest.cleura.cloud/gardener/v1/public/shoot/kna1/&lt;span style="color:#f92672"&gt;{&lt;/span&gt;projectId&lt;span style="color:#f92672"&gt;}&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-AUTH-LOGIN: ...&amp;#34;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-AUTH-TOKEN: ...&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;shoot&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;name&amp;#34;: &amp;#34;my-cluster&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;kubernetes&amp;#34;: {&amp;#34;version&amp;#34;: &amp;#34;1.31.0&amp;#34;},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;provider&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;infrastructureConfig&amp;#34;: {&amp;#34;floatingPoolName&amp;#34;: &amp;#34;ext-net&amp;#34;},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;workers&amp;#34;: [{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;name&amp;#34;: &amp;#34;default&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;machine&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;type&amp;#34;: &amp;#34;4C-8GB-50GB&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;image&amp;#34;: {&amp;#34;name&amp;#34;: &amp;#34;ubuntu&amp;#34;, &amp;#34;version&amp;#34;: &amp;#34;22.4.20230301&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;minimum&amp;#34;: 1,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;maximum&amp;#34;: 3,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;volume&amp;#34;: {&amp;#34;size&amp;#34;: &amp;#34;50Gi&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="poll-until-ready"&gt;Poll until ready
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl https://rest.cleura.cloud/gardener/v1/public/shoot/kna1/&lt;span style="color:#f92672"&gt;{&lt;/span&gt;projectId&lt;span style="color:#f92672"&gt;}&lt;/span&gt;/my-cluster &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-AUTH-LOGIN: ...&amp;#34;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-AUTH-TOKEN: ...&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | jq &lt;span style="color:#e6db74"&gt;&amp;#39;.lastOperation | {state, description, progress}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Poll until &lt;code&gt;lastOperation.state == &amp;quot;Succeeded&amp;quot;&lt;/code&gt;. Takes roughly 10–15 minutes on first provision.&lt;/p&gt;
&lt;h3 id="fetch-kubeconfig"&gt;Fetch kubeconfig
&lt;/h3&gt;&lt;p&gt;The Cleura docs reference two kubeconfig paths — &lt;code&gt;GET /kubeconfig&lt;/code&gt; (lowercase) and &lt;code&gt;POST /Kubeconfig&lt;/code&gt; (uppercase, different casing). Neither worked reliably in practice. The endpoint that actually returns a kubeconfig is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -s -X POST &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; https://rest.cleura.cloud/gardener/v1/public/shoot/kna1/&lt;span style="color:#f92672"&gt;{&lt;/span&gt;projectId&lt;span style="color:#f92672"&gt;}&lt;/span&gt;/my-cluster/adminkubeconfig &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-AUTH-LOGIN: ...&amp;#34;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-AUTH-TOKEN: ...&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;config&amp;#34;: {&amp;#34;expirationSeconds&amp;#34;: 3600}}&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | jq -r &amp;gt; my-cluster-kubeconfig.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;expirationSeconds&lt;/code&gt; field controls credential lifetime. A bug report has been filed with Cleura about the endpoint inconsistency — the &lt;code&gt;adminkubeconfig&lt;/code&gt; path is not documented.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Path&lt;/th&gt;
 &lt;th&gt;Method&lt;/th&gt;
 &lt;th&gt;Documented&lt;/th&gt;
 &lt;th&gt;Works&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;/kubeconfig&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;GET&lt;/td&gt;
 &lt;td&gt;yes&lt;/td&gt;
 &lt;td&gt;unclear&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;/Kubeconfig&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;POST&lt;/td&gt;
 &lt;td&gt;yes&lt;/td&gt;
 &lt;td&gt;unclear&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;/adminkubeconfig&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;POST&lt;/td&gt;
 &lt;td&gt;no&lt;/td&gt;
 &lt;td&gt;yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;→ &lt;a class="link" href="https://github.com/cleura/docs/issues/534" target="_blank" rel="noopener"
 &gt;Cleura docs issue #534 — kubeconfig endpoint inconsistencies in Gardener REST API&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="script"&gt;Script
&lt;/h3&gt;&lt;p&gt;A bash script wrapping the full workflow (list, create, wait, kubeconfig, delete) is available: &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/scripts/cleura-shoot.sh" &gt;cleura-shoot.sh&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export CLEURA_LOGIN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;you@example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export CLEURA_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;yourpass&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;./cleura-shoot.sh list
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;./cleura-shoot.sh create my-cluster
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;./cleura-shoot.sh wait my-cluster
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;./cleura-shoot.sh kubeconfig my-cluster
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;./cleura-shoot.sh delete my-cluster
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="iac-options"&gt;IaC options
&lt;/h3&gt;&lt;p&gt;No native Terraform provider exists for Cleura&amp;rsquo;s Gardener REST API. The Gardener Terraform provider (&lt;code&gt;registry.terraform.io/providers/gardener/gardener&lt;/code&gt;) requires the garden cluster kubeconfig, which Cleura does not expose. Options:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Approach&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Bash + curl&lt;/td&gt;
 &lt;td&gt;Minimal deps — just &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Crossplane &lt;code&gt;provider-http&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Declarative, Kubernetes-native, reconciliation loop&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Custom Terraform provider&lt;/td&gt;
 &lt;td&gt;Full &lt;code&gt;plan&lt;/code&gt;/&lt;code&gt;apply&lt;/code&gt; semantics — requires Go provider development&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Pulumi custom dynamic provider&lt;/td&gt;
 &lt;td&gt;Python/TypeScript, similar effort to custom Terraform provider&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item><item><title>BIFROST — Raspberry Pi jump node</title><link>https://backend-engineering-strategy-tools.github.io/site/homelab/bifrost/</link><pubDate>Fri, 05 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/homelab/bifrost/</guid><description>&lt;p&gt;The homelab needed a permanent always-on entry point — something low power, always reachable, a stable first hop. A first-gen Raspberry Pi in the rack fills that role. BIFROST.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="hardware"&gt;Hardware
&lt;/h2&gt;&lt;p&gt;Raspberry Pi 1 Model B running Raspbian, mounted in the rack with a &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/rack-3d-prints/" &gt;3D printed 1U mount&lt;/a&gt;. Draws under 2W at idle. Nothing runs on it except sshd.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="how-it-works"&gt;How it works
&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;ssh -p 22222 user@bifrost.mjnet.info
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The chain:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;bifrost.mjnet.info&lt;/code&gt; — Route53 CNAME pointing to &lt;code&gt;router.mjnet.info&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;router.mjnet.info&lt;/code&gt; — HEIMDAL (SYS-009), kept current via DDNS&lt;/li&gt;
&lt;li&gt;OPNsense port forward: external TCP 22222 → Pi:22&lt;/li&gt;
&lt;li&gt;OPNsense DNS override: &lt;code&gt;bifrost.mjnet.info&lt;/code&gt; → Pi&amp;rsquo;s internal IP&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The DNS override means the same hostname resolves to the internal IP when used inside the network — no split config needed in &lt;code&gt;~/.ssh/config&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="opnsense-config"&gt;OPNsense config
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Port forward&lt;/strong&gt; (Firewall → NAT → Port Forward):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interface: WAN&lt;/li&gt;
&lt;li&gt;Protocol: TCP&lt;/li&gt;
&lt;li&gt;Destination port: 22222&lt;/li&gt;
&lt;li&gt;Redirect target: Pi internal IP, port 22&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;DNS override&lt;/strong&gt; (Services → Unbound DNS → Host Overrides):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Host: &lt;code&gt;bifrost&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Domain: &lt;code&gt;mjnet.info&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;IP: Pi internal IP&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="ssh-config"&gt;SSH config
&lt;/h2&gt;&lt;p&gt;Add to &lt;code&gt;~/.ssh/config&lt;/code&gt; on any client:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Host bifrost
 HostName bifrost.mjnet.info
 Port 22222
 User pi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then &lt;code&gt;ssh bifrost&lt;/code&gt; from anywhere.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If a more robust solution becomes necessary later (no open ports, survives CGNAT), the &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/bifrost-rpi-options/" &gt;options doc&lt;/a&gt; covers Tailscale, Cloudflare Tunnel, and WireGuard.&lt;/p&gt;</description></item><item><title>Bastion / jump server</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/bastion/</link><pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/bastion/</guid><description>&lt;p&gt;A bastion host (jump server) is a single, hardened machine exposed to the outside that acts as the entry point into a private network. You SSH into the bastion, then hop from there to internal hosts — or configure your SSH client to do it transparently in one step.&lt;/p&gt;
&lt;p&gt;The pattern is simple: minimise the network attack surface to one well-monitored machine, harden that machine specifically, and keep everything else off the public internet.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="basic-jump-manual-two-hop"&gt;Basic jump: manual two-hop
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Step 1: SSH into the bastion&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh user@bastion.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Step 2: from bastion, SSH into internal host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh user@192.168.1.100
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Works, but requires your private key to be on the bastion — which you want to avoid.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="proxyjump-recommended"&gt;ProxyJump (recommended)
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;ProxyJump&lt;/code&gt; tells SSH to tunnel through the bastion transparently. Your key never leaves your local machine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# One-liner&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh -J user@bastion.example.com user@192.168.1.100
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Or in ~/.ssh/config&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host internal
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HostName 192.168.1.100
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User user
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ProxyJump bastion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host bastion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HostName bastion.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User user
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IdentityFile ~/.ssh/id_ed25519
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After this, &lt;code&gt;ssh internal&lt;/code&gt; works as a single command with no key on the bastion.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="agent-forwarding-vs-proxyjump"&gt;Agent forwarding vs ProxyJump
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Agent forwarding&lt;/strong&gt; (&lt;code&gt;-A&lt;/code&gt;, &lt;code&gt;ForwardAgent yes&lt;/code&gt;) forwards your SSH agent socket through the bastion so you can authenticate onward with your local key. Older approach — it works but the agent socket is briefly accessible on the bastion host, which is a lateral movement risk if the bastion is compromised.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ProxyJump&lt;/strong&gt; is strictly better for the common case: the connection to the internal host is established from your machine through an SSH tunnel, not from the bastion itself. No agent socket on the bastion.&lt;/p&gt;
&lt;p&gt;Use ProxyJump unless you specifically need agent forwarding for something else.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="hardening-basics"&gt;Hardening basics
&lt;/h2&gt;&lt;p&gt;A bastion is only useful if it&amp;rsquo;s actually hardened. Minimum:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /etc/ssh/sshd_config&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PasswordAuthentication no
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PermitRootLogin no
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AuthorizedKeysFile .ssh/authorized_keys
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AllowUsers jumpuser
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Key-only authentication — no passwords&lt;/li&gt;
&lt;li&gt;Dedicated user with no shell access to the bastion itself (optional: &lt;code&gt;ForceCommand&lt;/code&gt; to restrict what they can do)&lt;/li&gt;
&lt;li&gt;Non-standard port reduces log noise, not actual security&lt;/li&gt;
&lt;li&gt;fail2ban or equivalent for rate-limiting auth attempts&lt;/li&gt;
&lt;li&gt;Minimal installed software — the bastion should do one thing&lt;/li&gt;
&lt;li&gt;Regular log review (&lt;code&gt;/var/log/auth.log&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="restricting-to-jump-only"&gt;Restricting to jump-only
&lt;/h2&gt;&lt;p&gt;If you want users to be able to jump through the bastion but not get a shell on it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# In authorized_keys, prefix the key with:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;restrict,port-forwarding ssh-ed25519 AAAA...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or use &lt;code&gt;AllowTcpForwarding yes&lt;/code&gt; with &lt;code&gt;ForceCommand /usr/sbin/nologin&lt;/code&gt; — though the interaction between these options is subtle; test carefully.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="related"&gt;Related
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;For making the bastion reachable from outside without a public IP → &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunnels/" &gt;Tunnels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For exposing internal web services via public URLs → &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunneled-reverse-proxy/" &gt;Tunneled reverse proxy platforms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Dynamic DNS (DDNS)</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/ddns/</link><pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/ddns/</guid><description>&lt;p&gt;Most home internet connections have a dynamic IP — the ISP can reassign it at any time. Dynamic DNS (DDNS) keeps a DNS hostname pointed at whatever IP you currently have, by running a small client that detects changes and updates the DNS record automatically.&lt;/p&gt;
&lt;p&gt;Relevant when using &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunnels/" &gt;port forwarding or WireGuard&lt;/a&gt; to reach a private network from outside — you need a stable hostname to connect to.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="how-it-works"&gt;How it works
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;You register a hostname with a DDNS provider (e.g. &lt;code&gt;myhome.duckdns.org&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An update client runs on your router or a machine on your network&lt;/li&gt;
&lt;li&gt;The client periodically checks your public IP (or watches for changes) and calls the provider&amp;rsquo;s API to update the DNS record&lt;/li&gt;
&lt;li&gt;DNS TTL is kept short (60–300s) so changes propagate quickly&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="providers"&gt;Providers
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Provider&lt;/th&gt;
 &lt;th&gt;Cost&lt;/th&gt;
 &lt;th&gt;Domain&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://www.duckdns.org" target="_blank" rel="noopener"
 &gt;DuckDNS&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Free&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;*.duckdns.org&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Simple, no account required beyond OAuth login&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cloudflare&lt;/td&gt;
 &lt;td&gt;Free (if you own a domain)&lt;/td&gt;
 &lt;td&gt;Your own domain&lt;/td&gt;
 &lt;td&gt;Best option if you already use Cloudflare for DNS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;No-IP&lt;/td&gt;
 &lt;td&gt;Free (limited)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;*.ddns.net&lt;/code&gt; etc.&lt;/td&gt;
 &lt;td&gt;Requires manual renewal every 30 days on free tier&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Dynu&lt;/td&gt;
 &lt;td&gt;Free&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;*.dynu.net&lt;/code&gt; etc.&lt;/td&gt;
 &lt;td&gt;More generous free tier than No-IP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Afraid.org&lt;/td&gt;
 &lt;td&gt;Free&lt;/td&gt;
 &lt;td&gt;Shared subdomains&lt;/td&gt;
 &lt;td&gt;Long-running community service&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare&lt;/strong&gt; is the best option if you own a domain — you get a real subdomain (&lt;code&gt;home.yourdomain.com&lt;/code&gt;), the API is reliable, and the client support is universal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DuckDNS&lt;/strong&gt; is the easiest if you don&amp;rsquo;t own a domain — no configuration beyond a token.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="opnsense"&gt;OPNsense
&lt;/h2&gt;&lt;p&gt;OPNsense has a built-in DDNS client under &lt;strong&gt;Services → Dynamic DNS&lt;/strong&gt;. Supports Cloudflare, DuckDNS, No-IP, Route53, and others out of the box.&lt;/p&gt;
&lt;p&gt;Configuration for Cloudflare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Service: &lt;code&gt;Cloudflare&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Hostname: &lt;code&gt;home&lt;/code&gt; (the subdomain to update)&lt;/li&gt;
&lt;li&gt;Domain: &lt;code&gt;yourdomain.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Username: your Cloudflare account email&lt;/li&gt;
&lt;li&gt;Password: Cloudflare API token with &lt;code&gt;Zone:DNS:Edit&lt;/code&gt; permission for the domain&lt;/li&gt;
&lt;li&gt;Check IP: leave default (uses OPNsense&amp;rsquo;s WAN interface)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OPNsense updates the record whenever the WAN IP changes, detected via interface monitoring.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="linux-update-clients"&gt;Linux update clients
&lt;/h2&gt;&lt;p&gt;If the router doesn&amp;rsquo;t have a built-in client (or you want updates from a specific host):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ddclient&lt;/strong&gt; — the standard, supports most providers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt install ddclient
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /etc/ddclient.conf (Cloudflare example)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;protocol&lt;span style="color:#f92672"&gt;=&lt;/span&gt;cloudflare
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;zone&lt;span style="color:#f92672"&gt;=&lt;/span&gt;yourdomain.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;login&lt;span style="color:#f92672"&gt;=&lt;/span&gt;your@email.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;password&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;api-token&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ttl&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;home.yourdomain.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;inadyn&lt;/strong&gt; — lighter alternative, similar provider support:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt install inadyn
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /etc/inadyn.conf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;provider cloudflare.com &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; username &lt;span style="color:#f92672"&gt;=&lt;/span&gt; your@email.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; password &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &amp;lt;api-token&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hostname &lt;span style="color:#f92672"&gt;=&lt;/span&gt; home.yourdomain.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ttl &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxied &lt;span style="color:#f92672"&gt;=&lt;/span&gt; false
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="limitations"&gt;Limitations
&lt;/h2&gt;&lt;p&gt;DDNS does not help if your ISP uses CGNAT — if your router&amp;rsquo;s WAN IP is a private address (10.x, 100.64.x, 192.168.x), port forwarding and DDNS will not work. See &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunnels/" &gt;Tunnels&lt;/a&gt; for options that work without a public IP.&lt;/p&gt;
&lt;p&gt;DNS propagation delay means there&amp;rsquo;s a brief window after an IP change where connections will fail. Keep TTL at 60–300s to minimise this.&lt;/p&gt;</description></item><item><title>Firewall and router OS options</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/router-os/</link><pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/router-os/</guid><description>&lt;p&gt;Options for running a software-defined firewall or router, from homelab appliances to full routing OS deployments.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-main-split-appliance-vs-routing-os"&gt;The main split: appliance vs routing OS
&lt;/h2&gt;&lt;p&gt;Most options fall into one of two categories:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Firewall appliances&lt;/strong&gt; (OPNsense, pfSense, IPFire) — web UI-first, designed around the perimeter firewall use case. NAT, DHCP, DNS, VPN, IDS/IPS out of the box. Routing is possible but secondary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Routing operating systems&lt;/strong&gt; (VyOS, MikroTik RouterOS, FRRouting) — CLI-first, designed around dynamic routing protocols (BGP, OSPF). Firewall rules exist but feel like an afterthought compared to the routing capabilities.&lt;/p&gt;
&lt;p&gt;For a homelab perimeter gateway: appliance. For BGP peering, complex routing topologies, or network-as-code: routing OS.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="opnsense"&gt;OPNsense
&lt;/h2&gt;&lt;p&gt;Open-source firewall and routing platform based on FreeBSD. Fork of pfSense, with a stronger emphasis on community ownership and more frequent security updates.&lt;/p&gt;
&lt;p&gt;Full gateway function: stateful firewall, NAT, DHCP, DNS (Unbound), TFTP/PXE, VPN (WireGuard, OpenVPN, IPsec), traffic shaping, IDS/IPS (Suricata), DDNS.&lt;/p&gt;
&lt;p&gt;BGP is available via the FRRouting plugin but is not a first-class feature — VyOS is better suited for BGP-heavy setups.&lt;/p&gt;
&lt;p&gt;→ &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/opnsense/" &gt;OPNsense reference&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: homelab perimeter gateway, home network, small office. The current actively-maintained community fork of the pfSense lineage.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="pfsense"&gt;pfSense
&lt;/h2&gt;&lt;p&gt;The original FreeBSD-based firewall appliance. Same underlying capabilities as OPNsense — they share a common ancestor (m0n0wall).&lt;/p&gt;
&lt;p&gt;Now owned by Netgate. The &lt;strong&gt;Community Edition (CE)&lt;/strong&gt; remains open source; &lt;strong&gt;pfSense Plus&lt;/strong&gt; is commercial and ships only on Netgate hardware or as a cloud image. Development focus has shifted toward Plus; CE updates have been slower.&lt;/p&gt;
&lt;p&gt;The practical difference between OPNsense and pfSense CE is increasingly small at the feature level. The main reasons to choose one over the other are familiarity, UI preference, and update cadence. OPNsense is the more actively developed option for community use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: environments where pfSense is already deployed, or where existing documentation/tooling targets it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="vyos"&gt;VyOS
&lt;/h2&gt;&lt;p&gt;Open-source network OS built on Debian. Configured via a CLI with a commit/rollback model (similar to Juniper JunOS). Native BGP, OSPF, IS-IS via FRRouting.&lt;/p&gt;
&lt;p&gt;Configuration is declarative and version-controlled — the entire running config is a text file, which makes it automation-friendly (Ansible, Terraform).&lt;/p&gt;
&lt;p&gt;The rolling release is free; LTS releases require a subscription.&lt;/p&gt;
&lt;p&gt;→ &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/vyos/" &gt;VyOS reference&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: BGP peering, complex routing topologies, automation-driven network config, VM-based routing inside a cluster.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mikrotik-routeros"&gt;MikroTik RouterOS
&lt;/h2&gt;&lt;p&gt;Commercial OS that runs on MikroTik hardware and as a VM (CHR — Cloud Hosted Router). Full routing OS with BGP, OSPF, MPLS, and a firewall. Configured via Winbox GUI, web UI, or CLI.&lt;/p&gt;
&lt;p&gt;Very capable at the price point. Hardware is inexpensive. The learning curve is steeper than OPNsense but shallower than VyOS for most tasks. Community is large and documentation is thorough.&lt;/p&gt;
&lt;p&gt;CHR (the VM version) is free for speeds up to 1Mbps; licensed tiers above that. On physical hardware, the license is included.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: cost-conscious deployments that need routing features, or environments already using MikroTik hardware.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="ipfire"&gt;IPFire
&lt;/h2&gt;&lt;p&gt;Linux-based firewall focused on simplicity and security hardening. Web UI, stateful firewall, IDS (Snort/Suricata), VPN (OpenVPN, WireGuard, IPsec), proxy.&lt;/p&gt;
&lt;p&gt;Less feature-rich than OPNsense but lighter and more opinionated. No BGP. Easier to get to a secure baseline quickly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: simple gateway where you want a small attack surface and don&amp;rsquo;t need advanced routing or a plugin ecosystem.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="untangle--arista-edge-threat-management"&gt;Untangle / Arista Edge Threat Management
&lt;/h2&gt;&lt;p&gt;Commercial product with a free tier (NG Firewall). Web UI, application-layer filtering, content inspection, threat management features. More enterprise-oriented than the others.&lt;/p&gt;
&lt;p&gt;Requires registration. The free tier is limited; the feature set that differentiates it from OPNsense is mostly in the commercial tiers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: environments that need application-layer filtering with a managed UI, or commercial support requirements.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="comparison"&gt;Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;OPNsense&lt;/th&gt;
 &lt;th&gt;pfSense CE&lt;/th&gt;
 &lt;th&gt;VyOS&lt;/th&gt;
 &lt;th&gt;MikroTik RouterOS&lt;/th&gt;
 &lt;th&gt;IPFire&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Base OS&lt;/td&gt;
 &lt;td&gt;FreeBSD&lt;/td&gt;
 &lt;td&gt;FreeBSD&lt;/td&gt;
 &lt;td&gt;Debian&lt;/td&gt;
 &lt;td&gt;Proprietary&lt;/td&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Primary interface&lt;/td&gt;
 &lt;td&gt;Web UI&lt;/td&gt;
 &lt;td&gt;Web UI&lt;/td&gt;
 &lt;td&gt;CLI&lt;/td&gt;
 &lt;td&gt;Winbox / CLI&lt;/td&gt;
 &lt;td&gt;Web UI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;BGP / OSPF&lt;/td&gt;
 &lt;td&gt;Plugin (FRR)&lt;/td&gt;
 &lt;td&gt;Plugin (FRR)&lt;/td&gt;
 &lt;td&gt;Native (FRR)&lt;/td&gt;
 &lt;td&gt;Native&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;IDS/IPS&lt;/td&gt;
 &lt;td&gt;Suricata&lt;/td&gt;
 &lt;td&gt;Snort/Suricata&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Snort/Suricata&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;WireGuard&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes (Plus)&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DDNS&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Via script&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cost&lt;/td&gt;
 &lt;td&gt;Free&lt;/td&gt;
 &lt;td&gt;Free (CE)&lt;/td&gt;
 &lt;td&gt;Free (rolling)&lt;/td&gt;
 &lt;td&gt;Hardware license&lt;/td&gt;
 &lt;td&gt;Free&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Community&lt;/td&gt;
 &lt;td&gt;Active&lt;/td&gt;
 &lt;td&gt;Slowing (CE)&lt;/td&gt;
 &lt;td&gt;Active&lt;/td&gt;
 &lt;td&gt;Active&lt;/td&gt;
 &lt;td&gt;Active&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="further-reading"&gt;Further reading
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/opnsense/" &gt;OPNsense in the homelab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/vyos-bgp/" &gt;VyOS + BGP in the homelab&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Tunneled reverse proxy platforms</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunneled-reverse-proxy/</link><pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunneled-reverse-proxy/</guid><description>&lt;p&gt;A step beyond raw tunnels. These platforms expose services running on a private network as public HTTPS URLs — no open ports, no public IP required. The key difference from a VPN: you don&amp;rsquo;t get network-level access to the remote machine, you get a link to a specific service.&lt;/p&gt;
&lt;p&gt;Typical flow:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Internet → public server (VPS) → tunnel → private network → your service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The private machine maintains an outbound connection to the public server. Inbound traffic arrives at the public server and is forwarded back through the tunnel to the service.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="pangolin"&gt;Pangolin
&lt;/h2&gt;&lt;p&gt;Self-hosted platform built specifically for this use case. You run the server component on a VPS; client agents (called Newt) run on machines inside your private network and establish outbound tunnels. Services get public HTTPS URLs. Access control is built in — you can gate services behind auth without touching the service itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Components&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pangolin&lt;/strong&gt; — the server, runs on a VPS, handles routing and auth&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Newt&lt;/strong&gt; — the tunnel client, runs on private machines, connects out to Pangolin&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gerbil&lt;/strong&gt; — WireGuard-based tunnel layer (handled automatically)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traefik&lt;/strong&gt; — reverse proxy, managed by Pangolin for routing&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# On the VPS (docker-compose)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Pangolin + Traefik + Gerbil run together&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# On a private machine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -e SERVER_URL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;https://pangolin.yourdomain.com &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e TUNNEL_SECRET&lt;span style="color:#f92672"&gt;=&lt;/span&gt;... &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fosrl/newt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Services then appear at subdomains: &lt;code&gt;service.yourdomain.com&lt;/code&gt; — proxied through the tunnel, with optional SSO/auth in front.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Self-hosted&lt;/td&gt;
 &lt;td&gt;Yes — you own the VPS and the data&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports on private network&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;VPS only (not home)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Auth built in&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cost&lt;/td&gt;
 &lt;td&gt;Free, open source&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Maturity&lt;/td&gt;
 &lt;td&gt;Newer project (2024–)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Good fit for homelabs where you want public-facing services but don&amp;rsquo;t want to expose your home IP or open ports on your router.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="ngrok"&gt;ngrok
&lt;/h2&gt;&lt;p&gt;The original in this space. Run a single command, get a public URL. No server to manage — ngrok&amp;rsquo;s infrastructure handles it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ngrok http &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# → https://abc123.ngrok.io&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The URL changes on every restart unless you&amp;rsquo;re on a paid tier. Custom domains, persistent tunnels, and traffic inspection (useful for debugging webhooks) are paid features.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Self-hosted&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports on private network&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Auth built in&lt;/td&gt;
 &lt;td&gt;Yes (paid)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cost&lt;/td&gt;
 &lt;td&gt;Free tier limited; paid for custom domains&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Maturity&lt;/td&gt;
 &lt;td&gt;Established, widely used&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Best for: quick testing, webhook development, demos. Not ideal for a permanent homelab setup due to the dependency and cost at scale.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="frp-fast-reverse-proxy"&gt;frp (Fast Reverse Proxy)
&lt;/h2&gt;&lt;p&gt;Lightweight, self-hosted. You run &lt;code&gt;frps&lt;/code&gt; (server) on a VPS and &lt;code&gt;frpc&lt;/code&gt; (client) on private machines. More DIY than Pangolin — you configure the routing yourself, no built-in auth or dashboard.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# frps.toml (on VPS)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;bindPort&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;7000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# frpc.toml (on private machine)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serverAddr&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;your-vps-ip&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serverPort&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;7000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[[proxies]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;my-service&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;type&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;localPort&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;customDomains&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;[&amp;#34;service.yourdomain.com&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Self-hosted&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports on private network&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Auth built in&lt;/td&gt;
 &lt;td&gt;Basic (token auth between client/server)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cost&lt;/td&gt;
 &lt;td&gt;Free, open source&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Maturity&lt;/td&gt;
 &lt;td&gt;Established, widely used in homelab&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Good fit if you want full control and are comfortable wiring things together. No UI — config files only.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="expose-beyondcode"&gt;Expose (BeyondCode)
&lt;/h2&gt;&lt;p&gt;PHP-based self-hosted ngrok alternative. Dashboard included, custom domains, multiple tunnels.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;expose share http://localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Less common than frp or Pangolin, but polished for a self-hosted tool.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="comparison"&gt;Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Pangolin&lt;/th&gt;
 &lt;th&gt;ngrok&lt;/th&gt;
 &lt;th&gt;frp&lt;/th&gt;
 &lt;th&gt;Expose&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Self-hosted&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Auth / access control&lt;/td&gt;
 &lt;td&gt;Yes (built-in)&lt;/td&gt;
 &lt;td&gt;Paid&lt;/td&gt;
 &lt;td&gt;No (DIY)&lt;/td&gt;
 &lt;td&gt;Basic&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Dashboard&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Custom domains&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Paid&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Complexity&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;td&gt;Low&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Maturity&lt;/td&gt;
 &lt;td&gt;Newer&lt;/td&gt;
 &lt;td&gt;Established&lt;/td&gt;
 &lt;td&gt;Established&lt;/td&gt;
 &lt;td&gt;Moderate&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="when-to-use-which"&gt;When to use which
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Pangolin&lt;/strong&gt; if you want a self-hosted, permanent setup with auth and public URLs for homelab services. The right choice if you&amp;rsquo;re already running a VPS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ngrok&lt;/strong&gt; for quick tests, webhook development, or one-off sharing. Not for permanent services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;frp&lt;/strong&gt; if you want maximum control and minimal overhead, and are comfortable configuring a reverse proxy separately.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="related"&gt;Related
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;For network-level access (not service URLs) → &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunnels/" &gt;Tunnels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For SSH entry points → &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/bastion/" &gt;Bastion / jump server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Tunnels — remote network access</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunnels/</link><pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunnels/</guid><description>&lt;p&gt;Approaches for accessing a private network (home lab, office) from outside, when you don&amp;rsquo;t have a static public IP and may be behind NAT or CGNAT.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="first-check-if-youre-behind-cgnat"&gt;First: check if you&amp;rsquo;re behind CGNAT
&lt;/h2&gt;&lt;p&gt;Run on a device inside the network:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl ifconfig.me
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compare the result to the WAN IP shown in your router. If they match — you have a real public IP (possibly dynamic). If they differ — you&amp;rsquo;re behind CGNAT, and port forwarding will not work regardless of router config.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="tailscale"&gt;Tailscale
&lt;/h2&gt;&lt;p&gt;WireGuard-based mesh VPN. Each device gets a node in a private overlay network coordinated by Tailscale&amp;rsquo;s control plane. Connections are peer-to-peer where possible; relay servers (DERP) handle cases where direct traversal fails.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -fsSL https://tailscale.com/install.sh | sh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tailscale up
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Subnet routing&lt;/strong&gt;: one node (e.g. a Pi on the LAN) advertises the local subnet. Other Tailscale nodes route through it — the whole LAN becomes reachable, not just that one node.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tailscale up --advertise-routes&lt;span style="color:#f92672"&gt;=&lt;/span&gt;192.168.1.0/24
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Exit node&lt;/strong&gt;: route all internet traffic through a Tailscale node — useful on untrusted networks.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports required&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Works through CGNAT&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Third-party dependency&lt;/td&gt;
 &lt;td&gt;Yes (coordination server)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Free tier&lt;/td&gt;
 &lt;td&gt;100 devices&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Self-hostable&lt;/td&gt;
 &lt;td&gt;Yes — &lt;a class="link" href="https://github.com/juanfont/headscale" target="_blank" rel="noopener"
 &gt;Headscale&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The coordination server must be reachable to establish new connections. Existing sessions survive outages. Headscale replaces the coordination server while reusing the same clients.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="cloudflare-tunnel"&gt;Cloudflare Tunnel
&lt;/h2&gt;&lt;p&gt;The device on your private network runs &lt;code&gt;cloudflared&lt;/code&gt;, which holds an outbound connection to Cloudflare&amp;rsquo;s edge. Cloudflare proxies inbound traffic through it. No open ports, no public IP needed.&lt;/p&gt;
&lt;p&gt;Works for SSH, HTTP, and other TCP services. Native SSH support: access via &lt;code&gt;cloudflared access ssh&lt;/code&gt; or a browser terminal.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cloudflared tunnel login
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cloudflared tunnel create homelab
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cloudflared tunnel route dns homelab ssh.yourdomain.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cloudflared tunnel run homelab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports required&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Works through CGNAT&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Third-party dependency&lt;/td&gt;
 &lt;td&gt;Yes — traffic passes through Cloudflare&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Requires&lt;/td&gt;
 &lt;td&gt;Domain on Cloudflare&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cost&lt;/td&gt;
 &lt;td&gt;Free (Zero Trust free tier)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Traffic passes through Cloudflare&amp;rsquo;s infrastructure. For SSH this means an encrypted session transiting a third party&amp;rsquo;s edge.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="wireguard-self-hosted"&gt;WireGuard (self-hosted)
&lt;/h2&gt;&lt;p&gt;WireGuard server on a node in the private network. Remote clients connect with a config containing the server&amp;rsquo;s public key and endpoint. Encrypted point-to-point, no intermediary.&lt;/p&gt;
&lt;p&gt;Requires a real public IP at the WAN, UDP port forwarding on the router (default 51820), and DDNS if the IP is dynamic.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /etc/wireguard/wg0.conf (server)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[Interface]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Address&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;10.0.0.1/24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ListenPort&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;51820&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PrivateKey&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;lt;server-private-key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[Peer]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PublicKey&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;lt;client-public-key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;AllowedIPs&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;10.0.0.2/32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports required&lt;/td&gt;
 &lt;td&gt;Yes (UDP 51820)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Works through CGNAT&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Third-party dependency&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Complexity&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Full ownership, no external dependency, very fast. Key distribution is manual unless you layer a management tool on top (e.g. wg-easy, Netbird).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="port-forwarding--ddns"&gt;Port forwarding + DDNS
&lt;/h2&gt;&lt;p&gt;Forward a port on the router to the target host. Use a DDNS provider (DuckDNS, Cloudflare, etc.) to keep a hostname pointed at the dynamic IP.&lt;/p&gt;
&lt;p&gt;Exposes a service directly to the internet. Minimum hardening for SSH:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Key-only auth (&lt;code&gt;PasswordAuthentication no&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Non-standard port&lt;/li&gt;
&lt;li&gt;fail2ban or equivalent&lt;/li&gt;
&lt;li&gt;Regular review of auth logs&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Property&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports required&lt;/td&gt;
 &lt;td&gt;Yes (TCP)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP required&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Works through CGNAT&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Third-party dependency&lt;/td&gt;
 &lt;td&gt;DDNS provider only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Complexity&lt;/td&gt;
 &lt;td&gt;Low–Medium&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="comparison"&gt;Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Tailscale&lt;/th&gt;
 &lt;th&gt;Cloudflare Tunnel&lt;/th&gt;
 &lt;th&gt;WireGuard&lt;/th&gt;
 &lt;th&gt;Port forward&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Open ports&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Public IP&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CGNAT&lt;/td&gt;
 &lt;td&gt;Works&lt;/td&gt;
 &lt;td&gt;Works&lt;/td&gt;
 &lt;td&gt;Fails&lt;/td&gt;
 &lt;td&gt;Fails&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Traffic via third party&lt;/td&gt;
 &lt;td&gt;Metadata only&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Self-hostable&lt;/td&gt;
 &lt;td&gt;Yes (Headscale)&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Complexity&lt;/td&gt;
 &lt;td&gt;Low&lt;/td&gt;
 &lt;td&gt;Low–Med&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;td&gt;Low–Med&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="which-to-use"&gt;Which to use
&lt;/h2&gt;&lt;p&gt;Start with &lt;strong&gt;Tailscale&lt;/strong&gt; if you want the least friction and aren&amp;rsquo;t sure whether you&amp;rsquo;re behind CGNAT. Subnet routing covers the entire LAN through a single node.&lt;/p&gt;
&lt;p&gt;Use &lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; if you have a domain on Cloudflare and want browser-accessible SSH or HTTP without client software.&lt;/p&gt;
&lt;p&gt;Use &lt;strong&gt;WireGuard&lt;/strong&gt; if you have a confirmed public IP and want no third-party in the data path.&lt;/p&gt;
&lt;p&gt;Use &lt;strong&gt;port forwarding&lt;/strong&gt; only if you already have DDNS working and want no new software — it has the highest hardening overhead.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="related"&gt;Related
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;For a hardened SSH entry point → &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/bastion/" &gt;Bastion / jump server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For exposing internal web services via public URLs → &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/tunneled-reverse-proxy/" &gt;Tunneled reverse proxy platforms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>BGP</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/bgp/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/bgp/</guid><description>&lt;p&gt;BGP (Border Gateway Protocol) is the routing protocol that holds the internet together. Every major network operator uses it to advertise which IP prefixes they own and to exchange that information with peers. In a homelab context the scale is different but the mechanics are the same.&lt;/p&gt;
&lt;p&gt;BGP is a path-vector protocol: each router advertises routes along with the path (sequence of ASNs) taken to reach them. Routers choose the best path based on a set of attributes and policy rules, then advertise that path to their peers.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="ebgp-vs-ibgp"&gt;eBGP vs iBGP
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;eBGP&lt;/strong&gt; (external BGP) — sessions between routers in &lt;em&gt;different&lt;/em&gt; autonomous systems. Each party has a different ASN. This is what you configure between VyOS and OPNsense, and between VyOS and MetalLB.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;iBGP&lt;/strong&gt; (internal BGP) — sessions between routers in the &lt;em&gt;same&lt;/em&gt; autonomous system. Used inside large networks to distribute external routes internally. Not relevant for a basic homelab setup.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="asns-for-private-use"&gt;ASNs for private use
&lt;/h2&gt;&lt;p&gt;Autonomous System Numbers in the range &lt;strong&gt;64512–65534&lt;/strong&gt; are reserved for private use (&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc6996" target="_blank" rel="noopener"
 &gt;RFC 6996&lt;/a&gt;) — the same concept as RFC 1918 private IP addresses. Assign one to each participant in your BGP topology:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Participant&lt;/th&gt;
 &lt;th&gt;Example ASN&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;OPNsense&lt;/td&gt;
 &lt;td&gt;64512&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VyOS&lt;/td&gt;
 &lt;td&gt;64513&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;MetalLB (Talos cluster)&lt;/td&gt;
 &lt;td&gt;64514&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="why-bgp-for-kubernetes-loadbalancer-ips"&gt;Why BGP for Kubernetes LoadBalancer IPs
&lt;/h2&gt;&lt;p&gt;Kubernetes &lt;code&gt;LoadBalancer&lt;/code&gt; services need something external to the cluster to route traffic to them. In a cloud environment the cloud provider handles this automatically. On bare metal you need to do it yourself.&lt;/p&gt;
&lt;p&gt;Two common approaches with &lt;a class="link" href="https://metallb.universe.tf" target="_blank" rel="noopener"
 &gt;MetalLB&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;L2 mode&lt;/strong&gt; — MetalLB uses ARP (IPv4) or NDP (IPv6) to announce service IPs directly on the LAN. Simple to set up. Limitations: only one node handles traffic for each IP at a time (no real load balancing at the network layer), and the service IP must be in the same subnet as the nodes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a class="link" href="https://metallb.universe.tf/concepts/bgp/" target="_blank" rel="noopener"
 &gt;BGP mode&lt;/a&gt;&lt;/strong&gt; — MetalLB establishes a BGP session with an upstream router (VyOS, for example) and announces service IPs as /32 prefixes. The router learns the route and can ECMP across all nodes that are advertising it. More correct: actual load balancing, no subnet constraint, clean separation between cluster and network layer.&lt;/p&gt;
&lt;p&gt;The tradeoff is that BGP mode requires a BGP-capable router in the path, which is why VyOS exists in this topology.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="testing-with-a-real-bgp-network"&gt;Testing with a real BGP network
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://dn42.eu" target="_blank" rel="noopener"
 &gt;DN42&lt;/a&gt; is a community-run experimental network that simulates the real internet using actual BGP, DNS, and whois infrastructure. Participants connect via WireGuard or other tunnels and peer with each other using real BGP sessions and real (private-range) ASNs. A good way to practice BGP outside the homelab without needing a production ASN.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="related"&gt;Related
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/vyos/" &gt;VyOS&lt;/a&gt; — the BGP peer router&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/vyos-bgp/" &gt;VyOS + BGP experiment&lt;/a&gt; — the actual setup in this homelab&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>OPNsense</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/opnsense/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/opnsense/</guid><description>&lt;p&gt;OPNsense is an open-source firewall and routing platform based on FreeBSD. It is a fork of pfSense, with a stronger emphasis on community ownership, a cleaner UI, and more frequent security updates. Both are descendants of m0n0wall.&lt;/p&gt;
&lt;p&gt;It covers the full gateway function: stateful firewall, NAT, DHCP, DNS, TFTP, VPN, traffic shaping, and IDS/IPS — all through a web UI or via the API.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="feature-overview"&gt;Feature overview
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Feature&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Stateful firewall&lt;/td&gt;
 &lt;td&gt;Zone-based rules, aliases, scheduling&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NAT&lt;/td&gt;
 &lt;td&gt;Outbound, inbound (port forwarding), 1:1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DHCP&lt;/td&gt;
 &lt;td&gt;ISC DHCPv4 and Kea; supports network boot options&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DNS&lt;/td&gt;
 &lt;td&gt;Unbound resolver with DNSSEC; optional forwarding&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TFTP&lt;/td&gt;
 &lt;td&gt;Simple server at &lt;code&gt;/usr/local/tftp&lt;/code&gt;; used for PXE boot&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VPN&lt;/td&gt;
 &lt;td&gt;WireGuard, OpenVPN, IPsec&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;IDS/IPS&lt;/td&gt;
 &lt;td&gt;Suricata integration&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Traffic shaping&lt;/td&gt;
 &lt;td&gt;HFSC, PRIQ, CAKE&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;BGP / routing&lt;/td&gt;
 &lt;td&gt;FRRouting plugin available (not enabled by default)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="opnsense-vs-pfsense-vs-vyos"&gt;OPNsense vs pfSense vs VyOS
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;OPNsense&lt;/th&gt;
 &lt;th&gt;pfSense&lt;/th&gt;
 &lt;th&gt;VyOS&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Base&lt;/td&gt;
 &lt;td&gt;FreeBSD&lt;/td&gt;
 &lt;td&gt;FreeBSD&lt;/td&gt;
 &lt;td&gt;Debian Linux&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;License&lt;/td&gt;
 &lt;td&gt;BSD (true FOSS)&lt;/td&gt;
 &lt;td&gt;BSL (mixed)&lt;/td&gt;
 &lt;td&gt;GPL&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Model&lt;/td&gt;
 &lt;td&gt;Firewall appliance&lt;/td&gt;
 &lt;td&gt;Firewall appliance&lt;/td&gt;
 &lt;td&gt;Network OS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Config interface&lt;/td&gt;
 &lt;td&gt;Web UI + API&lt;/td&gt;
 &lt;td&gt;Web UI&lt;/td&gt;
 &lt;td&gt;CLI (commit/rollback)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;BGP&lt;/td&gt;
 &lt;td&gt;Via FRRouting plugin&lt;/td&gt;
 &lt;td&gt;Via FRRouting plugin&lt;/td&gt;
 &lt;td&gt;Native (FRRouting built-in)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Typical use&lt;/td&gt;
 &lt;td&gt;Edge gateway, firewall&lt;/td&gt;
 &lt;td&gt;Edge gateway, firewall&lt;/td&gt;
 &lt;td&gt;Router, BGP peer, lab router VM&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;OPNsense and pfSense are both appliance-style: you configure them through a UI and they manage all the underlying services for you. &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/vyos/" &gt;VyOS&lt;/a&gt; is a network OS in the Juniper/Cisco tradition — CLI-first, commit/rollback, intended for use as a router or BGP peer rather than a full gateway appliance.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="related"&gt;Related
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.opnsense.org/" target="_blank" rel="noopener"
 &gt;OPNsense documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/opnsense/plugins" target="_blank" rel="noopener"
 &gt;OPNsense plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/hardware/hardware-provisioning/ipxe-opnsense/" &gt;iPXE + OPNsense&lt;/a&gt; — PXE boot configuration via OPNsense DHCP and TFTP&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/opnsense/" &gt;OPNsense in the homelab&lt;/a&gt; — current setup and planned redo&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>OPNsense in the homelab</title><link>https://backend-engineering-strategy-tools.github.io/site/homelab/opnsense/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/homelab/opnsense/</guid><description>&lt;p&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/opnsense/" &gt;OPNsense&lt;/a&gt; running on the Sun Fire X4150 — perimeter gateway for both the homelab and the home network.&lt;/p&gt;
&lt;p&gt;Current state: inherited setup, not clean, not properly documented. It works, but it was not built with intention. A redo is on the todo list.&lt;/p&gt;
&lt;p&gt;Dual role is intentional and will stay that way — OPNsense on the Sun Fire handles both the homelab and the house. One box, one gateway.&lt;/p&gt;
&lt;p&gt;The Sun Fire X4150 handles it without breaking a sweat. Old, but solid.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="todo-clean-reinstall"&gt;TODO: Clean reinstall
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Fresh OPNsense install on the Sun Fire&lt;/li&gt;
&lt;li&gt;Rename from &lt;code&gt;router.mjnet.info&lt;/code&gt; → &lt;code&gt;heimdal.mjnet.info&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Document the full configuration: firewall rules, DHCP, DNS (Unbound), TFTP/PXE, BGP peering with VyOS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;On the hostname rename&lt;/strong&gt;: making the router&amp;rsquo;s hostname publicly resolvable is a mild information disclosure — it signals which host is the perimeter device. In practice the IP is what matters, and if it&amp;rsquo;s already reachable the name adds little. Still worth being deliberate about what resolves publicly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="intended-role"&gt;Intended role
&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;ISP/WAN → OPNsense (heimdal.mjnet.info) → LAN switch → rack
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Perimeter firewall and NAT gateway&lt;/li&gt;
&lt;li&gt;DHCP for the LAN&lt;/li&gt;
&lt;li&gt;DNS via Unbound&lt;/li&gt;
&lt;li&gt;TFTP/PXE for bare-metal provisioning&lt;/li&gt;
&lt;li&gt;eBGP peer upstream of VyOS (future — see &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/vyos-bgp/" &gt;VyOS + BGP&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>VyOS</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/vyos/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/vyos/</guid><description>&lt;p&gt;VyOS is an open-source network operating system built on Debian Linux. It runs on bare metal or as a VM, and is configured via a CLI with a commit/rollback model similar to Juniper JunOS. Configuration changes are staged and only take effect when you explicitly &lt;code&gt;commit&lt;/code&gt; — there is no live-editing a running config and hoping nothing breaks.&lt;/p&gt;
&lt;p&gt;It ships FRRouting (FRR) as the routing engine, giving it native support for BGP, OSPF, IS-IS, and other protocols. This is its main distinction from &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/opnsense/" &gt;OPNsense&lt;/a&gt; for homelab use: OPNsense is a firewall appliance that can do some routing; VyOS is a routing OS that can also do firewall.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="configuration-model"&gt;Configuration model
&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;vyos@router# set interfaces ethernet eth0 address &amp;#39;192.168.1.254/24&amp;#39;
vyos@router# set protocols bgp system-as &amp;#39;65001&amp;#39;
vyos@router# commit
vyos@router# save
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;configure&lt;/code&gt; enters configuration mode. &lt;code&gt;set&lt;/code&gt; stages a change. &lt;code&gt;commit&lt;/code&gt; applies it. &lt;code&gt;save&lt;/code&gt; persists it to disk. &lt;code&gt;rollback&lt;/code&gt; reverts to the last committed state if something goes wrong. The separation between staging and applying is genuinely useful when changing routing configuration remotely.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="key-features"&gt;Key features
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Feature&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;BGP&lt;/td&gt;
 &lt;td&gt;Via FRRouting; full eBGP/iBGP support&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;OSPF / IS-IS&lt;/td&gt;
 &lt;td&gt;Also via FRR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Static routing&lt;/td&gt;
 &lt;td&gt;Standard&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VLAN&lt;/td&gt;
 &lt;td&gt;802.1Q trunking&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NAT&lt;/td&gt;
 &lt;td&gt;Source and destination NAT&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Firewall&lt;/td&gt;
 &lt;td&gt;Zone-based, stateful&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;WireGuard&lt;/td&gt;
 &lt;td&gt;Built-in&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;OpenVPN&lt;/td&gt;
 &lt;td&gt;Built-in&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DHCP server&lt;/td&gt;
 &lt;td&gt;Built-in&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VXLAN&lt;/td&gt;
 &lt;td&gt;Supported&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="vyos-vs-opnsense"&gt;VyOS vs OPNsense
&lt;/h2&gt;&lt;p&gt;VyOS is the right choice when you want a dedicated BGP peer or a router VM with a clean CLI config model. OPNsense is the right choice when you want a full gateway appliance with a web UI.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="automation"&gt;Automation
&lt;/h2&gt;&lt;p&gt;VyOS is designed to be automated — the commit/rollback model maps cleanly onto infrastructure-as-code workflows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;REST API&lt;/strong&gt; — built-in HTTP API for retrieving and applying configuration programmatically. Useful for scripting config changes without SSH.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ansible&lt;/strong&gt; — official &lt;code&gt;vyos.vyos&lt;/code&gt; collection on Ansible Galaxy. Modules for interfaces, BGP, firewall rules, and more. Changes go through the normal commit/rollback cycle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Terraform&lt;/strong&gt; — community provider available. Less mature than the Ansible collection but usable for provisioning router config alongside other infrastructure.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="related"&gt;Related
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.vyos.io/" target="_blank" rel="noopener"
 &gt;VyOS documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.vyos.io/en/latest/automation/vyos-api.html" target="_blank" rel="noopener"
 &gt;VyOS REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.ansible.com/ansible/latest/collections/vyos/vyos/index.html" target="_blank" rel="noopener"
 &gt;VyOS Ansible collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://vyos.net/get/" target="_blank" rel="noopener"
 &gt;VyOS rolling release downloads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/bgp/" &gt;BGP&lt;/a&gt; — protocol background&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/networking/opnsense/" &gt;OPNsense&lt;/a&gt; — the complementary edge gateway&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/vyos-bgp/" &gt;VyOS + BGP in the homelab&lt;/a&gt; — the actual setup&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Cables &amp; Transceivers Inventory</title><link>https://backend-engineering-strategy-tools.github.io/site/homelab/inventory/cables/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/homelab/inventory/cables/</guid><description>&lt;h1 id="sas-cables"&gt;SAS Cables
&lt;/h1&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component ID&lt;/th&gt;
 &lt;th&gt;Type&lt;/th&gt;
 &lt;th&gt;Connectors&lt;/th&gt;
 &lt;th&gt;Length&lt;/th&gt;
 &lt;th&gt;Qty&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;CBL-SAS-001&lt;/td&gt;
 &lt;td&gt;Internal Mini-SAS&lt;/td&gt;
 &lt;td&gt;SFF-8087 → SFF-8087&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CBL-SAS-002&lt;/td&gt;
 &lt;td&gt;Int → Ext Mini-SAS&lt;/td&gt;
 &lt;td&gt;SFF-8087 → SFF-8088&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;Needed for internal cards (CTRL-002/003) to reach DAS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CBL-SAS-003&lt;/td&gt;
 &lt;td&gt;Ext SAS&lt;/td&gt;
 &lt;td&gt;SFF-8470 → SFF-8088&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;CTRL-006 → MIMIR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h1 id="ethernet-cables"&gt;Ethernet Cables
&lt;/h1&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component ID&lt;/th&gt;
 &lt;th&gt;Type&lt;/th&gt;
 &lt;th&gt;Speed&lt;/th&gt;
 &lt;th&gt;Length&lt;/th&gt;
 &lt;th&gt;Qty&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;CBL-ETH-001&lt;/td&gt;
 &lt;td&gt;Cat5e/Cat6&lt;/td&gt;
 &lt;td&gt;1GbE&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h1 id="sfp--sfp-transceivers"&gt;SFP / SFP+ Transceivers
&lt;/h1&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component ID&lt;/th&gt;
 &lt;th&gt;Type&lt;/th&gt;
 &lt;th&gt;Speed&lt;/th&gt;
 &lt;th&gt;Wavelength&lt;/th&gt;
 &lt;th&gt;Reach&lt;/th&gt;
 &lt;th&gt;Qty&lt;/th&gt;
 &lt;th&gt;Where used&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;SFP-001&lt;/td&gt;
 &lt;td&gt;SFP&lt;/td&gt;
 &lt;td&gt;1GbE&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;BIFROST-01/02 (28 cages each); MODI (4 cages)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SFP-002&lt;/td&gt;
 &lt;td&gt;XFP&lt;/td&gt;
 &lt;td&gt;10GbE&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;ASGARD switch ports 20-21 (2× XFP per module × 2)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h1 id="adapters"&gt;Adapters
&lt;/h1&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component ID&lt;/th&gt;
 &lt;th&gt;Description&lt;/th&gt;
 &lt;th&gt;From&lt;/th&gt;
 &lt;th&gt;To&lt;/th&gt;
 &lt;th&gt;Qty&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;ADP-001&lt;/td&gt;
 &lt;td&gt;SFF-8470 → SFF-8088&lt;/td&gt;
 &lt;td&gt;SFF-8470&lt;/td&gt;
 &lt;td&gt;SFF-8088&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;Passive; alternative to CBL-SAS-003 if cabling via adapter&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item><item><title>Network Inventory</title><link>https://backend-engineering-strategy-tools.github.io/site/homelab/inventory/network/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/homelab/inventory/network/</guid><description>&lt;h1 id="nic-catalog"&gt;NIC Catalog
&lt;/h1&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component ID&lt;/th&gt;
 &lt;th&gt;Manufacturer&lt;/th&gt;
 &lt;th&gt;Model / FRU&lt;/th&gt;
 &lt;th&gt;Ports&lt;/th&gt;
 &lt;th&gt;Speed&lt;/th&gt;
 &lt;th&gt;Interface&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;NIC-001&lt;/td&gt;
 &lt;td&gt;IBM&lt;/td&gt;
 &lt;td&gt;Onboard (x3550 M3 mobo)&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;1GbE&lt;/td&gt;
 &lt;td&gt;RJ45&lt;/td&gt;
 &lt;td&gt;Integrated; present on all x3550 M3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NIC-002&lt;/td&gt;
 &lt;td&gt;IBM&lt;/td&gt;
 &lt;td&gt;Dual-port GbE Daughter Card (FRU 43V6927)&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;1GbE&lt;/td&gt;
 &lt;td&gt;RJ45&lt;/td&gt;
 &lt;td&gt;Add-in daughter card slot&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NIC-003&lt;/td&gt;
 &lt;td&gt;Sun / Intel&lt;/td&gt;
 &lt;td&gt;Onboard quad GbE (Sun Fire X4150)&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;1GbE&lt;/td&gt;
 &lt;td&gt;RJ45&lt;/td&gt;
 &lt;td&gt;Integrated; all 4 ports on rear&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NIC-004&lt;/td&gt;
 &lt;td&gt;HP / Emulex&lt;/td&gt;
 &lt;td&gt;FlexFabric 554FLB (647584-001)&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;10GbE&lt;/td&gt;
 &lt;td&gt;SFP+ FLB&lt;/td&gt;
 &lt;td&gt;FlexibleLOM slot; FCoE + Flex-10 capable; BL460c Gen8&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h1 id="nic-placement"&gt;NIC Placement
&lt;/h1&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Asset ID&lt;/th&gt;
 &lt;th&gt;Hostname&lt;/th&gt;
 &lt;th&gt;Component ID&lt;/th&gt;
 &lt;th&gt;Total Data Ports&lt;/th&gt;
 &lt;th&gt;Mgmt Port&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-005&lt;/td&gt;
 &lt;td&gt;ODEN&lt;/td&gt;
 &lt;td&gt;NIC-001&lt;/td&gt;
 &lt;td&gt;2× GbE&lt;/td&gt;
 &lt;td&gt;1× IMM&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-006&lt;/td&gt;
 &lt;td&gt;LOKE&lt;/td&gt;
 &lt;td&gt;NIC-001 + NIC-002&lt;/td&gt;
 &lt;td&gt;4× GbE&lt;/td&gt;
 &lt;td&gt;1× IMM&lt;/td&gt;
 &lt;td&gt;Daughter card FRU 43V6927&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-009&lt;/td&gt;
 &lt;td&gt;HEIMDAL&lt;/td&gt;
 &lt;td&gt;NIC-003&lt;/td&gt;
 &lt;td&gt;4× GbE&lt;/td&gt;
 &lt;td&gt;1× mgmt&lt;/td&gt;
 &lt;td&gt;OPNsense firewall&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h1 id="network-addresses"&gt;Network Addresses
&lt;/h1&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Asset ID&lt;/th&gt;
 &lt;th&gt;Hostname&lt;/th&gt;
 &lt;th&gt;Interface&lt;/th&gt;
 &lt;th&gt;Role&lt;/th&gt;
 &lt;th&gt;IP Address&lt;/th&gt;
 &lt;th&gt;MAC Address&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-005&lt;/td&gt;
 &lt;td&gt;ODEN&lt;/td&gt;
 &lt;td&gt;eth0&lt;/td&gt;
 &lt;td&gt;data&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-005&lt;/td&gt;
 &lt;td&gt;ODEN&lt;/td&gt;
 &lt;td&gt;eth1&lt;/td&gt;
 &lt;td&gt;data&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-005&lt;/td&gt;
 &lt;td&gt;ODEN&lt;/td&gt;
 &lt;td&gt;mgmt&lt;/td&gt;
 &lt;td&gt;IMM&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-006&lt;/td&gt;
 &lt;td&gt;LOKE&lt;/td&gt;
 &lt;td&gt;eth0&lt;/td&gt;
 &lt;td&gt;data&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-006&lt;/td&gt;
 &lt;td&gt;LOKE&lt;/td&gt;
 &lt;td&gt;eth1&lt;/td&gt;
 &lt;td&gt;data&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-006&lt;/td&gt;
 &lt;td&gt;LOKE&lt;/td&gt;
 &lt;td&gt;eth2&lt;/td&gt;
 &lt;td&gt;data&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-006&lt;/td&gt;
 &lt;td&gt;LOKE&lt;/td&gt;
 &lt;td&gt;eth3&lt;/td&gt;
 &lt;td&gt;data&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-006&lt;/td&gt;
 &lt;td&gt;LOKE&lt;/td&gt;
 &lt;td&gt;mgmt&lt;/td&gt;
 &lt;td&gt;IMM&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h1 id="switch-ports"&gt;Switch Ports
&lt;/h1&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Asset ID&lt;/th&gt;
 &lt;th&gt;Hostname&lt;/th&gt;
 &lt;th&gt;Port Type&lt;/th&gt;
 &lt;th&gt;Count&lt;/th&gt;
 &lt;th&gt;SFP Cages&lt;/th&gt;
 &lt;th&gt;Uplink&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-012&lt;/td&gt;
 &lt;td&gt;BIFROST-01&lt;/td&gt;
 &lt;td&gt;SFP Fiber&lt;/td&gt;
 &lt;td&gt;28&lt;/td&gt;
 &lt;td&gt;28&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;All-SFP switch&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-013&lt;/td&gt;
 &lt;td&gt;BIFROST-02&lt;/td&gt;
 &lt;td&gt;SFP Fiber&lt;/td&gt;
 &lt;td&gt;28&lt;/td&gt;
 &lt;td&gt;28&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;All-SFP switch&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-014&lt;/td&gt;
 &lt;td&gt;MODI&lt;/td&gt;
 &lt;td&gt;RJ45 + SFP&lt;/td&gt;
 &lt;td&gt;24+4&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;24× GbE PoE + 4× SFP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-015&lt;/td&gt;
 &lt;td&gt;MAGNI&lt;/td&gt;
 &lt;td&gt;RJ45&lt;/td&gt;
 &lt;td&gt;24&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;24× GbE managed&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SYS-016&lt;/td&gt;
 &lt;td&gt;VALI&lt;/td&gt;
 &lt;td&gt;RJ45&lt;/td&gt;
 &lt;td&gt;24&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;?&lt;/td&gt;
 &lt;td&gt;Fanless; 24× GbE&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item><item><title>NGINX</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/nginx/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/nginx/</guid><description>&lt;p&gt;&lt;img alt="NGINX" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;NGINX is a high-performance web server and reverse proxy built around an event-driven, non-blocking architecture. Where Apache spawns a thread per connection, NGINX handles thousands of concurrent connections from a small, fixed number of worker processes — making it well-suited to acting as the entry point for modern distributed systems.&lt;/p&gt;
&lt;h2 id="server-blocks"&gt;Server blocks
&lt;/h2&gt;&lt;p&gt;NGINX configuration is structured around &lt;code&gt;server&lt;/code&gt; blocks (analogous to Apache&amp;rsquo;s VirtualHost). Each block defines how to handle requests for a given hostname or port:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;example.com&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;root&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/var/www/html&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;index&lt;/span&gt; &lt;span style="color:#e6db74"&gt;index.html&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Multiple server blocks in a single NGINX instance handle traffic for different domains — NGINX selects the block by matching the &lt;code&gt;Host&lt;/code&gt; header against &lt;code&gt;server_name&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="reverse-proxy"&gt;Reverse proxy
&lt;/h2&gt;&lt;p&gt;The primary use case in infrastructure work: NGINX sits in front of application servers and forwards requests to them. The application never needs to be exposed directly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;api.example.com&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://127.0.0.1:8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Host&lt;/span&gt; $host;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Real-IP&lt;/span&gt; $remote_addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Forwarded-For&lt;/span&gt; $proxy_add_x_forwarded_for;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;X-Forwarded-For&lt;/code&gt; and &lt;code&gt;X-Real-IP&lt;/code&gt; headers pass the original client IP to the upstream — without these the application sees only the proxy address.&lt;/p&gt;
&lt;h2 id="ssl-termination"&gt;SSL termination
&lt;/h2&gt;&lt;p&gt;NGINX handles TLS and speaks plain HTTP to backends. This centralises certificate management and offloads crypto from application processes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt; &lt;span style="color:#e6db74"&gt;ssl&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;example.com&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ssl_certificate&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/letsencrypt/live/example.com/fullchain.pem&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ssl_certificate_key&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ssl_protocols&lt;/span&gt; &lt;span style="color:#e6db74"&gt;TLSv1.2&lt;/span&gt; &lt;span style="color:#e6db74"&gt;TLSv1.3&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://127.0.0.1:8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Redirect HTTP → HTTPS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;example.com&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;301&lt;/span&gt; &lt;span style="color:#e6db74"&gt;https://&lt;/span&gt;$host$request_uri;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pair with &lt;a class="link" href="../letsencrypt/" &gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; and Certbot for automated certificate issuance and renewal.&lt;/p&gt;
&lt;h2 id="load-balancing"&gt;Load balancing
&lt;/h2&gt;&lt;p&gt;NGINX distributes traffic across multiple upstream instances using an &lt;code&gt;upstream&lt;/code&gt; block:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;upstream&lt;/span&gt; &lt;span style="color:#e6db74"&gt;app&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt; 10.0.0.1:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt; 10.0.0.2:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt; 10.0.0.3:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://app&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Default is round-robin. Other strategies: &lt;code&gt;least_conn&lt;/code&gt; (fewest active connections), &lt;code&gt;ip_hash&lt;/code&gt; (sticky sessions by client IP), &lt;code&gt;hash&lt;/code&gt; (consistent hashing by arbitrary key).&lt;/p&gt;
&lt;h2 id="static-files"&gt;Static files
&lt;/h2&gt;&lt;p&gt;NGINX serves static assets efficiently — far more so than most application frameworks. A common pattern: serve static files directly from NGINX, proxy only dynamic requests to the application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/static/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;alias&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/var/www/static/&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;expires&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1y&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;add_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Cache-Control&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;public,&lt;/span&gt; &lt;span style="color:#e6db74"&gt;immutable&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://app&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="useful-patterns"&gt;Useful patterns
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt; — protect endpoints from abuse:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;limit_req_zone&lt;/span&gt; $binary_remote_addr &lt;span style="color:#e6db74"&gt;zone=api:10m&lt;/span&gt; &lt;span style="color:#e6db74"&gt;rate=10r/s&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/api/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;limit_req&lt;/span&gt; &lt;span style="color:#e6db74"&gt;zone=api&lt;/span&gt; &lt;span style="color:#e6db74"&gt;burst=20&lt;/span&gt; &lt;span style="color:#e6db74"&gt;nodelay&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://app&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Health check path&lt;/strong&gt; — expose a simple status endpoint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/healthz&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;access_log&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;off&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ok\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;add_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Content-Type&lt;/span&gt; &lt;span style="color:#e6db74"&gt;text/plain&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test config before reloading: &lt;code&gt;nginx -t&lt;/code&gt;. Reload without dropping connections: &lt;code&gt;nginx -s reload&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://nginx.org/en/docs/" target="_blank" rel="noopener"
 &gt;NGINX documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.digitalocean.com/community/tools/nginx" target="_blank" rel="noopener"
 &gt;NGINX config generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://ssl-config.mozilla.org/" target="_blank" rel="noopener"
 &gt;Mozilla SSL Configuration Generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>