Securing TCP-based services
Learn how to use Pomerium's TCP Proxying support to connect to TCP services, such as databases and non-HTTP protocols.
Background
When replacing a traditional VPN, there are often non-HTTP based applications you still need to reach. Pomerium can provide the same type of protection to these services with Pomerium CLI, a client-side application to proxy TCP connections.
Authentication and authorization configuration is shared with standard HTTP routes, and the underlying transport is still encrypted between the end-user and Pomerium.
- Pomerium authorizes TCP connections on a per-connection basis (unlike HTTP requests, which it authorizes on a per-request basis)
- Pomerium only authorizes the TCP connection; it does not interact with application level authorization systems
Overview
Review the steps below to understand how you will secure TCP connections with Pomerium:
- Run Pomerium CLI's tcpcommand in your workstation to listen for TCP connections on loopback
- When an inbound connection is made, Pomerium CLI proxies the connection through pomerium, authenticating the user if needed
- Pomerium authorizes the connection and forwards it to the upstream service
Before you start
To complete this guide, you need:
- Docker
- Docker Compose
- Pomerium CLI
- mkcert (optional)
Certificates (optional)
If you want to generate your own locally trusted certificates with mkcert, run the following command:
mkcert -install
   mkcert '*.localhost.pomerium.io'
This will install a trusted CA and generate a new wildcard certificate:
- _wildcard.localhost.pomerium.io.pem
- _wildcard.localhost.pomerium.io-key.pem
Configure
Pomerium
Add the configuration below to your configuration file:
authenticate_service_url: https://authenticate.pomerium.app
# Uncomment to use certificates (optional)
# certificates:
#   - cert: /pomerium/cert.pem
#     key: /pomerium/key.pem
databroker_storage_type: postgres
databroker_storage_connection_string: postgresql://postgres:postgres@pgsql:5432
routes:
  - from: tcp+https://redis.localhost.pomerium.io:6379
    to: tcp://redis:6379
    policy:
      - allow:
          or:
            - domain:
                is: example.com
  - from: tcp+https://ssh.localhost.pomerium.io:22
    to: tcp://ssh:2222
    policy:
      - allow:
          or:
            - domain:
                is: example.com
  - from: tcp+https://pgsql.localhost.pomerium.io:5432
    to: tcp://pgsql:5432
    policy:
      - allow:
          or:
            - domain:
                is: example.com
Make sure you update each example.com instance to match the domain of your email address.
Docker Compose
Create a docker-compose.yaml file to run Pomerium and the services you will connect to over TCP.
The following services are included in the Docker Compose file:
- Redis
- SSH
- Postgres
version: "3"
services:
  pomerium:
    image: pomerium/pomerium:latest
    volumes:
      # Uncomment to mount certificates (optional)
      # - ./_wildcard.localhost.pomerium.io.pem:/pomerium/cert.pem:ro
      # - ./_wildcard.localhost.pomerium.io-key.pem:/pomerium/key.pem:ro
      - ./config.yaml:/pomerium/config.yaml:ro
    ports:
      - 443:443
  redis:
    image: redis:latest
    expose:
      - 6379
  ssh:
    image: linuxserver/openssh-server:latest
    expose:
      - 2222
    environment:
      PASSWORD_ACCESS: "true"
      USER_PASSWORD: supersecret
      USER_NAME: user
  pgsql:
    image: postgres
    restart: always
    environment:
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_DB=postgres
    expose:
      - 5432
Connect
To connect to your service, ensure pomerium-cli is in your $PATH and run the tcp command, specifying the service you wish to reach.
pomerium-cli tcp [hostname]:[port]
pomerium-cli will select a random port on localhost by default, but you can specify a port manually if desired. Keep reading for some specific application examples using the sample docker-compose.yaml.
If you haven't configured a trusted certificate, you'll need to add the --disable-tls-verification flag to the pomerium-cli commands below.
Without this flag, TCP connections will fail unless you include a trusted CA and certificate in your configuration.
In a production environment, it's critical that you configure Pomerium to use correct server certificates.
- See Certificates settings and Upstream mTLS (services) for more information on configuring certificates in Pomerium 
- See the TCP Reference page for a complete list of commands available in Pomerium CLI 
Redis
Start a proxy to Redis in the background:
$ pomerium-cli tcp redis.localhost.pomerium.io:6379 --listen localhost:6379
2023/10/02 12:20:05 listening on 127.0.0.1:6379
Start the Redis client:
$ redis-cli info
# Server
redis_version:7.0.5
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:d9291579292e26e3
redis_mode:standalone
os:Linux 6.3.13-linuxkit aarch64
arch_bits:64
monotonic_clock:POSIX clock_gettime
multiplexing_api:epoll
atomicvar_api:c11-builtin
gcc_version:10.2.1
process_id:1
process_supervised:no
run_id:e4c843df14511765f0e6284690be2f10c6b39e28
tcp_port:6379
server_time_usec:1696349965506132
uptime_in_seconds:75
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:1851149
executable:/data/redis-server
config_file:
io_threads_active:0
Postgres
In your Docker Compose file, postgres is the configured password for the postgres user and database.
Start a proxy to Postgres in the background:
$ pomerium-cli tcp pgsql.localhost.pomerium.io:5432 --listen localhost:5432
2023/10/03 12:22:08 listening on 127.0.0.1:5432
After password authentication, connect and list the schemas:
$  psql -h localhost -W -U postgres -c '\dn'
Password:
       List of schemas
   Name   |       Owner
----------+-------------------
 pomerium | postgres
 public   | pg_database_owner
(2 rows)
SSH
Configure
To configure your SSH client to use Pomerium's TCP support for SSH routes, create the following entry in your ssh_config or ~/.ssh/config:
Host *.localhost.pomerium.io
    ProxyCommand pomerium-cli tcp --listen - %h:%p
- Be sure to substitute your domain for localhost.pomerium.io
- Be sure pomerium-cliis in your$PATH
SSH clients can use external programs to establish a connection to a host. Most frequently, this is for using an SSH jump host to reach a target system. However, you can use any transport application. You can use Pomerium CLI's tcp command in conjunction with this configuration.
See the following links for more information:
Connect
Start a proxy to the SSH client in the background:
$ pomerium-cli tcp ssh.localhost.pomerium.io:22 --listen :2222
2023/10/03 12:29:17 listening on 127.0.0.1:50741
Initiate the SSH connection:
$ ssh myuser@ssh.localhost.pomerium.io -p 2222
myuser@ssh.localhost.pomerium.io's password:
Welcome to OpenSSH Server
4e737e02c43f:~$
In your example Docker Compose file, you have an SSH server configured with supersecret as the password for myuser.
That's it! A Pomerium proxy will be started automatically whenever you SSH to a host under localhost.pomerium.io.