Pomerium on Ubuntu 24.04 on Azure User Guide
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
pomeriumsystem 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
/healthzload balancer probe and a301redirect to HTTPS, plus a local demo upstream on127.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 thepomeriumuser, 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

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.

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.

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.

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.comthroughout/etc/pomerium/config.yamlwith 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 thecertificates:block, or enable Pomerium's built in autocert to obtain a certificate from Let's Encrypt automatically by settingautocert: trueandautocert_dir: /etc/pomerium/(the VM must be reachable on :443 from the internet for the ACME challenge). Thensudo 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.comandsecure.pomerium.example.comdemo 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.yamlholds 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.