{"id":79538,"date":"2026-10-28T06:20:18","date_gmt":"2026-10-28T06:20:18","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=79538"},"modified":"2026-10-28T06:20:20","modified_gmt":"2026-10-28T06:20:20","slug":"pomerium","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/pomerium\/","title":{"rendered":"Pomerium"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\">PART I \u2014 UNDERSTAND THE WORLD BEFORE THE TOOL<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 1. The Big Picture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Imagine a medieval castle. To keep the treasure inside safe, the king builds a massive stone wall and a moat. If you have the key to the front gate, you are allowed inside. Once you are inside, you can walk freely into the armory, the kitchen, and the throne room because the guards assume that anyone inside the walls is trustworthy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For decades, computer networks were built the exact same way. Companies built a &#8220;perimeter&#8221; using Firewalls and Virtual Private Networks (VPNs). If you connected to the VPN, you were &#8220;inside the network&#8221; and trusted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What changed?<\/strong> The modern world broke this model. Employees now work from coffee shops, not just the office. Applications live in the cloud, not just in the basement server room. Furthermore, if a hacker steals a single employee&#8217;s VPN password, they get past the &#8220;castle wall&#8221; and can freely attack every system inside (this is called <em>lateral movement<\/em>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We needed a new approach: <strong>Zero Trust<\/strong>. Instead of trusting you because you are <em>inside<\/em> the network, Zero Trust verifies your identity and device health <em>every single time<\/em> you try to open an application, regardless of where you are sitting. Pomerium is a tool built to enforce this exact model.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What You Should Understand Before Moving On:<\/strong> The old way of securing applications relied on trusting the network (VPNs). The new way relies on verifying identity and context for every single request (Zero Trust).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 2. Prerequisite Concepts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To understand Pomerium, you must understand the building blocks it relies on.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Concept<\/strong><\/td><td><strong>Simple Definition<\/strong><\/td><td><strong>Everyday Analogy<\/strong><\/td><td><strong>Technical Explanation<\/strong><\/td><td><strong>Why It Matters<\/strong><\/td><\/tr><\/thead><tbody><tr><td><strong>Reverse Proxy<\/strong><\/td><td>A middleman that sits in front of your applications and intercepts incoming traffic.<\/td><td>A receptionist who intercepts all visitors, checks their reason for visiting, and then points them to the right office.<\/td><td>A server that retrieves resources on behalf of a client from one or more backend servers.<\/td><td>Pomerium <em>is<\/em> a reverse proxy. It intercepts your web requests.<\/td><\/tr><tr><td><strong>Identity Provider (IdP)<\/strong><\/td><td>A service that manages user accounts and proves who you are.<\/td><td>The DMV (Department of Motor Vehicles) that issues your ID card.<\/td><td>A system (like Google Workspace, Okta, or GitHub) that authenticates users and provides tokens via OIDC\/SAML.<\/td><td>Pomerium doesn&#8217;t store passwords. It asks your IdP to verify you.<\/td><\/tr><tr><td><strong>OIDC \/ OAuth2<\/strong><\/td><td>Open protocols for authentication and authorization.<\/td><td>The standard barcode format used on your ID card so any scanner can read it.<\/td><td>OpenID Connect provides identity verification; OAuth2 provides delegated access.<\/td><td>Pomerium uses these to talk to your IdP securely.<\/td><\/tr><tr><td><strong>JWT (JSON Web Token)<\/strong><\/td><td>A secure, digitally signed digital ID card.<\/td><td>A wristband at a concert. Once they check your ticket, you get a wristband to prove you are allowed in.<\/td><td>A compact, URL-safe string containing cryptographic claims about a user.<\/td><td>Pomerium reads and passes JWTs to your applications.<\/td><\/tr><tr><td><strong>Zero Trust<\/strong><\/td><td>The security philosophy of &#8220;Never trust, always verify.&#8221;<\/td><td>A hotel where your keycard is checked at the front door, the elevator, AND your room door.<\/td><td>A framework requiring all users to be authenticated and authorized continuously before being granted access.<\/td><td>This is the architectural pattern Pomerium helps you achieve.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 3. Essential Terminology and Glossary<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Term<\/strong><\/td><td><strong>Full Form<\/strong><\/td><td><strong>Simple Meaning<\/strong><\/td><td><strong>Example<\/strong><\/td><td><strong>Why It Matters<\/strong><\/td><\/tr><\/thead><tbody><tr><td><strong>IAP<\/strong><\/td><td>Identity-Aware Proxy<\/td><td>A proxy that checks who you are before letting you through.<\/td><td>Pomerium, Google Cloud IAP.<\/td><td>This is the specific category of tool Pomerium falls into.<\/td><\/tr><tr><td><strong>Route<\/strong><\/td><td>N\/A<\/td><td>A rule connecting a public URL to a private application.<\/td><td>Connecting <code>admin.company.com<\/code> to internal IP <code>10.0.0.5<\/code>.<\/td><td>You will configure routes for every app you protect.<\/td><\/tr><tr><td><strong>Policy<\/strong><\/td><td>N\/A<\/td><td>The rules deciding <em>who<\/em> can use a Route.<\/td><td>&#8220;Only allow users with the email domain @mycompany.com&#8221;<\/td><td>This is how you enforce security.<\/td><\/tr><tr><td><strong>Envoy<\/strong><\/td><td>Envoy Proxy<\/td><td>The high-performance engine running underneath Pomerium.<\/td><td>A car&#8217;s engine, while Pomerium is the steering wheel and dashboard.<\/td><td>Pomerium is technically a control plane that configures an underlying Envoy proxy.<\/td><\/tr><tr><td><strong>Session<\/strong><\/td><td>N\/A<\/td><td>A temporary period of authorized access.<\/td><td>Being logged in for 12 hours before having to re-enter your password.<\/td><td>Pomerium manages session state so users don&#8217;t have to log in on every single click.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 4. How the Technology Domain Works Without This Tool<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before Identity-Aware Proxies like Pomerium, remote access looked like this:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>The user installs a VPN client on their laptop.<\/li>\n\n\n\n<li>The user connects to the company VPN gateway.<\/li>\n\n\n\n<li>The VPN gateway verifies the user&#8217;s password and connects their computer to the internal corporate network.<\/li>\n\n\n\n<li>The user types <code>[http:\/\/internal-wiki.local](http:\/\/internal-wiki.local)<\/code> into their browser.<\/li>\n\n\n\n<li>The request travels through the VPN tunnel to the internal server.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Code snippet<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">flowchart<\/span> <span class=\"hljs-selector-tag\">LR<\/span>\n    <span class=\"hljs-selector-tag\">User<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"User (Coffee Shop)\"<\/span>]<\/span> <span class=\"hljs-selector-tag\">--<\/span> 1. <span class=\"hljs-selector-tag\">Connects<\/span> <span class=\"hljs-selector-tag\">to<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">VPN<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"VPN Gateway\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">VPN<\/span> <span class=\"hljs-selector-tag\">--<\/span> 2. <span class=\"hljs-selector-tag\">Grants<\/span> <span class=\"hljs-selector-tag\">Network<\/span> <span class=\"hljs-selector-tag\">Access<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">Network<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Corporate Internal Network\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">Network<\/span> <span class=\"hljs-selector-tag\">--<\/span> 3. <span class=\"hljs-selector-tag\">Access<\/span> <span class=\"hljs-selector-tag\">App<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">App1<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Internal Wiki\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">Network<\/span> <span class=\"hljs-selector-tag\">--<\/span> 3. <span class=\"hljs-selector-tag\">Access<\/span> <span class=\"hljs-selector-tag\">App<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">App2<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Admin Dashboard\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">Network<\/span> <span class=\"hljs-selector-tag\">--<\/span> 3. <span class=\"hljs-selector-tag\">Access<\/span> <span class=\"hljs-selector-tag\">App<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">App3<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"HR System\"<\/span>]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>The problem with this approach:<\/strong> Once the user passes step 2, they have access to the entire <code>Corporate Internal Network<\/code>. If they are a malicious actor, they can easily probe <code>App2<\/code> or <code>App3<\/code>, even if they only needed to access <code>App1<\/code>.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">PART II \u2014 UNDERSTAND THE TOOL<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 5. What Is Pomerium?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pomerium is an open-source <strong>Identity-Aware Proxy (IAP)<\/strong>.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Primary Purpose:<\/strong> To securely connect users to internal applications without a VPN, by verifying their identity for every single request.<\/li>\n\n\n\n<li><strong>Who uses it:<\/strong> System Administrators, DevOps engineers, and Security teams.<\/li>\n\n\n\n<li><strong>Where it runs:<\/strong> Typically deployed at the edge of your network (in Docker, Kubernetes, or on a Linux server) facing the public internet.<\/li>\n\n\n\n<li><strong>What it does NOT do:<\/strong> It is not an Identity Provider (it doesn&#8217;t store passwords\u2014it relies on Google, Okta, GitHub, etc.). It is also not a web application firewall (WAF), though it provides some overlapping protections.<\/li>\n\n\n\n<li><strong>Definitions:<\/strong>\n<ul class=\"wp-block-list\">\n<li><em>One-sentence:<\/em> Pomerium is an open-source tool that acts as a secure gateway, verifying a user&#8217;s identity before letting them access private web applications.<\/li>\n\n\n\n<li><em>Technical:<\/em> Pomerium is an identity-aware access proxy built on Envoy that integrates with OIDC Identity Providers to enforce Zero Trust access control policies at the application layer (Layer 7).<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 6. Why Does This Tool Exist?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pomerium exists to solve the vulnerabilities of perimeter-based security (VPNs) and the complexity of managing application access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Situations where it is useful:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You have a private dashboard (like Grafana or Jenkins) that you want your team to access from home, but you don&#8217;t want to expose it to the public internet or force them to use a clunky VPN.<\/li>\n\n\n\n<li>You want to enforce Two-Factor Authentication (2FA) on an older application that doesn&#8217;t support 2FA natively. (Pomerium enforces 2FA at the proxy level via your IdP).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Situations where it is unnecessary:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You are building a public-facing blog where anyone should have access.<\/li>\n\n\n\n<li>You only need machine-to-machine TCP routing without user identity (a standard reverse proxy like Nginx or HAProxy is fine).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The Benefit:<\/strong> Fast, seamless user experience. Users just type the URL into their browser, authenticate with their normal work email (via Google\/Okta), and are instantly in the app. No VPN clients required.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 7. How the Tool Works<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pomerium sits between the public internet and your private applications.<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Intercept:<\/strong> A user requests <code>[https:\/\/wiki.company.com](https:\/\/wiki.company.com)<\/code>. Pomerium intercepts this request.<\/li>\n\n\n\n<li><strong>Authenticate:<\/strong> Pomerium checks if the user has a valid session cookie. If not, it redirects them to the Identity Provider (e.g., Google).<\/li>\n\n\n\n<li><strong>Authorize:<\/strong> Once Google says &#8220;This is Alice,&#8221; Pomerium checks its configuration file: <em>Is Alice allowed to view the wiki?<\/em><\/li>\n\n\n\n<li><strong>Proxy:<\/strong> If Alice is authorized, Pomerium forwards the request to the internal wiki server and returns the result to Alice.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Code snippet<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">sequenceDiagram\n    participant U <span class=\"hljs-keyword\">as<\/span> User Browser\n    participant P <span class=\"hljs-keyword\">as<\/span> Pomerium\n    participant IdP <span class=\"hljs-keyword\">as<\/span> Identity Provider (e.g. Google)\n    participant A <span class=\"hljs-keyword\">as<\/span> Internal App\n\n    U-&gt;&gt;P: <span class=\"hljs-number\">1.<\/span> GET https:<span class=\"hljs-comment\">\/\/app.company.com<\/span>\n    P-&gt;&gt;U: <span class=\"hljs-number\">2.<\/span> Redirect to IdP <span class=\"hljs-keyword\">for<\/span> login\n    U-&gt;&gt;IdP: <span class=\"hljs-number\">3.<\/span> User logs <span class=\"hljs-keyword\">in<\/span>\n    IdP-&gt;&gt;P: <span class=\"hljs-number\">4.<\/span> Returns Identity Token (OIDC)\n    P-&gt;&gt;P: <span class=\"hljs-number\">5.<\/span> Evaluates Policy (Is user allowed?)\n    P-&gt;&gt;A: <span class=\"hljs-number\">6.<\/span> Proxies request to Internal App\n    A-&gt;&gt;P: <span class=\"hljs-number\">7.<\/span> Returns Application Data\n    P-&gt;&gt;U: <span class=\"hljs-number\">8.<\/span> Displays Application to User\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Chapter 8. Core Features Explained Simply<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Feature<\/strong><\/td><td><strong>Simple Explanation<\/strong><\/td><td><strong>Problem Solved<\/strong><\/td><td><strong>Beginner or Advanced<\/strong><\/td><\/tr><\/thead><tbody><tr><td><strong>SSO Integration<\/strong><\/td><td>Uses your existing login system (Google, Okta, GitHub).<\/td><td>Prevents you from having to create new passwords for internal tools.<\/td><td>Essential<\/td><\/tr><tr><td><strong>Policy Enforcement<\/strong><\/td><td>Rules like &#8220;Only the Dev team can access this app.&#8221;<\/td><td>Granular security control per application.<\/td><td>Essential<\/td><\/tr><tr><td><strong>Identity Header Passing<\/strong><\/td><td>Sends the user&#8217;s email to the backend app securely via a JWT.<\/td><td>The backend app knows who you are without you having to log in twice.<\/td><td>Advanced<\/td><\/tr><tr><td><strong>Audit Logging<\/strong><\/td><td>Records exactly who accessed what, and when.<\/td><td>Compliance and security investigations.<\/td><td>Everyday<\/td><\/tr><tr><td><strong>TCP Support<\/strong><\/td><td>Secures SSH or database connections, not just websites.<\/td><td>Securing non-web traffic over Zero Trust.<\/td><td>Advanced<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 9. Similar Tools and Alternatives<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Tool<\/strong><\/td><td><strong>Primary Purpose<\/strong><\/td><td><strong>Main Strength<\/strong><\/td><td><strong>Main Limitation<\/strong><\/td><td><strong>Best Use Case<\/strong><\/td><td><strong>Relationship to Pomerium<\/strong><\/td><\/tr><\/thead><tbody><tr><td><strong>Cloudflare Access<\/strong><\/td><td>Managed ZTNA<\/td><td>Extremely easy to set up, globally distributed.<\/td><td>It is a proprietary SaaS; you don&#8217;t control the data plane.<\/td><td>Small teams wanting instant setup without managing servers.<\/td><td>Direct competitor (SaaS vs Self-hosted).<\/td><\/tr><tr><td><strong>Tailscale<\/strong><\/td><td>Mesh VPN<\/td><td>Connects devices via Wireguard easily.<\/td><td>Gives network-level access (VPN) rather than just application-level proxying.<\/td><td>Connecting servers and devices directly.<\/td><td>Complementary\/Alternative.<\/td><\/tr><tr><td><strong>Google Cloud IAP<\/strong><\/td><td>Cloud Identity Proxy<\/td><td>Native integration with GCP.<\/td><td>Only works well if your apps are hosted in Google Cloud.<\/td><td>Teams exclusively using Google Cloud infrastructure.<\/td><td>Direct competitor (Platform specific).<\/td><\/tr><tr><td><strong>Nginx \/ Traefik<\/strong><\/td><td>Standard Reverse Proxy<\/td><td>Extremely fast, flexible routing.<\/td><td>No native identity awareness; you have to build auth yourself.<\/td><td>Public websites, microservices routing.<\/td><td>Predecessor\/Alternative for non-auth needs.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Which Tool Should You Choose and Why?<\/strong> Choose <strong>Pomerium<\/strong> if you want to self-host your security infrastructure, maintain complete control over your data, and require granular identity-aware routing for web applications. Choose Cloudflare Access if you want a managed SaaS. Choose Tailscale if you need machine-to-machine VPN networking.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">PART III \u2014 ARCHITECTURE AND WORKFLOWS<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 10. Architecture Explained from Simple to Advanced<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pomerium can be deployed in two ways: All-in-One (for beginners) or Distributed (for production).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The Simplest Setup (All-in-One):<\/strong> For smaller setups, Pomerium runs as a single binary or single Docker container. Inside this single process, four distinct services run together:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Proxy:<\/strong> The front door. It receives the user&#8217;s web request.<\/li>\n\n\n\n<li><strong>Authenticate:<\/strong> Handles the handshake with your Identity Provider (Google, Okta).<\/li>\n\n\n\n<li><strong>Authorize:<\/strong> The bouncer. It checks the policy to see if the authenticated user is allowed in.<\/li>\n\n\n\n<li><strong>Databroker:<\/strong> The memory. It stores session data and user information.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Distributed Setup (Production):<\/strong> In high-traffic environments, you split these four components into separate servers or Kubernetes pods so they can scale independently.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Code snippet<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">flowchart<\/span> <span class=\"hljs-selector-tag\">TD<\/span>\n    <span class=\"hljs-selector-tag\">User<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"User Browser\"<\/span>]<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">Proxy<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Proxy Service (Edge)\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">Proxy<\/span> &lt;<span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">AuthZ<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Authorize Service\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">Proxy<\/span> &lt;<span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">AuthN<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Authenticate Service\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">AuthZ<\/span> &lt;<span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">DataBroker<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Databroker Service\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">AuthN<\/span> &lt;<span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">DataBroker<\/span>\n    <span class=\"hljs-selector-tag\">AuthN<\/span> &lt;<span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">IdP<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Identity Provider (External)\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">Proxy<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">App<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Internal Application\"<\/span>]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><em>In the diagram above, the Proxy receives the request, asks the Authenticate service to verify identity (which talks to the IdP), asks the Authorize service to verify permissions (which checks the Databroker for policy\/state), and finally proxies traffic to the App.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 11. The Complete Workflow<\/h2>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Trigger:<\/strong> A user navigates to <code>[https:\/\/grafana.internal.com](https:\/\/grafana.internal.com)<\/code>.<\/li>\n\n\n\n<li><strong>Input:<\/strong> The browser sends an HTTP GET request.<\/li>\n\n\n\n<li><strong>Component:<\/strong> The Pomerium <strong>Proxy Service<\/strong> receives it.<\/li>\n\n\n\n<li><strong>Processing (No Session):<\/strong> Pomerium sees no session cookie. It redirects the user to the IdP.<\/li>\n\n\n\n<li><strong>Dependency:<\/strong> The external IdP (e.g., Auth0) prompts for a password and 2FA.<\/li>\n\n\n\n<li><strong>Processing (Authorization):<\/strong> The user returns with a token. The <strong>Authorize Service<\/strong> checks the configuration: <em>Does user match policy?<\/em><\/li>\n\n\n\n<li><strong>Result:<\/strong> Access is granted. The Proxy forwards the GET request to the internal Grafana server.<\/li>\n\n\n\n<li><strong>Failure Path:<\/strong> If the user logs in but is not in the correct group, Pomerium intercepts the request and displays an HTTP 403 Access Denied page, preventing the traffic from ever touching the Grafana server.<\/li>\n<\/ol>\n\n\n\n<h1 class=\"wp-block-heading\">PART IV \u2014 INSTALLATION AND FIRST SUCCESS<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 13. Prerequisites and Environment Preparation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before installing Pomerium, you must have:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>A domain name:<\/strong> Pomerium relies heavily on HTTPS and cookies. You need a real domain (e.g., <code>example.com<\/code>).<\/li>\n\n\n\n<li><strong>TLS Certificates:<\/strong> You can use wildcard certs, Let&#8217;s Encrypt, or self-signed certs (for local testing).<\/li>\n\n\n\n<li><strong>An Identity Provider:<\/strong> You need a free account with an IdP like Auth0, GitHub, or a Google Workspace account. You will need a <code>Client ID<\/code> and <code>Client Secret<\/code> from them.<\/li>\n\n\n\n<li><strong>Shared Secret:<\/strong> A base64-encoded random string used by Pomerium components to securely talk to each other.<\/li>\n\n\n\n<li><strong>Cookie Secret:<\/strong> A base64-encoded random string used to encrypt user browser cookies.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Tip: Generate your secrets now in your terminal:<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bash<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">head -c32 \/dev\/urandom | base64\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><em>(Run this twice: once for <code>SHARED_SECRET<\/code> and once for <code>COOKIE_SECRET<\/code>)<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 14. Install on Supported Operating Systems<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pomerium is distributed as a single compiled binary, meaning it does not require complex runtime environments (like Java or Python).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A. Docker (Recommended for all OS \/ Beginners)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Because Pomerium requires an app to protect and specific configurations, using Docker Compose is the most universal way to run it on Windows, macOS, or Linux.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bash<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># Pull the official Pomerium image<\/span>\ndocker pull pomerium\/pomerium:latest\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">B. Ubuntu \/ Debian (Native)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use the official package repository.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bash<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># Add the signing key and repository<\/span>\nsudo curl <span class=\"hljs-number\">-1<\/span>sLf <span class=\"hljs-string\">'https:\/\/dl.cloudsmith.io\/public\/pomerium\/pomerium\/gpg.05193B615F3BA22A.key'<\/span> | sudo gpg --dearmor -o \/usr\/share\/keyrings\/pomerium-archive-keyring.gpg\n<span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"deb &#91;signed-by=\/usr\/share\/keyrings\/pomerium-archive-keyring.gpg] https:\/\/dl.cloudsmith.io\/public\/pomerium\/pomerium\/deb\/debian any-version main\"<\/span> | sudo tee \/etc\/apt\/sources.<span class=\"hljs-keyword\">list<\/span>.d\/pomerium.<span class=\"hljs-keyword\">list<\/span>\n\n<span class=\"hljs-comment\"># Install<\/span>\nsudo apt update &amp;&amp; sudo apt install pomerium\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><em>Configuration will live in <code>\/etc\/pomerium\/config.yaml<\/code>.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">C. macOS (Native via Homebrew)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Bash<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">brew install pomerium\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><em>To start it: <code>brew services start pomerium<\/code>.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">D. Windows<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pomerium does not have a native Windows installer. Windows users must use <strong>Docker Desktop<\/strong> or <strong>Windows Subsystem for Linux (WSL2)<\/strong> running Ubuntu.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 15. Your First Working Example<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s build a working Identity-Aware Proxy on your local machine using Docker Compose. We will protect a simple Nginx web server using <strong>GitHub<\/strong> as our Identity Provider.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Objective:<\/strong> Protect <code>http:\/\/localhost:8080<\/code> so only YOU (via your GitHub account) can access it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1: Get GitHub OAuth Credentials<\/strong><\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Go to your GitHub Settings -> Developer settings -> OAuth Apps.<\/li>\n\n\n\n<li>Click &#8220;New OAuth App&#8221;.<\/li>\n\n\n\n<li>Name: <code>Pomerium Local<\/code><\/li>\n\n\n\n<li>Homepage URL: <code>https:\/\/localhost<\/code><\/li>\n\n\n\n<li>Authorization callback URL: <code>https:\/\/localhost\/.pomerium\/callback<\/code><\/li>\n\n\n\n<li>Save. Copy the <strong>Client ID<\/strong> and generate a <strong>Client Secret<\/strong>.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2: Create the <code>config.yaml<\/code> file<\/strong> Create a folder named <code>pomerium-test<\/code>. Inside, create <code>config.yaml<\/code>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">YAML<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># config.yaml<\/span>\nauthenticate_service_url: https:<span class=\"hljs-comment\">\/\/localhost<\/span>\n\n<span class=\"hljs-comment\"># Identity Provider Settings<\/span>\nidp_provider: github\nidp_client_id: <span class=\"hljs-string\">\"YOUR_GITHUB_CLIENT_ID\"<\/span>\nidp_client_secret: <span class=\"hljs-string\">\"YOUR_GITHUB_CLIENT_SECRET\"<\/span>\n\n<span class=\"hljs-comment\"># Security (Replace with strings generated earlier)<\/span>\ncookie_secret: <span class=\"hljs-string\">\"OThkNzVkMmEtY2E2OS00ZGUzLTliNGEtYTEwNzE1Y2Vj\"<\/span> \nshared_secret: <span class=\"hljs-string\">\"Zjk1YThkYzUtYmEyNi00MjMzLWI1ZjYtNjkyMmVlOTc\"<\/span>\n\n<span class=\"hljs-comment\"># Testing settings (bypasses requirement for valid public TLS certs)<\/span>\ninsecure_server: <span class=\"hljs-keyword\">true<\/span>\n\n<span class=\"hljs-comment\"># The Routing Policy<\/span>\nroutes:\n  - from: https:<span class=\"hljs-comment\">\/\/localhost<\/span>\n    to: http:<span class=\"hljs-comment\">\/\/hello-app:80<\/span>\n    policy:\n      - allow:\n          <span class=\"hljs-keyword\">or<\/span>:\n            - claim\/email: <span class=\"hljs-string\">\"YOUR_GITHUB_EMAIL@example.com\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><em>(Replace the Client ID, Secret, and your GitHub email address).<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 3: Create the <code>docker-compose.yml<\/code> file<\/strong> In the same folder, create this file:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">YAML<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># docker-compose.yml<\/span>\nversion: <span class=\"hljs-string\">\"3\"<\/span>\nservices:\n  pomerium:\n    image: pomerium\/pomerium:latest\n    volumes:\n      - .\/config.yaml:\/pomerium\/config.yaml\n    ports:\n      - <span class=\"hljs-string\">\"443:443\"<\/span> <span class=\"hljs-comment\"># HTTPS<\/span>\n      - <span class=\"hljs-string\">\"80:80\"<\/span>   <span class=\"hljs-comment\"># HTTP<\/span>\n  \n  <span class=\"hljs-comment\"># The internal app we are protecting<\/span>\n  hello-app:\n    image: nginxdemos\/hello:plain-text\n    expose:\n      - <span class=\"hljs-string\">\"80\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 4: Run and Verify<\/strong><\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Open your terminal in the <code>pomerium-test<\/code> folder.<\/li>\n\n\n\n<li>Run: <code>docker-compose up<\/code><\/li>\n\n\n\n<li>Open your web browser and go to <code>https:\/\/localhost<\/code><\/li>\n\n\n\n<li><strong>Expected Output:<\/strong> You will be instantly redirected to GitHub to log in. After logging in, you will be redirected back to localhost, where you will see the Nginx hello screen!<\/li>\n\n\n\n<li><strong>To undo:<\/strong> Press <code>Ctrl+C<\/code> in your terminal, then type <code>docker-compose down<\/code>.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Common Mistake:<\/em> Using <code>http:\/\/<\/code> instead of <code>https:\/\/<\/code>. Pomerium routes strictly enforce HTTPS for the public-facing side.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">PART V \u2014 COMMANDS, CONFIGURATION AND DAILY USAGE<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 16. Essential Commands and Operations<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you install Pomerium natively (not Docker), you use the <code>pomerium<\/code> CLI.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command<\/strong><\/td><td><strong>What It Does<\/strong><\/td><td><strong>When to Use It<\/strong><\/td><td><strong>Example<\/strong><\/td><\/tr><\/thead><tbody><tr><td><code>pomerium<\/code><\/td><td>Starts the Pomerium service using defaults.<\/td><td>Running the server manually.<\/td><td><code>pomerium -config=\/etc\/pomerium\/config.yaml<\/code><\/td><\/tr><tr><td><code>pomerium-cli tcp<\/code><\/td><td>Starts a local TCP tunnel authenticated via Pomerium.<\/td><td>When you want to SSH into a server secured by Pomerium.<\/td><td><code>pomerium-cli tcp ssh.internal.com:22<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Note: Pomerium is primarily controlled via its configuration file, not dynamic CLI commands.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 17. Configuration Explained<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The core of Pomerium is the <code>config.yaml<\/code> file. It has two main sections: <strong>Global Settings<\/strong> and <strong>Routes<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">YAML<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># 1. Global Settings<\/span>\naddress: <span class=\"hljs-string\">\":443\"<\/span>                    <span class=\"hljs-comment\"># Port Pomerium listens on<\/span>\nauthenticate_service_url: https:<span class=\"hljs-comment\">\/\/auth.company.com # Where users go to login<\/span>\n\nidp_provider: okta                 <span class=\"hljs-comment\"># Which IdP you are using<\/span>\nidp_client_id: <span class=\"hljs-string\">\"xyz123\"<\/span>            <span class=\"hljs-comment\"># Provided by your IdP<\/span>\nidp_client_secret: <span class=\"hljs-string\">\"secret987\"<\/span>     <span class=\"hljs-comment\"># Provided by your IdP<\/span>\nidp_provider_url: <span class=\"hljs-string\">\"https:\/\/mycompany.okta.com\"<\/span> <span class=\"hljs-comment\"># Required for some IdPs<\/span>\n\ncookie_secret: <span class=\"hljs-string\">\"base64-secret\"<\/span>     <span class=\"hljs-comment\"># Encrypts the session cookie<\/span>\n\n<span class=\"hljs-comment\"># 2. Routes (The Apps you are protecting)<\/span>\nroutes:\n  - from: https:<span class=\"hljs-comment\">\/\/wiki.company.com # What the user types in their browser<\/span>\n    to: http:<span class=\"hljs-comment\">\/\/10.0.1.55:8080      # Where the actual app lives internally<\/span>\n    policy:                        <span class=\"hljs-comment\"># The security rules<\/span>\n      - allow:\n          <span class=\"hljs-keyword\">or<\/span>:\n            - domain: <span class=\"hljs-string\">\"company.com\"<\/span> <span class=\"hljs-comment\"># Anyone with a company email can enter<\/span>\n  \n  - from: https:<span class=\"hljs-comment\">\/\/admin.company.com<\/span>\n    to: http:<span class=\"hljs-comment\">\/\/10.0.1.56:80<\/span>\n    policy:\n      - allow:\n          <span class=\"hljs-keyword\">or<\/span>:\n            - claim\/email: <span class=\"hljs-string\">\"admin@company.com\"<\/span> <span class=\"hljs-comment\"># Only this specific person can enter<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Configuration Precedence:<\/strong> Pomerium reads configuration in this order:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Environment Variables (e.g., <code>POMERIUM_IDP_CLIENT_ID<\/code>)<\/li>\n\n\n\n<li><code>config.yaml<\/code><\/li>\n\n\n\n<li>Default values.<em>Best Practice:<\/em> Store secrets (like <code>idp_client_secret<\/code>) as environment variables, and routing rules in the YAML file.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 18. Logs, Status and Verification<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checking Logs:<\/strong> If Pomerium isn&#8217;t working, logs are your best friend. Pomerium outputs JSON-formatted logs by default.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Docker:<\/strong> <code>docker logs pomerium_container_name<\/code><\/li>\n\n\n\n<li><strong>Ubuntu (Systemd):<\/strong> <code>journalctl -u pomerium -f<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Understanding the Logs:<\/strong> When a user accesses an app successfully, you will see a log entry with <code>\"level\":\"info\"<\/code> and <code>\"authorize_result\":\"allow\"<\/code>. If they are blocked, you will see <code>\"authorize_result\":\"deny\"<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Troubleshooting Status:<\/strong> Pomerium has a built-in special endpoint. If you navigate to <code>[https:\/\/app.company.com\/.pomerium\/](https:\/\/app.company.com\/.pomerium\/)<\/code> in your browser (notice the <code>\/.pomerium\/<\/code> path), Pomerium will display a status page showing your current session, email, and the claims it received from the Identity Provider.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">PART VI \u2014 REAL-WORLD APPLICATIONS<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 19. Practical Use Cases<\/h2>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>The Internal Wiki (Small Team):<\/strong>\n<ul class=\"wp-block-list\">\n<li><em>Problem:<\/em> The team relies on an internal Confluence\/Wiki server, but VPNs are too slow.<\/li>\n\n\n\n<li><em>Solution:<\/em> Pomerium routes <code>wiki.company.com<\/code> to the internal server, allowing access to anyone with a <code>@company.com<\/code> Google Workspace email.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>The Developer Admin Panel (Production):<\/strong>\n<ul class=\"wp-block-list\">\n<li><em>Problem:<\/em> Developers need access to a Jenkins CI\/CD dashboard, but it has weak built-in security.<\/li>\n\n\n\n<li><em>Solution:<\/em> Pomerium wraps the Jenkins UI. Only users in the GitHub <code>DevOps<\/code> team are allowed through. Jenkins is completely hidden from the internet.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Legacy App Protection (Enterprise):<\/strong>\n<ul class=\"wp-block-list\">\n<li><em>Problem:<\/em> A 15-year-old HR application doesn&#8217;t support Two-Factor Authentication.<\/li>\n\n\n\n<li><em>Solution:<\/em> Pomerium is placed in front of it. Pomerium forces the user to log in via Okta (which enforces 2FA). The legacy app is instantly secured without rewriting its code.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 20. A Complete Hands-On Project<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Project:<\/strong> Secure a private Grafana dashboard.<strong>Prerequisites:<\/strong> Docker, Docker Compose, a free Auth0 account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1: Auth0 Setup<\/strong><\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Create a &#8220;Regular Web Application&#8221; in Auth0.<\/li>\n\n\n\n<li>Set Allowed Callback URLs to: <code>[https:\/\/grafana.local.test\/.pomerium\/callback](https:\/\/grafana.local.test\/.pomerium\/callback)<\/code><\/li>\n\n\n\n<li>Save Client ID, Client Secret, and your Auth0 Domain.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2: File Structure<\/strong> Create a directory with <code>docker-compose.yaml<\/code> and <code>config.yaml<\/code>. Update your computer&#8217;s <code>hosts<\/code> file (e.g., <code>\/etc\/hosts<\/code>) to map <code>127.0.0.1 grafana.local.test<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 3: config.yaml<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">YAML<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">authenticate_service_url: https:<span class=\"hljs-comment\">\/\/grafana.local.test<\/span>\nidp_provider: auth0\n<span class=\"hljs-attr\">idp_client_id<\/span>: <span class=\"hljs-string\">\"YOUR_AUTH0_CLIENT_ID\"<\/span>\n<span class=\"hljs-attr\">idp_client_secret<\/span>: <span class=\"hljs-string\">\"YOUR_AUTH0_SECRET\"<\/span>\n<span class=\"hljs-attr\">idp_provider_url<\/span>: <span class=\"hljs-string\">\"https:\/\/YOUR_AUTH0_DOMAIN.auth0.com\"<\/span>\n<span class=\"hljs-attr\">cookie_secret<\/span>: <span class=\"hljs-string\">\"YOUR_COOKIE_SECRET\"<\/span>\n<span class=\"hljs-attr\">shared_secret<\/span>: <span class=\"hljs-string\">\"YOUR_SHARED_SECRET\"<\/span>\n<span class=\"hljs-attr\">insecure_server<\/span>: <span class=\"hljs-literal\">true<\/span>\n\n<span class=\"hljs-attr\">routes<\/span>:\n  - <span class=\"hljs-keyword\">from<\/span>: https:<span class=\"hljs-comment\">\/\/grafana.local.test<\/span>\n    to: http:<span class=\"hljs-comment\">\/\/grafana:3000<\/span>\n    policy:\n      - allow:\n          and:\n            - authenticated_user: <span class=\"hljs-literal\">true<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 4: docker-compose.yaml<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">YAML<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">version: <span class=\"hljs-string\">\"3\"<\/span>\nservices:\n  pomerium:\n    image: pomerium\/pomerium:latest\n    volumes:\n      - .\/config.yaml:\/pomerium\/config.yaml\n    ports:\n      - <span class=\"hljs-string\">\"443:443\"<\/span>\n  \n  grafana:\n    image: grafana\/grafana:latest\n    <span class=\"hljs-comment\"># Notice we DO NOT map ports here. Grafana is entirely isolated.<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 5: Testing<\/strong> Run <code>docker-compose up<\/code>. Navigate to <code>[https:\/\/grafana.local.test](https:\/\/grafana.local.test)<\/code>. You will be intercepted by Auth0. Log in, and you will securely access Grafana!<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">PART VII \u2014 ADVANCED CAPABILITIES<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 21. Advanced Features<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>JWT Identity Headers:<\/strong> When Pomerium proxies traffic to the backend app (like Grafana), it injects a HTTP header called <code>X-Pomerium-Jwt-Assertion<\/code>. The backend app can read this JWT to automatically log the user in, achieving true Single Sign-On (SSO). The backend never needs to ask for a password again.<\/li>\n\n\n\n<li><strong>Redis \/ Postgres Databroker Storage:<\/strong> By default, Pomerium stores session data in memory. If Pomerium restarts, everyone gets logged out. In production, you configure Pomerium to use Redis or PostgreSQL to store sessions so it becomes Highly Available (HA).<\/li>\n\n\n\n<li><strong>TCP Proxying:<\/strong> Pomerium isn&#8217;t just for web apps. By using the <code>pomerium-cli<\/code> on a client machine, you can secure SSH or database (e.g., MySQL) traffic through Pomerium&#8217;s Zero Trust policies.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 22. Security Best Practices<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Insecure Practice<\/strong><\/td><td><strong>Safer Alternative<\/strong><\/td><\/tr><\/thead><tbody><tr><td>Hardcoding <code>idp_client_secret<\/code> in <code>config.yaml<\/code>.<\/td><td>Passing secrets via Environment Variables or a Secret Manager (Kubernetes Secrets).<\/td><\/tr><tr><td>Leaving backend apps exposed to the network.<\/td><td>Blocking all incoming traffic to the backend app <em>except<\/em> traffic coming from Pomerium&#8217;s IP.<\/td><\/tr><tr><td>Using basic <code>claim\/email<\/code> policies for large teams.<\/td><td>Using IdP group memberships (e.g., <code>groups: admin<\/code>) so offboarding is centralized in the IdP.<\/td><\/tr><tr><td><code>insecure_server: true<\/code><\/td><td>Only use this for local testing. In production, use valid TLS certificates (like Let&#8217;s Encrypt).<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 23. Production-Ready Architecture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When moving to production, you graduate from the &#8220;All-in-One&#8221; Docker container to a <strong>Distributed Architecture<\/strong>.<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Load Balancer:<\/strong> Place a cloud Load Balancer in front of Pomerium.<\/li>\n\n\n\n<li><strong>Separate Services:<\/strong> Run the <code>Proxy<\/code>, <code>Authenticate<\/code>, <code>Authorize<\/code>, and <code>Databroker<\/code> as separate pods in Kubernetes.<\/li>\n\n\n\n<li><strong>Database:<\/strong> Connect the <code>Databroker<\/code> to a managed PostgreSQL database.<\/li>\n\n\n\n<li><strong>Scale:<\/strong> If traffic increases, you can scale just the <code>Proxy<\/code> service horizontally to 5, 10, or 20 instances.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Code snippet<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">flowchart<\/span> <span class=\"hljs-selector-tag\">TD<\/span>\n    <span class=\"hljs-selector-tag\">Internet<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Public Internet\"<\/span>]<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">LB<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Cloud Load Balancer\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">LB<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">Proxy1<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Pomerium Proxy 1\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">LB<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">Proxy2<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Pomerium Proxy 2\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">Proxy1<\/span> &lt;<span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">AuthZ<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"Pomerium Authorize\"<\/span>]<\/span>\n    <span class=\"hljs-selector-tag\">Proxy2<\/span> &lt;<span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">AuthZ<\/span>\n    <span class=\"hljs-selector-tag\">AuthZ<\/span> &lt;<span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">DB<\/span><span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">\"PostgreSQL (State)\"<\/span>]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h1 class=\"wp-block-heading\">PART VIII \u2014 TROUBLESHOOTING AND MASTERY<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 24. Common Problems and Solutions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Problem or Error<\/strong><\/td><td><strong>Likely Cause<\/strong><\/td><td><strong>How to Diagnose<\/strong><\/td><td><strong>Solution<\/strong><\/td><\/tr><\/thead><tbody><tr><td><strong>Infinite Redirect Loop<\/strong><\/td><td>The <code>authenticate_service_url<\/code> doesn&#8217;t match the URL you configured in the IdP.<\/td><td>Check browser network tab. Does it bounce between the IdP and Pomerium?<\/td><td>Ensure the callback URL in your IdP exactly matches the <code>authenticate_service_url<\/code> + <code>\/.pomerium\/callback<\/code>.<\/td><\/tr><tr><td><strong>HTTP 403 Access Denied<\/strong><\/td><td>User authenticated successfully, but policy rejected them.<\/td><td>Go to <code>\/.pomerium\/<\/code> on the app URL and check your &#8220;Claims&#8221;.<\/td><td>Update <code>config.yaml<\/code> policy to match the user&#8217;s actual email or group claim.<\/td><\/tr><tr><td><strong>HTTP 503 Service Unavailable<\/strong><\/td><td>Pomerium cannot reach the internal backend app.<\/td><td>Run <code>curl -I http:\/\/backend-ip<\/code> from the machine running Pomerium.<\/td><td>Fix network routing, firewall rules, or Docker networks so Pomerium can reach the <code>to:<\/code> address.<\/td><\/tr><tr><td><strong>Invalid Certificate Error<\/strong><\/td><td>Browser warns connection is not private.<\/td><td>Look at the certificate details in the browser.<\/td><td>Provide valid TLS certificates to Pomerium, or set <code>insecure_server: true<\/code> if testing locally.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 25. Best Practices and Common Mistakes<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Habit:<\/strong> Always check the <code>\/.pomerium\/<\/code> endpoint when writing policies. It shows you exactly what data the IdP is sending to Pomerium, making it easy to write correct <code>claim<\/code> rules.<\/li>\n\n\n\n<li><strong>Mistake:<\/strong> Forgetting that Pomerium secures the <em>front door<\/em>, but not the <em>back window<\/em>. If Pomerium protects an internal server at <code>10.0.0.5<\/code>, you MUST configure the firewall on <code>10.0.0.5<\/code> to drop all traffic that doesn&#8217;t come from Pomerium. Otherwise, someone on the internal network can just bypass Pomerium entirely.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 26. Learning Roadmap<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Stage<\/strong><\/td><td><strong>Topics<\/strong><\/td><td><strong>Practical Activity<\/strong><\/td><td><strong>Expected Skill<\/strong><\/td><\/tr><\/thead><tbody><tr><td>1. Fundamentals<\/td><td>Zero Trust, IdPs, Reverse Proxies<\/td><td>Explain why VPNs are vulnerable to lateral movement.<\/td><td>Conceptual understanding<\/td><\/tr><tr><td>2. First use<\/td><td>Docker Compose, Basic config<\/td><td>Run Pomerium locally with GitHub SSO.<\/td><td>Independent basic use<\/td><\/tr><tr><td>3. Practical use<\/td><td>Policies, Routes, Headers<\/td><td>Protect multiple apps with different user policies.<\/td><td>Working proficiency<\/td><\/tr><tr><td>4. Advanced use<\/td><td>PostgreSQL Databroker, TCP<\/td><td>Deploy Pomerium in Kubernetes; SSH via Pomerium.<\/td><td>Applied competence<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 27. Exercises and Knowledge Assessment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Q1 (Beginner):<\/strong> Why does Pomerium not require a database to store user passwords?<em>Answer:<\/em> Because Pomerium relies on an external Identity Provider (IdP) like Google or Okta to handle authentication and passwords.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Q2 (Intermediate):<\/strong> Your policy allows <code>domain: company.com<\/code>, but the CEO gets a 403 Forbidden error. How do you troubleshoot?<em>Answer:<\/em> Ask the CEO to navigate to <code>[https:\/\/app.company.com\/.pomerium\/](https:\/\/app.company.com\/.pomerium\/)<\/code> and read the JSON output. Verify that their identity token actually includes the expected domain claim.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Q3 (Advanced):<\/strong> How does Pomerium prevent users from having to log into Google on every single webpage click?<em>Answer:<\/em> Pomerium utilizes the Databroker and a signed session cookie. Upon the first successful login, Pomerium issues a cookie encrypted with the <code>cookie_secret<\/code>. Subsequent requests are verified locally by Pomerium without pinging the IdP until the session expires.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chapter 28. Final Revision and Quick Reference<\/h2>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>What it is:<\/strong> An open-source Identity-Aware Proxy (IAP) that enforces Zero Trust access to internal applications.<\/li>\n\n\n\n<li><strong>Core mechanism:<\/strong> It intercepts traffic, redirects unauthenticated users to an IdP, evaluates access policies, and proxies authorized traffic to the backend.<\/li>\n\n\n\n<li><strong>Essential Terminology:<\/strong> IdP (Identity Provider), Route (public to private map), Policy (access rules), JWT (secure identity token).<\/li>\n\n\n\n<li><strong>Configuration:<\/strong> Driven by <code>config.yaml<\/code> containing <code>authenticate_service_url<\/code>, IdP secrets, and a list of <code>routes<\/code>.<\/li>\n\n\n\n<li><strong>Best Use Case:<\/strong> Replacing clunky client VPNs for employees accessing internal web dashboards, wikis, and administrative tools.<\/li>\n\n\n\n<li><strong>Alternatives:<\/strong> Cloudflare Access (SaaS ZTNA), Tailscale (Mesh VPN).<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Quick Cheat Sheet (config.yaml Route Syntax):<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">YAML<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">routes:\n  - from: https:<span class=\"hljs-comment\">\/\/public-facing-url.com<\/span>\n    to: http:<span class=\"hljs-comment\">\/\/internal-server-ip:port<\/span>\n    policy:\n      - allow:\n          <span class=\"hljs-keyword\">and<\/span>:    <span class=\"hljs-comment\"># Can be 'and', 'or', 'not'<\/span>\n            - domain: <span class=\"hljs-string\">\"mycompany.com\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>","protected":false},"excerpt":{"rendered":"<p>PART I \u2014 UNDERSTAND THE WORLD BEFORE THE TOOL Chapter 1. The Big Picture Imagine a medieval castle. To keep the treasure inside safe, the king builds&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-79538","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79538","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=79538"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79538\/revisions"}],"predecessor-version":[{"id":79539,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79538\/revisions\/79539"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=79538"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=79538"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=79538"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}