Zero Trust Everything: The Complete Guide to Self-Hosting Teleport for Secure Infrastructure Access
CoderOasis runs Teleport internally for everything — admin panel access, secure database connections, Kubernetes, and zero-trust philosophy across our entire team. This is the complete technical guide to installing, configuring, and running your own self-hosted Teleport cluster from scratch.
Let me tell you exactly how someone gets access to CoderOasis's servers.
They don't get a password. They don't get an SSH key. There's no VPN to connect to. Nobody emails them a credential. The first time they need access to anything — a server, a database, the Ghost CMS admin panel — they open a browser, authenticate through our identity provider with hardware MFA enforced, and Teleport issues them a short-lived cryptographic certificate that expires in eight hours. That certificate is scoped precisely to the resources their role permits. When the eight hours are up, it's gone. There's nothing to revoke because there's nothing persistent to begin with.
Every session is logged. Every command typed in an SSH session is recorded. Every database query runs through an audit trail. Every access attempt — successful or not — lands in a structured event log that we can search, replay, and alert on. If someone on our team's laptop is compromised, the attacker has a stale certificate that expired hours ago and no persistent credentials to pivot with.
This is Teleport. And I want to be specific about what it replaced at CoderOasis: a shared bastion server with SSH keys distributed to team members, a LastPass shared folder for database passwords, and a VPN that half our team forgot to connect to before doing things they shouldn't have done without it. The migration to Teleport took a weekend. The security posture improvement was immediate and measurable.
This article is the complete technical guide to self-hosting Teleport. Not the 15-minute quickstart that gets you a demo cluster with no production hardening. The real setup — architecture decisions, TLS configuration, RBAC design, database access, Kubernetes integration, MCP server protection, application proxying, machine identity for CI/CD, and the audit log pipeline. Everything.
If you want the cloud-hosted version and don't want to run your own infrastructure, Teleport Cloud is the path. This guide is for teams that either need data sovereignty, have a compliance requirement for on-prem, or -- like us -- just prefer to own their infrastructure stack. We covered that philosophy across the self-hosted email guide and the full productivity stack guide. Teleport fits the same philosophy: critical security infrastructure should be something you own and understand, not something you rent and trust blindly.
What Is Teleport and Why Is It Different From a VPN or Bastion Host?
Before the install commands, the mental model. You need to understand what Teleport actually is architecturally, because "it's like a VPN" and "it's like a bastion host" are both wrong in ways that matter.
The Problem With VPNs
A VPN grants broad network access. Once you're on the VPN, you're on the network. You can reach any host on that network segment that the firewall allows. This is the antithesis of zero trust. "Being on the network" is not an identity. It's a location claim that attackers routinely fake, steal, or route through compromised machines.
VPNs also don't tell you what happened. You know someone connected. You don't know which specific database they queried, which commands they ran on which server, or whether they accessed anything they shouldn't have. VPN logs show connection events, not access events.
The Problem With SSH Key Management
SSH keys are credentials. Credentials can be stolen. Credentials can be accidentally committed to Git repositories -- we've all seen the GitHub secret scanner alerts. SSH keys don't expire. A key generated for a contractor who left six months ago is still valid on every server where their authorized_keys entry was added -- if anyone remembered to remove it, which they often don't.
At CoderOasis, we had a legacy bastion server with authorized_keys files. When we audited it before migrating to Teleport, we found four keys for people who no longer had any relationship with the project. Those keys had root access. Nobody had thought to remove them because there was no offboarding process that included "remove SSH keys from bastion."
What Teleport Actually Is
Teleport is an identity-aware access proxy. Every piece of it is built around the principle that identity is the perimeter. Not the network. Not the firewall. The cryptographically verified identity of the specific person or machine making a specific request.
Architecturally, Teleport has three core services:
The Auth Service is the certificate authority and the brain of the cluster. It issues short-lived X.509 and SSH certificates to verified users and machines. It stores the audit log. It enforces RBAC. It maintains the cluster's certificate authority. In small deployments, this runs on the same host as the Proxy Service.
The Proxy Service is the public-facing entry point. Users and agents connect to it. It handles TLS termination, routes connections to the appropriate resources, and streams sessions to the Auth Service for audit logging. This is the only thing that needs to be publicly accessible.
Agents are lightweight processes that run on or near the resources you want to protect. The SSH agent opens a reverse tunnel to the Proxy Service -- your servers never need to be publicly accessible. The Database agent proxies connections to your databases. The Kubernetes agent sits in front of your cluster. The Application agent proxies internal web apps.
The reverse tunnel architecture is one of Teleport's most underrated features. Your database doesn't need to be reachable from the internet. Your Kubernetes API server doesn't need to be publicly accessible. The agent initiates an outbound connection to the Proxy Service, and traffic flows through that tunnel. Your firewall never needs an inbound rule for production databases.
The Zero Trust Properties Teleport Actually Provides
No standing privileges. By default, nobody has persistent access to anything. Access is granted by issuing a certificate for the requested resource. When the certificate expires, access ends automatically. No manual revocation required.
No shared secrets. There are no passwords, no SSH keys, no API tokens for accessing infrastructure. Everything is certificate-based. A compromised developer laptop doesn't give an attacker persistent access -- only access until the current certificate expires (maximum TTL: whatever you configure, typically 8-24 hours).
Complete audit trail. Every access event -- login, command, query, keystroke in a recorded session -- is a structured event with the authenticated user's identity, the resource accessed, the timestamp, and the session ID. Not "someone connected from this IP at this time." Specifically: which user, which certificate, which resource, and for SSH sessions, every command they typed.
Least privilege by default. RBAC roles define exactly which resources each user can access and what they can do with them. A developer role can SSH into dev servers but not production. A database analyst role can SELECT from the reporting database but not UPDATE or access the payments database. The Kubernetes role can kubectl get pods in the application namespace but can't touch kube-system. These constraints are enforced at the proxy level – they're not just audit controls, they're access controls.
Architecture Decisions Before You Install Anything
Single Node vs. High Availability
For small teams and self-hosted setups, a single node running both Auth and Proxy services is perfectly viable. That's what CoderOasis runs. The trade-off is availability: if the Teleport node goes down, access to your infrastructure goes through the CLI (SSH keys you've kept as emergency access) or your emergency runbook.
For production systems where Teleport downtime means engineering teams can't access anything, HA is worth the complexity:
- Auth Service: Run multiple instances behind a load balancer. Requires external state storage --
etcdcluster or DynamoDB for the backend, not the default local filesystem. - Proxy Service: Stateless, run multiple instances behind a load balancer.
- Session recording storage: S3 (or MinIO for self-hosted) instead of local disk.
For most self-hosted teams, start single-node. Migrate to HA when you have the team to operate it.
Storage Backend
The default backend is a local SQLite-compatible database at /var/lib/teleport. Fine for single-node. For HA or for teams that want better durability guarantees, the options are:
- DynamoDB (AWS) -- The native HA backend if you're on AWS. Works out of the box.
- etcd -- For on-prem HA. Requires running an etcd cluster.
- Firestore (GCP) -- GCP native equivalent.
- CockroachDB -- Community-contributed, works well for self-hosted HA.
Where to Run It
Teleport's Proxy Service needs to be publicly accessible (or at least reachable by your team) on port 443. Everything else can be private. Our setup: a Hetzner VPS with Teleport Proxy+Auth, every other server and service behind private networking with only the Teleport agent making outbound connections to the proxy.
Requirements for the proxy/auth node:
- 2 vCPU minimum, 4 recommended
- 4GB RAM minimum for single-node
- 50GB SSD for audit logs (size depends on session recording volume)
- Static public IP or hostname
- Port 443 open for web UI and TLS multiplexing
- Ubuntu 24.04 LTS or Debian 12 recommended
TLS Certificate Strategy
Teleport needs a valid TLS certificate. Three options:
- ACME/Let's Encrypt (recommended): Teleport handles certificate renewal automatically. Requires port 80 or port 443 to be accessible.
- Existing certificate: Supply
cert-fileandkey-filepointing to your certificate files. Manage renewal yourself. - Self-signed (development only): Do not use in production. Browser and client warnings at every connection.
We use ACME. Teleport handles the renewal. It's never failed in two years.
Installing Teleport
Preparation
On your Proxy/Auth host:
# Update the system
apt update && apt upgrade -y
# Create the teleport user -- we run the service as non-root for everything
# except the initial binding on port 443
useradd --system --no-create-home --shell /bin/false teleport
# Set up the firewall
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment "SSH -- replace with Teleport SSH after setup"
ufw allow 443/tcp comment "Teleport Web UI and TLS multiplexing"
ufw enable
# DNS: set up your domain before proceeding
# You need:
# teleport.yourdomain.com -> your server IP (A record)
# *.teleport.yourdomain.com -> same IP (wildcard for app subdomains)
Installing the Teleport Binary
# Install the Teleport package -- always pin to a specific major version
# As of this writing, v17 is current stable
TELEPORT_VERSION="17.5.1"
curl https://cdn.teleport.dev/install.sh | bash -s ${TELEPORT_VERSION}
# Verify installation
teleport version
tctl version
tsh version
# All three should show the same version
Generating the Initial Configuration
# Generate base configuration with ACME/Let's Encrypt
# Replace teleport.yourdomain.com with your actual domain
# Replace [email protected] with your actual email
sudo teleport configure -o file \
--acme \
[email protected] \
--cluster-name=teleport.yourdomain.com
# This creates /etc/teleport.yaml
cat /etc/teleport.yaml
Editing the Configuration File
The generated config is a starting point. Here's the full production configuration we recommend, with every setting explained:
# /etc/teleport.yaml
version: v3
teleport:
nodename: teleport-main
data_dir: /var/lib/teleport
log:
output: /var/log/teleport/teleport.log
severity: INFO
format:
output: json # JSON for log aggregation (Grafana, ELK, etc.)
# Limit connections per source IP to prevent abuse
connection_limits:
max_connections: 5000
max_users: 250
auth_service:
enabled: true
cluster_name: "teleport.yourdomain.com"
listen_addr: "127.0.0.1:3025" # Auth only needs to be localhost if proxy is colocated
public_addr: "teleport.yourdomain.com:3025"
# Authentication configuration
authentication:
type: local # 'local' for local users + optional SSO
second_factor: "webauthn" # Enforce hardware MFA -- no TOTP-only
webauthn:
rp_id: "teleport.yourdomain.com"
# Enforce per-session MFA for privileged access
# Users must tap their hardware key for every new SSH/DB session
require_session_mfa: false # Set to true for highest security
# Session recording -- record every session to local storage
# For production: use S3/MinIO for durability
session_recording: "node" # Record at node, not proxy
# Certificate lifetimes
# User certs: short -- they re-authenticate often
# Host certs: longer -- servers don't log in daily
client_idle_timeout: 1h # Disconnect idle sessions after 1 hour
disconnect_expired_cert: true # Hard disconnect when cert expires
# Audit log
audit_events_uri:
- "file:///var/lib/teleport/log"
proxy_service:
enabled: true
# Single port for everything -- multiplexed TLS
# This handles web UI, SSH, DB, Kubernetes, app traffic on 443
web_listen_addr: "0.0.0.0:443"
public_addr: "teleport.yourdomain.com:443"
# ACME certificate management
acme:
enabled: true
email: "[email protected]"
# Kubernetes proxy address -- reachable by kubectl via Teleport
kube_public_addr: "teleport.yourdomain.com:443"
# Multiplexing mode -- puts everything on port 443
# No need to expose 3022, 3023, 3024 separately
proxy_listener_mode: multiplex
# PROXY protocol -- enable if behind a load balancer that sends PROXY headers
# Leave unset (or 'off') for direct Teleport deployments
# proxy_protocol: "off"
ssh_service:
enabled: true # Enable SSH on the Teleport node itself
listen_addr: "0.0.0.0:3022"
labels:
role: teleport-proxy
env: production
# These are disabled here -- run as separate agents on protected resources
# (shown in subsequent sections)
app_service:
enabled: false
db_service:
enabled: false
kubernetes_service:
enabled: false
Systemd Service and Starting Teleport
# Create log directory
mkdir -p /var/log/teleport
chown teleport:teleport /var/log/teleport
# Install as systemd service
teleport install systemd -o /etc/systemd/system/teleport.service
# Enable and start
systemctl daemon-reload
systemctl enable teleport
systemctl start teleport
# Watch the startup logs -- verify everything initialized
journalctl -u teleport -f
You should see lines like:
{"level":"info","msg":"Auth service is starting on 127.0.0.1:3025"}
{"level":"info","msg":"Proxy service is starting on 0.0.0.0:443"}
{"level":"info","msg":"SSH service is starting on 0.0.0.0:3022"}
{"level":"info","msg":"Certificate issued from Let's Encrypt"}
Navigate to https://teleport.yourdomain.com in a browser. You should see the Teleport login page with a valid TLS certificate.
Creating the First Admin User
# Create the initial admin user
# --roles: editor (can modify resources) + access (can access infrastructure)
# --logins: the Linux usernames this user can log in as via SSH
tctl users add admin \
--roles=editor,access \
--logins=ubuntu,root
# This prints a signup URL:
# https://teleport.yourdomain.com/web/invite/<token>
# Open this in a browser to complete registration and enroll MFA
Complete MFA enrollment -- Teleport supports FIDO2/WebAuthn hardware keys (YubiKey, etc.), biometric authenticators (Touch ID, Windows Hello), and TOTP apps. We enforce hardware keys for admin accounts.
After registration, test access:
# Install tsh locally on your workstation
# macOS
brew install teleport
# Linux
curl https://cdn.teleport.dev/install.sh | bash
# Log in
tsh login --proxy=teleport.yourdomain.com --user=admin
# List accessible servers
tsh ls
# Should show the Teleport proxy node itself (since we enabled SSH service)
Designing Your RBAC Model
Before adding any resources, design your role structure. RBAC is where Teleport earns its keep and where teams that rush through setup pay for it later.
Teleport's Role Model
A Teleport role has four sections:
- allow: What this role permits
- deny: What this role explicitly blocks (deny beats allow)
- options: Behavior modifiers (session TTL, MFA requirements, recording settings)
- metadata: Name, version, labels
Labels are the key mechanism. Nodes, databases, apps, and Kubernetes clusters all have labels. Roles grant access by matching labels -- not by listing specific resources. This is how you maintain "all production servers" access without updating roles every time you add a server.
The Role Structure We Use at CoderOasis
# /etc/teleport/roles/contributor.yaml
# For external contributors and limited team members
kind: role
version: v7
metadata:
name: contributor
description: "Read-only access to non-sensitive resources"
spec:
allow:
# Can SSH to development servers only
node_labels:
env: ["dev", "staging"]
logins:
- developer
- "{{internal.logins}}" # Maps to their Linux username
# Can access non-production databases
db_labels:
env: ["dev", "staging"]
db_names:
- "appdb_dev"
- "appdb_staging"
db_users:
- "readonly"
# Can view (not replay) their own sessions
rules:
- resources: ["session"]
verbs: ["list", "read"]
deny:
# Explicitly block production even if labels match
node_labels:
env: ["production"]
db_labels:
env: ["production"]
options:
max_session_ttl: "8h"
disconnect_expired_cert: true
# Require MFA per session for database access
require_session_mfa: "hardware_key" # Require hardware key tap per session
# Record all sessions
record_session:
desktop: true
default: "best_effort"
---
# /etc/teleport/roles/engineer.yaml
# Full-time team members -- access to all environments with audit
kind: role
version: v7
metadata:
name: engineer
description: "Full engineer access -- all environments"
spec:
allow:
# All servers
node_labels:
"*": "*"
logins:
- ubuntu
- "{{internal.logins}}"
# All databases -- read/write
db_labels:
"*": "*"
db_names:
- "*"
db_users:
- "appuser"
- "readonly"
- "{{internal.db_users}}"
# All apps
app_labels:
"*": "*"
# Kubernetes -- developer groups on all clusters
kubernetes_groups:
- developers
- staging-admins
kubernetes_labels:
"*": "*"
kubernetes_resources:
- kind: "*"
namespace: "*"
name: "*"
rules:
- resources: ["session"]
verbs: ["list", "read"]
options:
max_session_ttl: "12h"
disconnect_expired_cert: true
require_session_mfa: "hardware_key"
record_session:
default: "best_effort"
---
# /etc/teleport/roles/admin.yaml
# CoderOasis core team -- full access including Teleport management
kind: role
version: v7
metadata:
name: coderoasis-admin
description: "Full administrative access including Teleport cluster management"
spec:
allow:
node_labels:
"*": "*"
logins:
- root
- ubuntu
- "{{internal.logins}}"
db_labels:
"*": "*"
db_names:
- "*"
db_users:
- "*"
app_labels:
"*": "*"
kubernetes_groups:
- system:masters
kubernetes_labels:
"*": "*"
kubernetes_resources:
- kind: "*"
namespace: "*"
name: "*"
# Full Teleport cluster management
rules:
- resources: ["*"]
verbs: ["*"]
options:
max_session_ttl: "8h"
disconnect_expired_cert: true
require_session_mfa: "hardware_key"
record_session:
default: "best_effort"
---
# /etc/teleport/roles/access-request-approver.yaml
# Can approve just-in-time access requests
kind: role
version: v7
metadata:
name: access-approver
spec:
allow:
review_requests:
roles:
- engineer
- coderoasis-admin
preview_as_roles:
- engineer
Apply roles with tctl:
# Apply all role files
for f in /etc/teleport/roles/*.yaml; do
tctl create -f "$f"
echo "Applied $f"
done
# Verify roles exist
tctl get roles
Adding SSH Nodes
Every server you want to access through Teleport gets a Teleport SSH agent. The agent runs as a systemd service, opens a reverse tunnel to your Proxy Service, and registers the node in the cluster.
Install and Configure a Node Agent
On the target server:
# Install Teleport (same version as the cluster)
TELEPORT_VERSION="17.5.1"
curl https://cdn.teleport.dev/install.sh | bash -s ${TELEPORT_VERSION}
# Generate a join token on the Auth server
# Tokens are one-time-use and expire -- this prevents unauthorized nodes from joining
# Run this on your Auth/Proxy host:
# tctl tokens add --type=node --ttl=1h
# Copy the token output
On the Auth host, generate the token:
tctl tokens add --type=node --ttl=1h
# Output:
# The invite token: abc123def456...
# This token will expire in 60 minutes
Back on the target server, create the config:
# /etc/teleport.yaml on the target node
version: v3
teleport:
nodename: web-server-01 # Unique name for this node
data_dir: /var/lib/teleport
log:
output: /var/log/teleport/teleport.log
severity: INFO
# Connect via the Proxy Service -- this works from behind NAT
proxy_server: "teleport.yourdomain.com:443"
# The join token
auth_token: "abc123def456..."
# CA pin -- prevents connecting to a different cluster if DNS is poisoned
# Get this from: tctl status (on the auth server)
ca_pin: "sha256:abc123..."
auth_service:
enabled: false
proxy_service:
enabled: false
ssh_service:
enabled: true
listen_addr: "0.0.0.0:3022"
# Labels -- these are what RBAC roles match against
labels:
env: production
role: webserver
team: platform
region: eu-west
# Enhanced session recording -- capture commands at kernel level
# Requires Linux 5.8+ with BPF capabilities
enhanced_recording:
enabled: true
command_buffer_size: 8192
disk_buffer_size: 300
network_buffer_size: 8192
app_service:
enabled: false
db_service:
enabled: false
kubernetes_service:
enabled: false
# Install and start the service
teleport install systemd -o /etc/systemd/system/teleport.service
systemctl daemon-reload
systemctl enable teleport
systemctl start teleport
# Verify the node appears in the cluster (run on Auth host)
tctl nodes ls
# Output:
# Node Name Address Labels
# ────────────────── ──────────────────── ───────────────────────────
# web-server-01 127.0.0.1:3022 env=production,role=webserver
Accessing Nodes
From your workstation:
# List all nodes you can access
tsh ls
# Filter by label
tsh ls env=production
# SSH into a node
tsh ssh ubuntu@web-server-01
# Or use the standard SSH syntax through the Teleport proxy
ssh -o ProxyCommand="tsh proxy ssh %r@%h:%p" ubuntu@web-server-01
# Set up SSH config for native SSH client support
tsh config > ~/.ssh/config.d/teleport.conf
# Now: ssh ubuntu@web-server-01 works natively
The session is authenticated, authorized against your RBAC role, and recorded. Run tsh recordings ls to see your sessions. The Web UI at https://teleport.yourdomain.com lets you replay any recorded session.
Hardening: Close Port 22
Once Teleport SSH is working, close port 22 on your nodes. All access goes through Teleport. Port 22 is eliminated as an attack surface:
# On each node after verifying Teleport SSH works
ufw delete allow 22/tcp
ufw reload
# Keep emergency access: a local console or cloud provider serial console
# in case Teleport is unavailable -- but remove the public SSH attack surface
Database Access
This is one of Teleport's most powerful and most underused features. Instead of distributing database passwords to every developer who needs occasional database access, Teleport issues short-lived database certificates and proxies the connection. No developer ever sees a database password.
How Database Access Works
The Teleport Database Service runs as an agent near your database. When a developer runs tsh db connect, Teleport:
- Verifies their certificate and RBAC permissions
- Issues a short-lived X.509 certificate for the specific database and user
- Routes their connection through the database agent
- Logs every query (for supported databases) to the audit log
The database never receives the developer's identity directly -- it receives a certificate from Teleport's CA, mapped to a database user your RBAC configuration specifies. The database sees a connection fromreadonlyorappuser-- not from a named individual. Teleport's audit log maps that connection to the specific engineer who initiated it.
Setting Up PostgreSQL Access
On the database host (or a host that can reach the database):
# /etc/teleport.yaml -- database agent configuration
version: v3
teleport:
nodename: db-agent-01
data_dir: /var/lib/teleport
proxy_server: "teleport.yourdomain.com:443"
auth_token: "db-join-token-here" # tctl tokens add --type=db --ttl=1h
ca_pin: "sha256:..."
auth_service:
enabled: false
proxy_service:
enabled: false
ssh_service:
enabled: false
db_service:
enabled: true
# List of databases to protect
databases:
- name: "coderoasis-production"
description: "Production PostgreSQL"
protocol: "postgres"
uri: "localhost:5432" # Or the database host if remote
labels:
env: production
type: postgres
team: platform
tls:
mode: "verify-full" # Verify PostgreSQL's certificate
# server_name: "postgres.internal"
- name: "coderoasis-staging"
description: "Staging PostgreSQL"
protocol: "postgres"
uri: "pg-staging.internal:5432"
labels:
env: staging
type: postgres
- name: "coderoasis-mysql"
description: "Production MySQL -- legacy"
protocol: "mysql"
uri: "localhost:3306"
labels:
env: production
type: mysql
app_service:
enabled: false
kubernetes_service:
enabled: false
Configure PostgreSQL to trust Teleport certificates:
-- In PostgreSQL, create the database users that Teleport will map to
-- Teleport issues certificates for these users -- no passwords
CREATE USER "readonly" WITH LOGIN;
GRANT CONNECT ON DATABASE appdb TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly;
CREATE USER "appuser" WITH LOGIN;
GRANT CONNECT ON DATABASE appdb TO appuser;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO appuser;
In pg_hba.conf, add certificate authentication for the Teleport CA's client certificates:
# Allow Teleport certificate authentication from the db agent
hostssl appdb readonly 127.0.0.1/32 cert clientcert=verify-ca
hostssl appdb appuser 127.0.0.1/32 cert clientcert=verify-ca
Configure PostgreSQL to trust Teleport's certificate authority:
# Get Teleport's database CA certificate
# Run on the auth host:
tctl auth export --type=db-client > /etc/postgresql/teleport-ca.crt
# In postgresql.conf:
# ssl = on
# ssl_ca_file = '/etc/postgresql/teleport-ca.crt'
Start the database agent and connect:
# On the auth host
tctl tokens add --type=db --ttl=1h
# Copy the token, update auth_token in /etc/teleport.yaml on db agent host
# On the db agent host
systemctl start teleport
# Verify database appears in cluster
# On auth host:
tctl db ls
# From developer workstation:
tsh db ls
# Log in to the database
tsh db login --db-user=readonly --db-name=appdb coderoasis-production
# Connect using your native client -- no password prompt
psql "service=coderoasis-production"
# Or directly:
tsh db connect coderoasis-production --db-user=readonly --db-name=appdb
Every query runs through Teleport's audit log. From the web UI, you can see the full query history for any database session: who connected, when, which queries they ran.
Database Access for MySQL, MongoDB, Redis
The configuration is nearly identical -- just change protocol:
databases:
- name: "mongo-production"
protocol: "mongodb"
uri: "mongodb://mongo.internal:27017"
labels:
env: production
- name: "redis-cache"
protocol: "redis"
uri: "redis://redis.internal:6379"
labels:
env: production
Connect:
tsh db connect mongo-production --db-user=appuser
# Opens a mongo shell authenticated via Teleport certificate
tsh db connect redis-cache
# Opens a redis-cli session
Kubernetes Access
Teleport's Kubernetes integration is where the zero-trust model really starts to shine for modern infrastructure. Instead of distributing kubeconfig files that contain long-lived credentials, every kubectl call flows through Teleport's proxy, authenticated by the user's current certificate.
Registering a Kubernetes Cluster
In the Kubernetes cluster you want to protect, install the Teleport Kubernetes agent via Helm:
# Add the Teleport Helm repository
helm repo add teleport https://charts.releases.teleport.dev
helm repo update
# Generate a join token for the Kubernetes agent
# Run on auth host:
tctl tokens add --type=kube --ttl=1h
# Copy the token
# Create namespace
kubectl create namespace teleport-agent
# Create the values file
cat > teleport-kube-agent-values.yaml <<EOF
proxyAddr: "teleport.yourdomain.com:443"
authToken: "kube-join-token-here" # From tctl tokens add above
kubeClusterName: "production-k8s" # Name in Teleport -- can differ from actual name
# Labels for RBAC matching
labels:
env: production
region: eu-west
team: platform
# Resource limits
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
EOF
# Install the agent
helm install teleport-kube-agent teleport/teleport-kube-agent \
--namespace teleport-agent \
--values teleport-kube-agent-values.yaml
# Verify the agent connected
kubectl get pods -n teleport-agent
# Wait for: teleport-kube-agent-... Running
# On auth host -- verify cluster appears
tctl kube ls
Kubernetes RBAC Configuration
Teleport maps users to Kubernetes RBAC groups. The Teleport role specifies which Kubernetes groups a user gets when they access a cluster. You define the Kubernetes RBAC permissions for those groups in the cluster itself.
Teleport role defining Kubernetes access:
# The 'engineer' role from earlier already includes:
# kubernetes_groups: [developers, staging-admins]
# This maps the Teleport user to these Kubernetes RBAC groups
Kubernetes RBAC resources for those groups:
# Apply this in your Kubernetes cluster
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: developers
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"] # Allow kubectl exec -- Teleport records these sessions
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: developers
subjects:
- kind: Group
name: developers # Matches kubernetes_groups in Teleport role
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: developers
apiGroup: rbac.authorization.k8s.io
Using Kubernetes through Teleport:
# From developer workstation -- after tsh login:
# List available Kubernetes clusters
tsh kube ls
# Authenticate to a cluster and update kubeconfig
tsh kube login production-k8s
# Now kubectl works through Teleport
kubectl get pods -n application
kubectl logs -n application deployment/backend
kubectl exec -it -n application pod/backend-xxxx -- /bin/sh
# Every kubectl call is logged:
# - Who made the request (Teleport user identity)
# - Which cluster
# - Which namespace and resource
# - The response code
# - Timestamp
# Switch between clusters
tsh kube login staging-k8s
kubectl get pods # Now talking to staging
Every kubectl exec session is recorded in Teleport. You can replay it from the web UI. This is the audit trail that compliance requires and that most Kubernetes setups simply don't have.
Proxying Internal Web Apps
At CoderOasis we use Teleport's Application Service to proxy our Ghost CMS admin panel and our internal monitoring dashboards. Instead of exposing these on the public internet or requiring a VPN, Teleport proxies them as subdomains of our Teleport cluster domain. Only authenticated, authorized users can reach them.
How Application Access Works
The App agent registers an internal application with the Teleport cluster. Teleport creates a subdomain: ghost-admin.teleport.yourdomain.com. When an authenticated user navigates to that URL, Teleport verifies their certificate and role, then proxies the request to the internal application. The internal app never needs to be publicly accessible.
This is how our Ghost admin panel works. The admin panel runs on localhost:2368 on our Ghost server. Teleport proxies it at ghost.teleport.yourdomain.com. Nobody outside our team can reach it, and access is RBAC-controlled.
Configuring Application Access
On the server running the application (or a server that can reach the app):
# /etc/teleport.yaml -- app agent configuration
version: v3
teleport:
nodename: app-agent-01
data_dir: /var/lib/teleport
proxy_server: "teleport.yourdomain.com:443"
auth_token: "app-join-token"
ca_pin: "sha256:..."
auth_service:
enabled: false
proxy_service:
enabled: false
ssh_service:
enabled: false
app_service:
enabled: true
apps:
# Ghost CMS Admin Panel
- name: "ghost-admin"
description: "CoderOasis Ghost CMS Admin"
uri: "http://localhost:2368" # Internal URL of the app
public_addr: "ghost.teleport.yourdomain.com" # Public URL via Teleport
labels:
env: production
app: ghost
team: editorial
# Grafana dashboard -- internal monitoring
- name: "grafana"
description: "Infrastructure Monitoring"
uri: "http://localhost:3000"
public_addr: "grafana.teleport.yourdomain.com"
labels:
env: production
app: grafana
team: platform
# Traefik dashboard -- internal, never publicly exposed
- name: "traefik"
description: "Traefik Reverse Proxy Dashboard"
uri: "http://localhost:8080"
public_addr: "traefik.teleport.yourdomain.com"
labels:
env: production
app: traefik
team: platform
# Any internal web service
- name: "internal-api-docs"
description: "Internal API Documentation"
uri: "http://api-server.internal:8000/docs"
public_addr: "api-docs.teleport.yourdomain.com"
labels:
env: production
team: engineering
db_service:
enabled: false
kubernetes_service:
enabled: false
Add wildcard DNS:
# In your DNS configuration:
# *.teleport.yourdomain.com -> your Teleport proxy IP
# This allows Teleport to serve apps on subdomains automatically
Update RBAC to allow app access:
The roles we defined earlier already include app_labels: "*": "*" for engineers and admins. For more granular control:
# Restrict Ghost admin access to editorial role only
kind: role
version: v7
metadata:
name: editorial
spec:
allow:
app_labels:
app: ["ghost"]
# No SSH, no database access
options:
max_session_ttl: "8h"
Access the apps:
# List available applications
tsh apps ls
# Log in to an application -- opens browser to the app
tsh apps login ghost-admin
# Browser opens ghost.teleport.yourdomain.com -- authenticated via Teleport
# Or browse directly:
# Navigate to ghost.teleport.yourdomain.com
# Teleport intercepts, checks your certificate, proxies to localhost:2368
# Ghost admin panel appears -- no Ghost-level authentication needed
# (Teleport auth replaces it)
The Web UI equivalent: in the Teleport web interface, your authorized applications appear as tiles. Click one, it opens in a new tab, authenticated. No passwords, no separate logins.
Zero Trust for AI Agents
This is the most forward-looking feature in Teleport's 2025-2026 release cycle and the reason we're covering it specifically. If you're running AI coding assistants, automation agents, or MCP servers that give AI agents access to your infrastructure, you have an identity problem that most people haven't solved yet.
The problem: your AI agent has a token or API key to access your MCP server. That token has whatever permissions were granted when someone created it. It doesn't expire unless you rotate it manually. If the agent runtime or the token is compromised, an attacker has standing access to everything the agent could access.
Teleport's Secure MCP capability applies the same certificate-based, RBAC-enforced, fully-audited model to MCP connections that it applies to SSH and databases.
How MCP Access Works
Teleport proxies connections between MCP clients (AI agents like Claude, Cursor, or your custom tooling) and MCP servers (the backend services that give AI agents tools). The agent authenticates to Teleport, receives a short-lived certificate, and the MCP connection is brokered through Teleport's policy engine. Every tool call is logged.
# /etc/teleport.yaml -- MCP-capable agent configuration
version: v3
teleport:
nodename: mcp-agent-01
data_dir: /var/lib/teleport
proxy_server: "teleport.yourdomain.com:443"
auth_token: "mcp-join-token"
auth_service:
enabled: false
proxy_service:
enabled: false
ssh_service:
enabled: false
# Register MCP servers as applications
app_service:
enabled: true
apps:
- name: "coderoasis-mcp"
description: "CoderOasis Internal MCP Server"
uri: "http://localhost:8811" # Your MCP server address
protocol: "mcp" # Tells Teleport this is MCP traffic
labels:
env: production
type: mcp
team: engineering
RBAC for MCP access:
kind: role
version: v7
metadata:
name: ai-agent-reader
description: "Read-only MCP access for AI coding assistants"
spec:
allow:
app_labels:
type: ["mcp"]
env: ["dev", "staging"]
# Restrict which MCP tools the AI can call
# This is policy-as-code for your AI agents
options:
max_session_ttl: "1h" # Short TTL for AI agent sessions
require_session_mfa: false # AI agents can't tap hardware keys
Connect an MCP client through Teleport:
# Get the MCP server connection config for your AI client
tsh apps login coderoasis-mcp
# For Claude Desktop, Cursor, or other MCP clients:
# Configure the MCP server URL as the Teleport-proxied address
# The client gets a short-lived certificate for each session
# All tool calls are audited in the Teleport audit log
This means every time your AI coding assistant calls a tool – queries a database, reads a file, executes code – that call is in Teleport's audit log with the agent's identity. Not an anonymous API key. A specific identity, with role-based permissions, that you can revoke or restrict at any time.
GitHub / Git Repository Access
Teleport's Git integration proxies access to GitHub and other Git providers through identity-aware SSH. Every git push, git clone, or git pull is logged with the authenticated user's Teleport identity.
# Register GitHub as an application through Teleport
app_service:
apps:
- name: "github"
description: "GitHub access via Teleport"
uri: "https://github.com"
protocol: "tcp"
public_addr: "github.teleport.yourdomain.com"
labels:
type: git
More practically, Teleport issues SSH certificates for Git operations. Configure your Git client:
# Configure Git to use Teleport's SSH proxy
# After tsh login, tsh provides a local SSH agent with short-lived GitHub certs
tsh git clone https://github.com/yourorg/yourrepo.git
# Or configure SSH for GitHub to route through Teleport
# ~/.ssh/config:
# Host github.com
# IdentityAgent /Users/you/.tsh/sockets/agent.12345
Machine Identity: CI/CD Pipeline Access
Production CI/CD pipelines need to deploy code, run migrations, push container images, and access production resources. The traditional approach is to create service account credentials and store them as CI/CD secrets. Those secrets are long-lived, have broad permissions, and are stored in systems you don't fully control.
Teleport Machine ID (tbot) gives your CI/CD pipeline a cryptographic identity and issues short-lived certificates for each job run.
Setting Up tbot for GitHub Actions
On a server that your GitHub Actions runner can reach, or in the runner itself:
# Install tbot (part of the Teleport binary)
# In your CI/CD environment -- GitHub Actions example
# Create a bot user in Teleport
tctl bots add github-actions \
--roles=ci-deployer \
--token-ttl=1h
# Output includes a join token and bot name
Define the CI deployer role:
kind: role
version: v7
metadata:
name: ci-deployer
spec:
allow:
# SSH access to production -- for deployment scripts
node_labels:
env: production
purpose: deploy
logins:
- deploy
# Database access -- for running migrations
db_labels:
env: production
db_names:
- "appdb"
db_users:
- "migrator"
# Kubernetes -- for rolling deployments
kubernetes_groups:
- ci-deployer
kubernetes_labels:
env: production
kubernetes_resources:
- kind: deployment
namespace: "application"
name: "*"
options:
max_session_ttl: "1h" # Short TTL appropriate for CI jobs
disconnect_expired_cert: true
GitHub Actions workflow:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC token
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Teleport
uses: teleport-actions/setup@v1
with:
version: '17'
- name: Authenticate to Teleport
uses: teleport-actions/auth@v2
with:
proxy: teleport.yourdomain.com:443
token: ${{ secrets.TELEPORT_BOT_TOKEN }}
# Teleport issues a short-lived certificate for this job run
- name: Run Database Migrations
run: |
tsh db login --db-user=migrator --db-name=appdb appdb-production
tsh db connect appdb-production -- psql -f migrations/latest.sql
- name: Deploy to Kubernetes
run: |
tsh kube login production-k8s
kubectl set image deployment/backend \
backend=registry.yourdomain.com/backend:${{ github.sha }} \
-n application
- name: Verify Deployment
run: |
kubectl rollout status deployment/backend -n application
The CI pipeline has no stored database passwords or Kubernetes credentials. Each job gets a fresh certificate that expires when the job completes. Every action is logged in Teleport's audit trail under the bot identity.
Just-in-Time Access Requests
Not everyone needs production database access all the time. Teleport's access request system lets users request elevated access that requires approval, with automatic expiry.
Configuring Access Requests
Update the engineer role to allow requesting admin-level access:
# Add to the engineer role's spec.allow section:
request:
roles:
- coderoasis-admin
- db-admin
# Maximum duration for the elevated access
max_duration: "4h"
# Require reason for the request
annotations:
reason: "required"
Add an approver role:
kind: role
version: v7
metadata:
name: access-approver
spec:
allow:
review_requests:
roles:
- coderoasis-admin
- db-admin
# Can see what the requester would be able to do with the elevated role
preview_as_roles:
- coderoasis-admin
Requesting and approving access:
# Developer requests temporary admin access
tsh request create \
--roles=db-admin \
--reason="Need to investigate production query performance -- Incident #1234" \
--max-duration=2h
# Request created: request-id-abc123
# Waiting for approval...
# On Slack/Teams: Teleport sends notification to approvers
# Approvers can also use the web UI
# Approver reviews and approves
tctl request approve request-id-abc123 \
--reason="Approved for incident investigation"
# Developer's CLI gets the approval notification
# Certificate is reissued with the additional role
# 2 hours later: certificate expires, elevated access ends automatically
Integrate with Slack for approval notifications:
# Teleport access request plugin for Slack
# Deploy as a separate process or Docker container
# /etc/teleport-slack-plugin/teleport-slack.toml
[teleport]
addr = "teleport.yourdomain.com:443"
identity = "/var/lib/teleport-slack/identity"
[slack]
token = "xoxb-slack-bot-token"
[role_to_recipients]
# Access requests for admin role go to the #ops channel
"coderoasis-admin" = ["#ops", "[email protected]"]
"db-admin" = ["#ops"]
Configuring SSO Integration
Teleport local users work fine for small teams. For larger teams with an existing identity provider, SSO integration is the right move. Teleport supports GitHub, Okta, Azure AD, Google Workspace, and any SAML 2.0 provider.
GitHub SSO (What We Use at CoderOasis)
# GitHub OAuth connector
# Create in /etc/teleport/connectors/github.yaml
kind: github
version: v3
metadata:
name: github
spec:
# Create a GitHub OAuth App at github.com/settings/developers
client_id: "your-github-oauth-app-client-id"
client_secret: "your-github-oauth-app-client-secret"
redirect_url: "https://teleport.yourdomain.com/v1/webapi/github/callback"
# Map GitHub teams to Teleport roles
teams_to_roles:
# GitHub org/team -> Teleport role mapping
- organization: "coderoasis"
team: "admins"
roles: ["coderoasis-admin", "access-approver"]
- organization: "coderoasis"
team: "engineers"
roles: ["engineer"]
- organization: "coderoasis"
team: "contributors"
roles: ["contributor"]
# Apply the connector
tctl create -f /etc/teleport/connectors/github.yaml
# Test it -- navigate to the web UI and click "Sign in with GitHub"
# GitHub OAuth flow completes, Teleport maps teams to roles, certificate issued
With SSO configured, offboarding is immediate: remove someone from the GitHub organization, their next login fails, their current certificates expire within hours. No SSH keys to revoke, no database passwords to rotate, no VPN accounts to disable.
Audit Log and Session Recording
Everything Teleport logs goes to the audit log. Configuring where and how that log is stored is a production concern.
Default: Local Files
The default stores audit events as NDJSON files in /var/lib/teleport/log/. Works for getting started. Not suitable for long-term storage on a single node.
Production: Structured Logging to External Systems
auth_service:
audit_events_uri:
- "file:///var/lib/teleport/log" # Local backup
- "dynamodb://teleport-audit" # DynamoDB for HA/AWS
# OR
# - "firestore://teleport-audit" # GCP Firestore
# Session recordings (the actual terminal content)
# For production: store in S3/MinIO, not local disk
audit_sessions_uri: "s3://your-s3-bucket/teleport-sessions?region=eu-west-1"
# OR for self-hosted S3-compatible (MinIO):
# audit_sessions_uri: "s3://teleport-sessions?endpoint=https://minio.internal:9000®ion=us-east-1"
Querying the Audit Log
# Search audit events
tctl events search --query='event="session.start"' --from=2026-05-01 --to=2026-05-02
# Find all database sessions by a specific user
tctl events search \
--query='event="db.session.query" AND user="alice"' \
--from=2026-05-01
# Find all failed access attempts
tctl events search --query='event="auth" AND success=false'
# Export for SIEM ingestion (outputs JSON)
tctl events search --query='event!=""' --format=json | \
curl -X POST https://your-siem.internal/api/events \
-H "Content-Type: application/json" \
-d @-
Session Replay
# List recorded sessions
tsh recordings ls
# Replay a specific session in the terminal
tsh play <session-id>
# Export session recording as video (useful for incident reports)
tsh play --format=pty <session-id> > session.cast
# Convert with asciinema for sharing
# Web UI: Sessions -> select any session -> Play
# Shows exact terminal output, keystroke by keystroke
Teleport Connect: The Desktop Application
For teams less comfortable with the CLI, Teleport Connect is a desktop application (macOS, Windows, Linux) that provides a GUI for everything tsh does:
- Browse your servers, databases, Kubernetes clusters, and applications
- One-click SSH sessions that open in integrated tabs
- Database connections that open in your preferred GUI tool
- Kubernetes context switching
Install:
# macOS
brew install --cask teleport-connect
# Linux -- download from goteleport.com/download
# Direct link to AppImage, .deb, or .rpm
# Windows -- MSI installer from goteleport.com/download
For non-engineers on your team who need occasional access to internal tools, Teleport Connect is a dramatically lower barrier than the CLI.
Monitoring and Alerting
Health Checks
# Check cluster status
tctl status
# Output:
# Cluster teleport.yourdomain.com
# Version 17.5.1
# CA pin sha256:abc123...
# Auth Connected
# Proxy Running
# Health endpoint for load balancers / uptime monitoring
curl https://teleport.yourdomain.com/healthz
# Returns 200 OK when healthy
# Prometheus metrics endpoint (configure in teleport.yaml)
# diag_addr: "127.0.0.1:3000"
# Then scrape: http://127.0.0.1:3000/metrics
Prometheus + Grafana Integration
# Add to /etc/teleport.yaml under the teleport: section
teleport:
diag_addr: "127.0.0.1:3000" # Expose metrics locally
Key metrics to alert on:
teleport_audit_failed_disk_monitoring_total-- audit log disk issuesteleport_connected_resources-- number of agents connectedteleport_auth_attempts-- authentication attempts (alert on spikes)process_resident_memory_bytes-- memory usage of the Teleport processteleport_proxy_peer_client_dial_error_total-- proxy connectivity errors
Log-Based Alerting
The JSON audit log makes it trivial to set up alerts via your log aggregation tool:
# Grafana Loki alert for failed root SSH attempts
{app="teleport"} | json | event="session.start" | login="root" | success="false"
# Alert on any user accessing production databases outside business hours
{app="teleport"} |
json |
event="db.session.start" |
db_labels_env="production" |
line_format "{{.user}} {{.db_name}} {{.time}}"
Troubleshooting Common Issues
Node Won't Join the Cluster
# Check the node can reach the proxy
curl -k https://teleport.yourdomain.com/healthz
nc -zv teleport.yourdomain.com 443
# Verify the join token is valid and not expired
tctl tokens ls # Run on auth host
# Check the CA pin matches
tctl status # Run on auth host -- shows current CA pin
# Check node logs for specific error
journalctl -u teleport -n 100 --no-pager | grep -i error
Certificate Issues
# Check certificate expiry
tsh status # Shows current certificate and expiry
# Force re-login
tsh logout && tsh login --proxy=teleport.yourdomain.com --user=admin
# Check ACME certificate renewal
journalctl -u teleport | grep -i acme
# Manually trigger ACME renewal (if cert is near expiry)
systemctl restart teleport
Database Access Fails
# Verify the database agent is running
tctl db ls # Should show the database
tsh db ls # User-visible list
# Check database agent logs
journalctl -u teleport -n 50 | grep "db_service"
# Verify PostgreSQL trusts Teleport CA
# In pg_hba.conf: confirm hostssl entries exist
# In postgresql.conf: confirm ssl_ca_file points to teleport-ca.crt
# Check Teleport can reach the database
tctl db test-connection coderoasis-production --db-user=readonly
Kubernetes Access Fails
# Verify Kubernetes agent is connected
tctl kube ls
# Check if your role grants the right Kubernetes groups
tsh status # Shows current role assignments and kubernetes_groups
# Verify Kubernetes RBAC resources exist
kubectl get clusterrolebinding developers -o yaml
# Re-authenticate to Kubernetes
tsh kube login production-k8s --all # Forces fresh cert
# Check what kubectl context is active
kubectl config current-context
Upgrading Teleport
Teleport releases major versions roughly every three months. The upgrade process is rolling -- agents can be one major version behind the Proxy/Auth.
# Check current versions
teleport version
tctl version
# Upgrade Auth/Proxy first (always upgrade Auth before Proxy, Proxy before agents)
# Using apt (Debian/Ubuntu):
apt update && apt install teleport
# Restart the service
systemctl restart teleport
# Verify new version
teleport version
# Upgrade agents (can lag one major version behind cluster)
# SSH to each node and run the same apt upgrade + restart
For automated upgrades, Teleport provides an Automatic Updater:
# Enable automatic patch-version upgrades (stays within major version)
teleport-update enable --proxy=teleport.yourdomain.com
The CoderOasis Setup in Production
Here's exactly what our deployment looks like:
Infrastructure:
- One Hetzner CX32 VPS (4 vCPU, 8GB RAM, 80GB SSD) running Auth+Proxy
- SSH agents on every production server (3 servers)
- Database agent proxying PostgreSQL and MySQL
- App agents for Ghost admin, Grafana, and Traefik dashboard
- Kubernetes agent on our Docker Swarm equivalent (we run Portainer + Docker, not Kubernetes -- but the principle is identical)
Authentication: - GitHub SSO with team-to-role mapping
- Hardware MFA enforced for all users (YubiKey 5)
- Session MFA required for database access
Roles: coderoasis-admin-- Traven and two trusted team leadsengineer-- Active contributorscontributor-- External writers who need Ghost admin access onlyci-deployer-- GitHub Actions bot identity
What replaced:- SSH key bastion host -- gone
- Shared database passwords in LastPass -- gone
- VPN for internal access -- gone
- Manual access revocation on offboarding -- gone (GitHub team removal handles it)
Audit: - All events logged to local files + shipped to Grafana Loki
- Session recordings stored on the Teleport node (we don't do enough sessions to justify S3)
- Alerts configured for any production access between midnight and 6 AM
The total cost: approximately €12/month for the Hetzner VPS. Compared to Teleport Cloud at roughly $10/user/month for a small team, self-hosting pays for itself at 2+ users and gives us full data sovereignty for the audit logs and session recordings.
Community Edition vs. Enterprise
Teleport Community Edition (AGPL-3.0) includes:
- SSH, database, Kubernetes, and application access
- RBAC with role-based access control
- Session recording and audit logging
- GitHub, GitLab, OIDC, and SAML SSO
- Machine identity (tbot)
- Access requests with approvals
- Web UI and tsh CLI
- MCP server access (added in 2025)
Teleport Enterprise adds: - FedRAMP/FIPS 140-2 compliance mode
- Hardware Security Module (HSM) support
- Advanced policy controls (ABAC, geographic restrictions)
- SOC2/ISO27001 compliance features
- Dedicated support SLA
- Teleport Identity Security (access graph visualization)
- Active Directory / Windows Desktop access with full session recording
For 95% of self-hosted use cases, Community Edition is sufficient. That's what CoderOasis runs. If you're in a regulated industry with specific compliance certifications (FedRAMP, HIPAA, PCI-DSS), Enterprise is worth evaluating.
Frequently Asked Questions
How many users can a single-node Teleport cluster handle?
Comfortably: hundreds. The Teleport Auth Service handles auth events efficiently, and the Proxy Service is designed for high concurrency. A single node with 4 vCPU and 8GB RAM can handle thousands of connected agents and hundreds of concurrent user sessions without difficulty. CoderOasis runs a 4 vCPU / 8GB node with zero performance concerns.
What happens when the Teleport node is down?
Users lose access through Teleport. Existing SSH sessions remain active (they don't drop when Auth goes down). New sessions can't be established. This is why we maintain emergency SSH access via cloud provider serial console and a small break-glass process documented in our runbook. For teams that can't tolerate Teleport downtime, HA deployment is the answer.
Can Teleport replace our VPN entirely?
For accessing internal resources: yes, completely. For network-level access patterns (one service reaching another service on the same network), Teleport isn't the right tool -- you want service mesh, firewall rules, or VPC peering for that. Teleport replaces VPNs for human access to infrastructure, not for service-to-service traffic.
Does Teleport work with Windows servers?
Yes, with caveats. SSH access to Windows is limited. Teleport's Windows Desktop Access uses RDP proxied through Teleport with full session recording. It requires Active Directory integration and a Teleport Windows Desktop Service. Community Edition does not include Windows Desktop Access -- that requires Enterprise. For Linux-first teams, this isn't a concern.
How do we handle emergency access if Teleport is unavailable?
Document a break-glass procedure:
- All team leads have a physical YubiKey with an emergency SSH key
- That SSH key is stored in an encrypted vault (we use Vaultwarden -- see the self-hosted stack guide)
- Each production server has a single emergency
authorized_keysentry for that key - Using the emergency key triggers an alert (via SSH command logging to a separate syslog server)
- Any use of the emergency key requires a post-incident report
The emergency key has never been used. That's the point -- Teleport's availability is high enough that we haven't needed it. But it exists, and everyone knows where it is.
This is the access model we trust for CoderOasis, and it's the model I'd recommend to any team that takes security seriously without wanting to make life miserable for the people actually doing the work. Zero trust doesn't have to mean zero usability. Teleport proves that -- our team reaches servers, databases, and internal tools faster through Teleport than they did through the old VPN and SSH key system, because everything is in one place, everything is discoverable, and nothing requires a manual onboarding ticket to set up.
For sysadmin and security topics, check out the SysAdmin topic section and the Cybersecurity coverage on CoderOasis. The CVE-2026-31431 article on the kernel privilege escalation we covered is directly relevant context for why removing SSH key management from the equation matters – you can't have your Linux keys stolen if they don't exist.