Po
Security Azure

Pomerium on Ubuntu 24.04 on Azure User Guide

| Product: Pomerium on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Pomerium on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Pomerium is the open source identity aware access proxy: a single Go binary that runs 24/7 under systemd, terminates its own TLS, and gates access to your internal applications by authenticating every request against your own identity provider (any OIDC provider) and enforcing per route authorization policy. You grant access by identity and context instead of by network location, the foundation of a zero trust posture.

The image installs the latest stable Pomerium Core release, resolved at build time from the official Pomerium apt repository and baked reproducibly into the image (the exact version is recorded in /opt/pomerium/VERSION). Pomerium runs as a single Go binary under systemd as the unprivileged pomerium system user, which is granted the CAP_NET_BIND_SERVICE capability so it can bind port 443 without running as root.

HTTPS is intrinsic. Pomerium is a TLS proxy: it terminates its own TLS on port 443. On the very first boot this image generates a fresh per instance self signed certificate (the certificate covers the proxy domains plus the VM public IP), and generates the two secrets Pomerium needs, the shared secret and the cookie secret (each a 32 byte key), directly into the configuration. Those secrets are unique to this VM and are generated from the kernel random source on first boot. No shared secret and no default login ever ships inside the image.

You bring your own identity provider. Pomerium authenticates users against your OIDC provider (Google, Microsoft Entra ID, Okta, Auth0, Authelia, Keycloak and others). The image ships with a placeholder identity provider block that you replace with your own values after launch. Because Pomerium discovers your provider lazily, the service starts and serves public routes immediately, and only contacts your provider when a user signs in.

What is included:

  • The latest stable Pomerium Core release (single Go binary), run under systemd as the unprivileged pomerium system user

  • Pomerium terminating TLS on :443 itself, with a per instance self signed certificate regenerated on first boot

  • A tiny nginx on :80 that serves an unauthenticated /healthz load balancer probe and a 301 redirect to HTTPS, plus a local demo upstream on 127.0.0.1:8000

  • A working public demo route (proxied with no authentication) and a gated demo route (an unauthenticated request is redirected to sign in) so you can see the proxy working the moment it boots

  • Per instance shared secret and cookie secret generated on first boot into /etc/pomerium/config.yaml (owned by the pomerium user, not world readable), rendered from a placeholder template so the shipped image carries no secret

  • A placeholder OIDC identity provider block you replace with your own provider, and a demo authorization policy you replace with your own rules

  • Ubuntu 24.04 LTS base with latest security patches applied at build time

  • Azure Linux Agent for seamless cloud integration and SSH key injection

  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region

  • Subscription to the Pomerium listing on Azure Marketplace

  • An OIDC identity provider you control (for production sign in), and a DNS domain you can point at the VM

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a proxy fronting a handful of applications. For heavier traffic use Standard_D2s_v5 or larger. Pomerium is stateless in this all in one configuration (its data broker is in memory), so it scales with CPU and memory.

Step 1: Deploy from the Azure Portal

Search Pomerium in Marketplace, select the cloudimg publisher, click Create. NSG rules: TCP 22 (admin), TCP 443 (the proxy over HTTPS) and TCP 80 (HTTP to HTTPS redirect and health probe) from the networks your users connect from.

Step 2: Deploy from the Azure CLI

RG="pomerium-prod"; LOCATION="eastus"; VM_NAME="pomerium-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/pomerium-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name pomerium-vnet --address-prefix 10.100.0.0/16 --subnet-name pomerium-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name pomerium-nsg
az network nsg rule create -g "$RG" --nsg-name pomerium-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name pomerium-nsg --name allow-https --priority 110 \
  --destination-port-ranges 443 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name pomerium-nsg --name allow-http --priority 120 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name pomerium-vnet --subnet pomerium-subnet --nsg pomerium-nsg --public-ip-sku Standard

Step 3: Verify the Pomerium and nginx services

Connect over SSH, then confirm both services are active and see which ports are bound. Pomerium listens on :443 (its own TLS), nginx listens on :80 (redirect and health probe) and on 127.0.0.1:8000 (the loopback demo upstream):

sudo systemctl status pomerium nginx --no-pager | head -14
sudo ss -tlnp | grep -E ':443|:80 |:8000'
cat /opt/pomerium/VERSION

pomerium.service and nginx.service both active (running); the Pomerium version baked into the image; ss shows Pomerium listening on :443 for its own TLS, nginx on :80 for the redirect and health probe, and a loopback demo upstream on 127.0.0.1:8000

Step 4: Understand the per instance secrets and configuration

Pomerium needs two secrets, a shared secret and a cookie secret, each a 32 byte key. This image generates both on the first boot into /etc/pomerium/config.yaml, which is owned by the pomerium user and is not world readable. The shipped image carries only placeholders, so no two VMs ever share a secret. The endpoints for this VM are summarized in a root only file:

sudo cat /root/pomerium-credentials.txt
sudo grep -E '^address|^authenticate_service_url|^autocert|shared_secret|cookie_secret|^  - from|^    to' /etc/pomerium/config.yaml
sudo openssl x509 -in /etc/pomerium/certs/cert.pem -noout -subject -ext subjectAltName

The credentials file lists the proxy URL, the authenticate URL and the placeholder base domain. In the configuration you can see address: ":443", autocert: false (Pomerium serves the self signed certificate rather than requesting one from Let's Encrypt), the public and secure demo routes, and the two per instance secrets (masked in the screenshot below). The certificate Subject Alternative Names cover the proxy domains and this VM.

The pomerium-credentials.txt file with the proxy endpoints, the config.yaml highlights showing address :443, autocert false and the public and secure demo routes with the per instance shared and cookie secrets masked, and the self signed certificate Subject Alternative Names covering the proxy domains and the VM public IP

Step 5: See the proxy and the access gate over TLS

Pomerium is domain based: it routes by hostname. The image ships placeholder domains under pomerium.example.com. To reach them before you set up DNS, map the three hostnames to the VM public IP in your client's hosts file, then trust the self signed certificate. On the VM itself we can demonstrate the same behavior with curl using --resolve:

# /ping is served without a host match (Envoy level health), so it works on the raw IP:
curl -sk -o /dev/null -w 'ping: HTTP %{http_code}\n' https://127.0.0.1/ping
# The PUBLIC demo route is proxied with no authentication -> HTTP 200:
curl -sk -o /dev/null -w 'public route: HTTP %{http_code}\n' \
  --resolve public.pomerium.example.com:443:127.0.0.1 https://public.pomerium.example.com/
# The SECURE demo route is identity gated -> unauthenticated requests are redirected to sign in:
curl -sk -o /dev/null -w 'secure route: HTTP %{http_code} -> %{redirect_url}\n' \
  --resolve secure.pomerium.example.com:443:127.0.0.1 https://secure.pomerium.example.com/
# Pomerium publishes an unauthenticated JWKS document upstreams use to verify signed identity:
curl -sk -o /dev/null -w 'jwks: HTTP %{http_code}\n' \
  --resolve authenticate.pomerium.example.com:443:127.0.0.1 \
  https://authenticate.pomerium.example.com/.well-known/pomerium/jwks.json

The health check returns HTTP 200, the public route returns HTTP 200 (Pomerium proxied the demo upstream with no sign in), and the secure route returns HTTP 302 with a redirect to the authenticate service sign in page, the identity aware gate in action. There is no anonymous access to a gated route.

curl showing /ping return HTTP 200 over TLS, the public demo route return HTTP 200 proxied with no authentication, the secure demo route return HTTP 302 with a redirect to the authenticate sign in URL, and the JWKS endpoint return HTTP 200

Step 6: Reach the proxy from your workstation

To browse the demo routes from your own machine before setting up DNS, map the placeholder hostnames to the VM public IP and trust the certificate. Copy the certificate from the VM, then add a hosts entry (replace <public-ip> with your VM public IP):

scp azureuser@<public-ip>:/etc/pomerium/certs/cert.pem pomerium.crt
# Linux/macOS: append to /etc/hosts (needs sudo):
echo "<public-ip> authenticate.pomerium.example.com public.pomerium.example.com secure.pomerium.example.com" | sudo tee -a /etc/hosts
curl --cacert pomerium.crt https://public.pomerium.example.com/     # 200, the demo upstream
curl --cacert pomerium.crt -I https://secure.pomerium.example.com/  # 302 to sign in

Step 7: Wire up your identity provider

To actually sign users in, replace the placeholder OIDC block with your own provider. Create an OIDC application at your provider, set the redirect URL to https://authenticate.<your-domain>/oauth2/callback, then edit the configuration:

sudo grep -nE 'idp_provider|idp_provider_url|idp_client_id|idp_client_secret' /etc/pomerium/config.yaml

Edit the configuration (for example sudo nano /etc/pomerium/config.yaml) and set the four idp_ values to your provider's, then apply the change:

sudo systemctl restart pomerium

Set idp_provider to your provider type (for example google, azure, okta, auth0, or oidc for a generic provider), idp_provider_url to your provider's issuer URL, and idp_client_id / idp_client_secret to the values from the OIDC application you created. After the restart, a request to the secure route redirects the user to your provider, and after sign in Pomerium enforces the route policy.

The placeholder identity provider block in config.yaml showing idp_provider oidc with a placeholder issuer URL and placeholder client id and secret to replace, alongside the secure route policy that allows a user whose email domain matches, the two values you change to wire up your own OIDC provider and authorization rules

Step 8: Add your own applications and policy

The two demo routes show the pattern. To protect your own application, add a route pointing at your upstream and an authorization policy:

routes:
  - from: https://app.your-domain.com
    to: http://127.0.0.1:9000          # your upstream application
    policy:
      - allow:
          and:
            - domain:
                is: your-company.com    # any signed in user from your domain

Pomerium passes a signed identity header to your upstream so the application knows who the user is. Policies can match on email, group, domain, device and more. Reload after editing with sudo systemctl restart pomerium.

Step 9: Use your own domain and a trusted certificate (production)

The self signed certificate and the pomerium.example.com placeholder domains are a convenience for getting started. For production:

  • Replace pomerium.example.com throughout /etc/pomerium/config.yaml with a domain you control, and point its DNS (a wildcard record is convenient) at the VM public IP.

  • Provide a trusted certificate. Either drop a CA signed certificate and key into /etc/pomerium/certs/ and keep the certificates: block, or enable Pomerium's built in autocert to obtain a certificate from Let's Encrypt automatically by setting autocert: true and autocert_dir: /etc/pomerium/ (the VM must be reachable on :443 from the internet for the ACME challenge). Then sudo systemctl restart pomerium.

  • Users then reach your routes at your own domain with no certificate trust step.

Step 10: Server components

Component Version / Detail
Proxy Pomerium Core (latest stable release, baked reproducibly, see /opt/pomerium/VERSION)
TLS terminated by Pomerium on :443 (self signed per VM, autocert off), certificate at /etc/pomerium/certs/
Front helper nginx (:80 -> 301 https, unauthenticated /healthz, loopback 127.0.0.1:8000 demo upstream)
Secrets per instance shared_secret + cookie_secret (32 byte), generated first boot into /etc/pomerium/config.yaml
Identity your OIDC provider (placeholder block shipped, you replace it)
Data broker in memory (stateless all in one)
Service user pomerium (unprivileged, granted CAP_NET_BIND_SERVICE to bind :443)
Operating system Ubuntu 24.04 LTS (patched at build)
License Apache 2.0 (Pomerium Core)

Step 11: Managing the Pomerium service

sudo systemctl status pomerium --no-pager | head -12
sudo systemctl is-active pomerium nginx

Apply a configuration change with sudo systemctl restart pomerium, reload the nginx helper with sudo systemctl reload nginx, and follow the logs live with sudo journalctl -u pomerium -f. The configuration lives at /etc/pomerium/config.yaml. After any change, restart Pomerium. The service uses a systemd watchdog, so a hung process is automatically restarted.

Step 12: Security recommendations

  • Restrict the NSG. Allow TCP 443 (and 22 for admin) only from the networks your users connect from.

  • Use a trusted certificate and your own domain (Step 9) so users validate the proxy without a manual trust step and OIDC redirects work cleanly.

  • Keep the demo routes out of production. Remove the public.pomerium.example.com and secure.pomerium.example.com demo routes once you have added your own, so nothing is exposed unintentionally.

  • Scope your policies tightly. Prefer group and email based rules over broad domain rules, and layer device and context conditions where your provider supports them.

  • Protect the secrets. /etc/pomerium/config.yaml holds the shared and cookie secrets and is not world readable; keep it that way and rotate the secrets if a VM is ever exposed.

  • Keep the OS patched. Unattended security upgrades remain enabled on the running VM.

Step 13: Support and Licensing

Pomerium Core is distributed under the Apache License 2.0. This cloudimg image bundles the unmodified official open source Core release; the separate commercial Pomerium Enterprise Console is not included. cloudimg provides the packaging, hardening, per instance secret and TLS automation, the working demo routes, and 24/7 support with a guaranteed 24 hour response SLA. Pomerium is an independent open source project and this image is not affiliated with or endorsed by Pomerium, Inc.

Deploy on Azure

Find Pomerium on Ubuntu 24.04 on the Azure Marketplace, published by cloudimg. Deploy from the Portal or the Azure CLI as shown above.

Need Help?

Email support@cloudimg.co.uk for deployment help, configuration questions, or licensing enquiries.