Securing Internal Applications with Pomerium: A Guide to Implementing Zero-Trust Application Proxy on a VPS
The Paradigm Shift: Moving from Legacy VPNs to Zero-Trust
For decades, the standard blueprint for securing internal corporate applications relied heavily on the concept of a secure perimeter. Organizations deployed Virtual Private Networks (VPNs) to grant remote employees access to the internal network. However, in the modern landscape of distributed teams and cloud infrastructure, this traditional model introduces significant vulnerabilities. Once a malicious actor or a compromised device breaches the VPN perimeter, they gain lateral access to the entire network infrastructure.
Furthermore, legacy VPNs often degrade user experience due to latency, require cumbersome client software installations, and place a heavy administrative burden on IT teams. To mitigate these risks, forward-thinking enterprises are transitioning to a Zero-Trust Architecture (ZTA). The core principle of Zero-Trust is simple yet absolute: never trust, always verify. Access is never granted based solely on network location; instead, every single request must be authenticated, authorized, and encrypted before access is permitted.
This technical guide provides a comprehensive walkthrough on how to implement a self-hosted, clientless Zero-Trust Application Proxy on a Virtual Private Server (VPS) using Pomerium. By the end of this article, you will be able to securely expose your internal services to authorized users without opening inbound firewall ports to the public internet or requiring a client-side VPN application.
What is Pomerium and How Does It Work?
Pomerium is an open-source, identity-aware access proxy that integrates seamlessly with your existing Identity Providers (IdPs) such as Google Workspace, Azure Active Directory, Okta, or GitHub. It acts as a centralized gatekeeper for your internal applications (e.g., internal dashboards, databases, development servers, and admin panels).
When a user attempts to access a protected application, Pomerium intercepts the request and evaluates it based on context and identity:
- Identity Verification: Pomerium checks if the user is authenticated via your chosen Identity Provider.
- Contextual Authorization: It evaluates defined policies (e.g., "Is this user a member of the engineering team?" or "Is the request originating from an approved country?").
- Secure Upstream Delivery: If authorized, Pomerium proxies the traffic securely to the internal application running on your loopback interface (localhost) or an isolated internal network.
Pomerium provides a seamless, browser-based single sign-on (SSO) experience for users while ensuring that the underlying application remains completely invisible to the public internet. This effectively eliminates the attack surface for automated bot scans and brute-force attempts.
Prerequisites and System Architecture
Before initiating the deployment process, ensure your environment meets the following baseline requirements:
- A Linux VPS: A virtual private server running a modern distribution such as Ubuntu 22.04 LTS or Debian 12.
- A Registered Domain Name: Access to a domain (e.g.,
company.internal) with the ability to configure DNS records (A, AAAA, or CNAME). - An Identity Provider (IdP): Developer credentials/API keys from an IdP. In this guide, we will use Google OAuth 2.0 as our reference implementation.
- Docker and Docker Compose: Installed on the VPS to containerize and manage the Pomerium stack and upstream applications efficiently.
The Architectural Layout
Unlike a traditional setup where applications listen on public ports (e.g., 80 or 443) and manage their own authentication, our Zero-Trust architecture isolates applications completely:
- The public firewall blocks all inbound traffic except for ports 80 (for Let's Encrypt validation) and 443 (managed exclusively by Pomerium).
- Internal applications (such as a private wiki, a Promethus dashboard, or an internal API) are configured to listen strictly on
127.0.0.1or reside within an isolated Docker bridge network. - Pomerium acts as the single entry point, handles TLS termination automatically, validates user session tokens, and proxies clean traffic internally.
Step-by-Step Implementation Guide
Step 1: Configuring Your Identity Provider (Google OAuth)
To allow Pomerium to verify user identities, you must register it as an application within your Identity Provider's console. For Google Workspace/Cloud:
- Navigate to the Google Cloud Console and create or select a project.
- Go to APIs & Services > OAuth consent screen. Configure the user type (Internal for organizations, External for testing) and save.
- Navigate to Credentials > Create Credentials > OAuth client ID.
- Select Web application as the application type.
- Under Authorized redirect URIs, input Pomerium's designated redirect endpoint:
[https://authenticate.yourdomain.com/oauth2/callback](https://authenticate.yourdomain.com/oauth2/callback). - Click create and securely record the generated Client ID and Client Secret.
Step 2: Preparing DNS and the VPS Environment
Point your domain and desired subdomains to your VPS public IP address via your DNS provider's dashboard. For example:
authenticate.yourdomain.com→ Point to VPS IP (Used for Pomerium authentication flow)app1.yourdomain.com→ Point to VPS IP (Your first internal application)
Next, SSH into your VPS and establish a structured directory layout for the deployment:
mkdir -p ~/pomerium-proxy/config
cd ~/pomerium-proxyStep 3: Generating Secure Cryptographic Keys
Pomerium requires a 32-byte, base64-encoded key to encrypt session cookies and state securely. You can generate this directly from your Linux terminal using the openssl utility:
openssl rand -base64 32Keep this output secure; it will be populated into your configuration file as the cookie_secret.
Step 4: Writing the Pomerium Policy Configuration
Create the core configuration file within the directory structure: nano config/config.yaml. Populate it with the following production-ready structure, adapting the placeholders to your domain and credentials:
# Pomerium Core Configuration File
address: ":443"
# Identity Provider Settings
idp_provider: "google"
idp_client_id: "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"
idp_client_secret: "YOUR_GOOGLE_CLIENT_SECRET"
# Cryptographic Cookie Secret
cookie_secret: "YOUR_GENERATED_BASE64_SECRET"
# Authentication URL
authenticate_service_url: [https://authenticate.yourdomain.com](https://authenticate.yourdomain.com)
# Access Control Policies and Routing
routes:
- from: [https://app1.yourdomain.com](https://app1.yourdomain.com)
to: http://internal-app:8080
policy:
- allow:
and:
- email:
is: "[email protected]"
- from: [https://app2.yourdomain.com](https://app2.yourdomain.com)
to: [http://127.0.0.1:9000](http://127.0.0.1:9000)
policy:
- allow:
and:
- domain:
is: "yourdomain.com"This policy configuration enforces that app1 is accessible *only* to a single specific email address, while app2 is accessible to any authenticated user belonging to the yourdomain.com organization.
Step 5: Deploying via Docker Compose
To orchestrate Pomerium alongside a sample internal application for testing, create a docker-compose.yml file in the root of your project directory:
version: '3.8'
services:
pomerium:
image:独立 pomerium/pomerium:latest
container_name: pomerium_proxy
volumes:
- ./config/config.yaml:/pomerium/config.yaml:ro
ports:
- "80:80"
- "443:4443"
environment:
- TZ=UTC
restart: always
depends_on:
- internal-app
internal-app:
image: turing85/echo-server:latest
container_name: internal_app_service
expose:
- "8080"
environment:
- PORT=8080
restart: alwaysNote: Ensure your network firewall or standard reverse proxy maps host port 443 directly to Pomerium's handling port so that Pomerium can automatically provision and renew TLS certificates via Let's Encrypt.
Execute the stack using the following command:
docker compose up -dVerifying and Auditing the Zero-Trust Architecture
Once the containers are operational, navigate to [https://app1.yourdomain.com](https://app1.yourdomain.com) via a standard web browser. You should experience the following sequence:
- Redirection: You are instantly redirected to the secure Google OAuth login page.
- Authentication: You log in using your enterprise or authorized personal credentials.
- Evaluation: Google redirects back to Pomerium's authentication service, which cross-references your identity with the YAML file policies.
- Access Granted/Denied: If you match the criteria, you are cleanly proxied to the echo server application. If you attempt access with an unauthorized email, Pomerium throws a
403 Forbiddenerror page, completely insulating the application layer from interaction.
The Importance of Centralized Audit Logs
A primary benefit of implementing an identity-aware proxy over a traditional VPN is the granularity of access logging. Traditional VPNs log when a user connects to the network, but cannot easily track what specific URLs or internal API endpoints they hit. Pomerium records every single HTTP request along with the authenticated user's identity, the source IP, and the authorization decision. These logs can be forwarded to a centralized SIEM system for strict compliance and continuous security monitoring.
Conclusion: Enterprise Security for Any Scale
Deploying a Zero-Trust Application Proxy with Pomerium on a standard VPS levels the playing field, allowing smaller engineering teams and businesses to leverage enterprise-grade security models without costly infrastructure investments. By decoupling authentication from the individual applications and discarding the legacy, vulnerable perimeter-based client VPN model, you significantly harden your security posture, reduce the exposed attack surface to zero, and deliver a frictionless, native web experience for your distributed workforce.
