Skip to main content
COSMICBYTEZLABS
NewsSecurityHOWTOsToolsTraining
StudyProjectsNewsletterHire MeAbout
Subscribe

Press Enter to search or Esc to close

News
Security
HOWTOs
Tools
Training
Study
Projects
Newsletter
Hire Me
About
RSS Feed
Reading List
Subscribe

Stay in the Loop

Get the latest security alerts, tutorials, and tech insights delivered to your inbox.

Subscribe NowFree forever. No spam.
COSMICBYTEZLABS

Your trusted source for IT intelligence, cybersecurity insights, and hands-on technical guides.

1451+ Articles
151+ Guides

CONTENT

  • Latest News
  • Security Alerts
  • HOWTOs
  • Checklists
  • Projects
  • Exam Prep

RESOURCES

  • Search
  • Browse Tags
  • Newsletter Archive
  • Reading List
  • RSS Feed

COMPANY

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CosmicBytez Labs. All rights reserved.

System Status: Operational
  1. Home
  2. Projects
  3. Teleport PAM: Zero-Trust Privileged Access for Your Homelab
Teleport PAM: Zero-Trust Privileged Access for Your Homelab
PROJECTIntermediate

Teleport PAM: Zero-Trust Privileged Access for Your Homelab

Deploy Teleport's open-source privileged access management platform to replace static SSH keys with short-lived certificates, enforce MFA, record every...

Dylan H.

Projects

June 10, 2026
11 min read
3-5 hours

Tools & Technologies

Teleport v18Docker Composetctl (Teleport CLI)tsh (Teleport SSH client)OpenSSH

Overview

Every homelab eventually accumulates a pile of SSH keys scattered across ~/.ssh/ — one per server, a few shared across machines, some with passwords you half-remember. That works until it doesn't: a compromised laptop, a forgotten authorized key on a decommissioned box, or an intern who left six months ago and still has authorized_keys entries everywhere.

Teleport solves this with a fundamentally different model. Instead of distributing static keys, it acts as a certificate authority. Users authenticate once (with MFA), receive a short-lived certificate (default: 12 hours), and that certificate is what gets them onto machines — no keys to manage, rotate, or forget. When the cert expires, access ends automatically.

This project walks you through deploying Teleport Community Edition on your homelab:

  • Self-hosted Teleport cluster (Auth + Proxy + Web UI) via Docker Compose
  • SSH node enrollment — bring existing Linux boxes under Teleport control
  • MFA enforcement with TOTP
  • Role-based access control: admin vs. read-only roles
  • Session recording and audit log review
  • Using tsh to SSH through the cluster

By the end you'll have a centralised access plane for every Linux server in your lab, with full session recordings you can replay, and a clean audit log of every login.


Architecture

Teleport has three logical components, all of which can run on a single host in a homelab:

┌─────────────────────────────────────────┐
│           Teleport Cluster Host          │
│                                          │
│  ┌──────────────┐  ┌──────────────────┐  │
│  │  Auth Service │  │  Proxy Service   │  │
│  │               │  │                  │  │
│  │  - CA (SSH,   │  │  - Web UI :3080  │  │
│  │    TLS, DB)   │  │  - SSH proxy     │  │
│  │  - Audit log  │  │    :3023         │  │
│  │  - User/Role  │  │  - Reverse tunnel│  │
│  │    store      │  │    :3024         │  │
│  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────┘
                    │
          Certificate-based auth
                    │
    ┌───────────────┼───────────────┐
    │               │               │
┌───────┐      ┌───────┐      ┌───────┐
│ Node1 │      │ Node2 │      │ Node3 │
│(SSH   │      │(SSH   │      │(SSH   │
│agent) │      │agent) │      │agent) │
└───────┘      └───────┘      └───────┘

Auth Service — the brains. Maintains the certificate authority, stores users, roles, and audit events. Only the Proxy talks to Auth directly; nodes and clients never reach it.

Proxy Service — the front door. Handles the Web UI, client connections (port 3023), and reverse tunnels from nodes (port 3024). This is the only port you expose externally.

Node agents — Teleport binary running on each SSH host. Registers with the cluster via reverse tunnel, so nodes don't need inbound firewall rules.

tsh — the client CLI. Replaces ssh for cluster-managed connections.


Prerequisites

  • A Linux host to run the Teleport cluster (Ubuntu 22.04+ or Debian 12+), 2 GB RAM minimum
  • Docker and Docker Compose installed on the cluster host
  • One or more additional Linux VMs/servers to enroll as nodes
  • A hostname or IP for your Teleport cluster (a local hostname works fine for homelab)
  • Ports 3023, 3024, 3025, and 3080 available on the cluster host

Step 1 — Deploy the Teleport Cluster with Docker Compose

Create a working directory and the Compose file:

mkdir -p ~/teleport/{data,config}
cd ~/teleport

Create docker-compose.yml:

services:
  teleport:
    image: public.ecr.aws/gravitational/teleport-ent-distroless:18
    container_name: teleport
    restart: unless-stopped
    ports:
      - "3023:3023"   # SSH proxy
      - "3024:3024"   # Reverse tunnel
      - "3025:3025"   # Auth gRPC
      - "3080:3080"   # Web UI / HTTPS
    volumes:
      - ./data:/var/lib/teleport
      - ./config:/etc/teleport
    command: ["teleport", "start", "--config=/etc/teleport/teleport.yaml"]

Generate the initial configuration file — replace teleport.homelab.local with your cluster hostname or IP:

docker run --rm \
  -v $(pwd)/config:/etc/teleport \
  public.ecr.aws/gravitational/teleport-ent-distroless:18 \
  teleport configure \
    --cluster-name=homelab \
    --public-addr=teleport.homelab.local:3080 \
    --output=/etc/teleport/teleport.yaml

Open config/teleport.yaml and make these changes under the auth_service section to enable local users and session recording:

auth_service:
  enabled: true
  cluster_name: homelab
  # Store sessions locally (use S3 or GCS for production)
  session_recording: node
  authentication:
    type: local
    second_factor: otp        # require TOTP MFA
    webauthn:
      rp_id: teleport.homelab.local
 
proxy_service:
  enabled: true
  web_listen_addr: "0.0.0.0:3080"
  public_addr: "teleport.homelab.local:3080"
  ssh_public_addr: "teleport.homelab.local:3023"
  tunnel_public_addr: "teleport.homelab.local:3024"
  https_keypairs: []          # Let Teleport generate a self-signed cert for homelab
 
ssh_service:
  enabled: false              # Don't run SSH service on the cluster host itself

Start the cluster:

docker compose up -d
docker compose logs -f teleport

Wait for the line Teleport is ready in the logs (usually 15–30 seconds).


Step 2 — Create the First Admin User

Exec into the container to use tctl:

docker exec teleport tctl users add admin \
  --roles=editor,access \
  --logins=root,ubuntu,debian

This outputs a one-time registration URL. Open it in a browser, set a password, and enroll your TOTP authenticator (Google Authenticator, Bitwarden, etc.).

Note on --logins: This is the list of OS usernames the Teleport user is allowed to connect as. Add any login names present on your SSH nodes.

Verify the user was created:

docker exec teleport tctl users ls

Step 3 — Install tsh on Your Workstation

Download the Teleport client for your workstation OS from the Teleport downloads page or via package manager:

# macOS
brew install teleport
 
# Ubuntu/Debian
curl https://goteleport.com/static/install.sh | bash -s 18
 
# Or download the binary directly
curl -L https://get.gravitational.com/teleport-v18-linux-amd64-bin.tar.gz | \
  tar -xz -C /usr/local/bin teleport/tsh --strip-components=1

Log in to your cluster (accept the self-signed cert warning for homelab):

tsh login --proxy=teleport.homelab.local:3080 --insecure --user=admin

Enter your password, then your TOTP code. On success you'll see:

> Profile URL:  https://teleport.homelab.local:3080
  Logged in as: admin
  Cluster:      homelab
  Roles:        editor, access
  Logins:       root, ubuntu, debian
  Kubernetes:   disabled
  Valid until:  2026-06-11 08:00:00 +0000 UTC [valid for 12h0m0s]
  Extensions:   permit-agent-forwarding, permit-port-forwarding, permit-pty

You now hold a short-lived SSH certificate valid for 12 hours. When it expires, you authenticate again — no persistent keys anywhere on your workstation.


Step 4 — Enroll Your First SSH Node

On the target Linux host (the machine you want to access via Teleport), install the Teleport agent:

# Replace with your actual version
curl -L https://get.gravitational.com/teleport-v18-linux-amd64-bin.tar.gz | \
  tar -xz -C /usr/local/bin --strip-components=1
 
# Verify
teleport version

Back on the cluster host, generate a node join token (valid for 30 minutes):

docker exec teleport tctl tokens add \
  --type=node \
  --ttl=30m

Copy the token value from the output. On the target SSH host, create /etc/teleport.yaml:

teleport:
  nodename: node01
  data_dir: /var/lib/teleport
  auth_token: <YOUR_TOKEN_HERE>
  proxy_server: teleport.homelab.local:3080
  log:
    output: stderr
    severity: INFO
 
auth_service:
  enabled: false
 
proxy_service:
  enabled: false
 
ssh_service:
  enabled: true
  listen_addr: "0.0.0.0:3022"
  labels:
    env: homelab
    role: ssh-node
  # Forward commands for host inventory
  commands:
    - name: hostname
      command: [hostname]
      period: 1m0s
    - name: arch
      command: [uname, -p]
      period: 1h0m0s

Create a systemd unit and start the agent:

cat > /etc/systemd/system/teleport.service << 'EOF'
[Unit]
Description=Teleport SSH Service
After=network.target
 
[Service]
Type=simple
Restart=on-failure
ExecStart=/usr/local/bin/teleport start --config=/etc/teleport.yaml
LimitNOFILE=65536
 
[Install]
WantedBy=multi-user.target
EOF
 
systemctl daemon-reload
systemctl enable --now teleport
systemctl status teleport

Within a few seconds the node registers. Verify from the cluster:

docker exec teleport tctl nodes ls

You should see node01 appear with status Online.


Step 5 — SSH Through Teleport

From your workstation (where you ran tsh login), list available nodes:

tsh ls

Output:

Node Name  Address         Labels
---------  --------------  ---------------------------
node01     127.0.0.1:3022  arch=x86_64, env=homelab, hostname=node01

Connect to the node:

tsh ssh root@node01

That's it — no key files, no host key prompts, no password. Teleport issued a certificate, the proxy routed the connection, and the node verified your cert against the cluster CA.

You can also use standard ssh with a generated config entry:

tsh config >> ~/.ssh/config
ssh root@node01.homelab

Step 6 — Define Custom RBAC Roles

The built-in editor and access roles are broad. Create a tighter readonly role that can SSH but only as a non-root user, with no ability to forward ports:

Create role-readonly.yaml:

kind: role
version: v7
metadata:
  name: readonly
spec:
  allow:
    logins: ['readonly-user']
    node_labels:
      env: homelab
    rules:
      - resources: ['session']
        verbs: ['list', 'read']
  deny:
    logins: ['root']
  options:
    forward_agent: false
    port_forwarding: false
    max_session_ttl: 4h
    enhanced_recording:
      - command
      - network

Apply it:

docker exec -i teleport tctl create -f < role-readonly.yaml

Create a restricted user:

docker exec teleport tctl users add readonly-user \
  --roles=readonly \
  --logins=readonly-user

This user can reach homelab nodes but cannot escalate to root, forward ports, or modify cluster resources.


Step 7 — Add a Second Admin with Separate MFA

For a multi-person homelab or to test role isolation, add a second user:

docker exec teleport tctl users add labuser \
  --roles=access \
  --logins=ubuntu,labuser

Walk through the same TOTP enrollment. Now both users must authenticate independently — no shared credentials.


Step 8 — Review Session Recordings and Audit Log

Every tsh ssh session is recorded as an interactive terminal stream (with timing data for playback).

List recorded sessions:

docker exec teleport tctl recordings ls

Play back a session by ID in the terminal:

tsh play <session-id>

The Web UI at https://teleport.homelab.local:3080 also has a full session player — you can watch the terminal replay at real speed, seek through it, and see the exact commands typed.

View the raw audit log:

docker exec teleport tctl audit events --format=json | jq .

Key event types to watch for:

EventMeaning
user.loginSuccessful web/tsh login
session.startSSH session opened
session.endSSH session closed (with recording reference)
authAuthentication attempt (success or failure)
user.createNew user added
role.createdRole definition changed

Export events for a time range (useful for weekly review):

docker exec teleport tctl audit events \
  --from="2026-06-01" \
  --to="2026-06-10" \
  --format=json > audit-june.json

Testing

Run these checks to validate your deployment:

# 1. Verify cluster health
docker exec teleport tctl status
 
# 2. List all registered nodes
docker exec teleport tctl nodes ls
 
# 3. List all users and their roles
docker exec teleport tctl users ls
 
# 4. SSH to a node and confirm session recording starts
tsh ssh ubuntu@node01 -- echo "Teleport recording test"
 
# 5. Confirm the session appears in recordings
docker exec teleport tctl recordings ls | head -5
 
# 6. Test role enforcement — readonly-user cannot sudo to root
tsh ssh --user=readonly-user readonly-user@node01 -- sudo whoami
# Expected: sudo: Permission denied / role denies root login
 
# 7. Confirm expired cert is rejected (after manually expiring)
# tsh logout && tsh ssh root@node01  → should prompt for re-auth

Troubleshooting

Node won't join / stays Offline

Check the agent logs on the node: journalctl -u teleport -f. Common cause: the cluster hostname isn't resolving from the node. Add an /etc/hosts entry pointing teleport.homelab.local at your cluster host IP.

Browser shows untrusted certificate

Expected for a self-signed homelab cert. In Firefox: Advanced → Accept Risk. For a trusted cert, add your cluster to your local CA (or use a real domain with Let's Encrypt — Teleport supports ACME natively).

tsh login fails with "dial tcp: connection refused"

Verify ports 3023/3080 are exposed from the container: docker compose ps should show them mapped. Check your firewall: ufw allow 3023/tcp && ufw allow 3080/tcp.

tctl commands return permission errors

You must exec into the container as root: docker exec -u root teleport tctl <cmd>. The default container entrypoint drops privileges.


Deployment Notes

For a production-style homelab deployment:

Persistent data: The ./data volume in Docker Compose stores all cluster state (audit log, session recordings, CA keys). Back it up regularly — losing it means re-issuing all join tokens and certificates.

TLS with a real cert: If you have a domain, Teleport supports ACME out of the box:

proxy_service:
  acme:
    enabled: true
    email: admin@yourdomain.com

Firewall rules: Only port 3080 needs to be externally reachable if you're using reverse tunnels. Nodes connect outbound to 3024; they don't need inbound holes.

Upgrades: Pull the new image tag and docker compose up -d --pull always. Teleport maintains backward compatibility across minor versions. For major version upgrades (e.g., v17 → v18), read the upgrade guide — auth and proxy must be upgraded before nodes.


Extensions and Next Steps

Once the core SSH PAM is running, Teleport's Community Edition covers several more access planes:

Kubernetes Access — enroll your k3s or k8s cluster so kubectl goes through Teleport with the same cert-based auth and session recording:

teleport configure kube --cluster-name=homelab-k8s --proxy=teleport.homelab.local:3080

Database Access — proxy connections to PostgreSQL, MySQL, or MongoDB through Teleport, with the same audit trail and no static credentials stored in apps.

Application Access — put internal web apps (Grafana, Portainer, etc.) behind Teleport's app proxy for SSO instead of a VPN.

GitHub/OIDC SSO — replace local passwords with GitHub OAuth or any OIDC provider (Authentik, Keycloak). Users authenticate with their existing identity; Teleport maps groups to roles.

# Add to auth_service in teleport.yaml
authentication:
  type: github
  github:
    client_id: <YOUR_GITHUB_CLIENT_ID>
    client_secret: <YOUR_GITHUB_CLIENT_SECRET>
    redirect_url: https://teleport.homelab.local:3080/v1/webapi/github/callback
    display: GitHub
    teams_to_roles:
      - organization: your-org
        team: homelab-admins
        roles: [editor, access]

Ansible/Terraform integration — use tsh as the SSH binary in Ansible inventory for certificate-backed automation, no static keys needed in CI/CD.

Hardware MFA — swap TOTP for WebAuthn with a YubiKey or platform authenticator for phishing-resistant MFA on every SSH session.


Summary

You now have a fully functional zero-trust SSH access platform running in your homelab:

  • No more static keys: short-lived certificates replace ~/.ssh/authorized_keys entries across all enrolled nodes
  • MFA on every login: TOTP challenge before any certificate is issued
  • Full session recording: every terminal session is captured and replayable from the Web UI
  • Clean audit log: every login, session start/end, and admin action is logged with user, IP, and timestamp
  • Centralised RBAC: add or revoke access by editing a user record or role — no manual key cleanup across dozens of servers

The same Teleport cluster you built here scales to dozens of nodes, and the Community Edition is free and open-source. When your lab grows beyond SSH, the Kubernetes, database, and application access features slot in without rebuilding anything.

#privileged-access-management#zero-trust#SSH#MFA#Homelab#Security#certificates#audit

Related Articles

Building a Wazuh XDR + SIEM Homelab

Deploy a full Wazuh stack in Docker to gain host-based intrusion detection, file integrity monitoring, vulnerability scanning, and active response across your…

14 min read

Runtime Security Monitoring with Falco: Detect Container

Deploy Falco on a Docker host to monitor container syscalls at the kernel level, write custom homelab detection rules, and route real-time alerts through.

12 min read

Self-Hosted Password Manager with Vaultwarden

Deploy a fully self-hosted, Bitwarden-compatible password manager using Vaultwarden on Docker with Caddy reverse proxy, automatic TLS, WebSocket...

10 min read
Back to all Projects