HashiCorp Boundary da acceso a servidores internos por sesiones autenticadas y auditables, sin repartir acceso a toda la red como haría una VPN. El usuario inicia sesión en el controller, pide conectarse a un target concreto y un worker abre un túnel TCP solo hacia ese host y puerto. En este tutorial instalarás un controller y un worker de Boundary en el mismo servidor Ubuntu 24.04, con PostgreSQL como base de datos, TLS de Let's Encrypt y un target SSH hacia un servidor de tu red privada.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS y al menos 2 GB de RAM, por ejemplo un VPS de CubePath, con una IP pública y acceso a la red privada donde están los servidores que quieres publicar.
  • Un usuario no root con privilegios sudo.
  • Un dominio con un registro A que apunte a la IP pública del servidor. En la guía se usa boundary.your_domain.
  • Un servidor interno accesible por SSH desde el servidor de Boundary. En la guía su IP privada es 10.0.0.20.
  • La CLI de Boundary en tu equipo para administrar y conectarte (se explica en el paso 6).

Puertos que usa esta instalación:

PuertoUsoExposición
9200/TCPAPI y panel web del controllerPúblico
9201/TCPClúster (worker hacia controller)Solo local en esta guía
9202/TCPProxy del worker (túneles de sesión)Público
9203/TCPEndpoint de salud (ops)Solo local
80/TCPEmisión y renovación del certificadoPúblico

Paso 1: Instalar Boundary desde el repositorio de HashiCorp

Descarga la clave GPG de HashiCorp en /etc/apt/keyrings:

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/hashicorp-archive-keyring.gpg

Añade el repositorio para tu versión de Ubuntu:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(. /etc/os-release && echo "$VERSION_CODENAME") main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

Instala Boundary y comprueba la versión:

sudo apt update
sudo apt install boundary
boundary version
Version information:
  Build Date:          2026-...
  Git Revision:        ...
  Version Number:      0.x.x

Asegúrate de que existe un usuario de sistema boundary para ejecutar los servicios (el paquete puede haberlo creado ya) y de que existe el directorio de configuración:

getent passwd boundary >/dev/null || sudo useradd --system --home /etc/boundary.d --shell /usr/sbin/nologin boundary
sudo install -d -o boundary -g boundary -m 0750 /etc/boundary.d /etc/boundary.d/tls

Paso 2: Preparar PostgreSQL

El controller guarda en PostgreSQL toda su configuración (scopes, usuarios, targets) y el estado de las sesiones. Instala PostgreSQL 16, el de Ubuntu 24.04:

sudo apt install postgresql

Crea un rol y una base de datos propiedad de ese rol. Sustituye your_db_password por una contraseña larga y aleatoria:

sudo -u postgres psql -c "CREATE ROLE boundary WITH LOGIN PASSWORD 'your_db_password';"
sudo -u postgres psql -c "CREATE DATABASE boundary OWNER boundary;"

Comprueba que el rol puede conectarse:

psql "postgresql://boundary:[email protected]:5432/boundary" -c 'SELECT current_user;'
 current_user
--------------
 boundary
(1 row)

Paso 3: Obtener el certificado TLS

El controller sirve su API y el panel web por HTTPS. Instala Certbot y abre los puertos necesarios en UFW:

sudo apt install certbot
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 9200/tcp
sudo ufw allow 9202/tcp
sudo ufw enable

Boundary se ejecuta como el usuario boundary, que no puede leer /etc/letsencrypt. Crea un hook que copie el certificado a /etc/boundary.d/tls cada vez que se emita o renueve y reinicie el controller si está en marcha:

sudo nano /etc/letsencrypt/renewal-hooks/deploy/boundary.sh
#!/usr/bin/env bash
set -euo pipefail

install -o boundary -g boundary -m 0644 "${RENEWED_LINEAGE}/fullchain.pem" /etc/boundary.d/tls/cert.pem
install -o boundary -g boundary -m 0600 "${RENEWED_LINEAGE}/privkey.pem" /etc/boundary.d/tls/key.pem

if systemctl is-active --quiet boundary-controller.service; then
  systemctl restart boundary-controller.service
fi

Hazlo ejecutable y solicita el certificado. Certbot ejecuta los scripts de renewal-hooks/deploy en cada renovación, pero no en la primera emisión, así que lanzarás el hook a mano una vez:

sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/boundary.sh
sudo certbot certonly --standalone -d boundary.your_domain --agree-tos -m admin@your_domain --no-eff-email
sudo RENEWED_LINEAGE=/etc/letsencrypt/live/boundary.your_domain /etc/letsencrypt/renewal-hooks/deploy/boundary.sh
sudo ls -l /etc/boundary.d/tls
-rw-r--r-- 1 boundary boundary 2868 Sep 25 11:02 cert.pem
-rw------- 1 boundary boundary  241 Sep 25 11:02 key.pem

Paso 4: Configurar el controller

Boundary cifra los datos sensibles de la base de datos con claves KMS. En producción conviene un KMS externo (Vault Transit, AWS KMS, etc.); en esta guía se usan claves AEAD estáticas guardadas en el archivo de configuración. Genera tres claves aleatorias de 32 bytes, una para cada propósito (root, worker-auth y recovery):

for purpose in root worker-auth recovery; do echo "$purpose: $(openssl rand -base64 32)"; done

Crea el archivo del controller:

sudo nano /etc/boundary.d/controller.hcl

Pega esta configuración, sustituyendo la contraseña de la base de datos, el dominio y las tres claves:

disable_mlock = true

controller {
  name        = "controller-1"
  description = "Boundary controller"

  database {
    url = "postgresql://boundary:[email protected]:5432/boundary?sslmode=disable"
  }
}

listener "tcp" {
  address       = "0.0.0.0:9200"
  purpose       = "api"
  tls_cert_file = "/etc/boundary.d/tls/cert.pem"
  tls_key_file  = "/etc/boundary.d/tls/key.pem"
}

listener "tcp" {
  address = "127.0.0.1:9201"
  purpose = "cluster"
}

listener "tcp" {
  address     = "127.0.0.1:9203"
  purpose     = "ops"
  tls_disable = true
}

kms "aead" {
  purpose   = "root"
  aead_type = "aes-gcm"
  key       = "your_root_key"
  key_id    = "global_root"
}

kms "aead" {
  purpose   = "worker-auth"
  aead_type = "aes-gcm"
  key       = "your_worker_auth_key"
  key_id    = "global_worker-auth"
}

kms "aead" {
  purpose   = "recovery"
  aead_type = "aes-gcm"
  key       = "your_recovery_key"
  key_id    = "global_recovery"
}

El listener cluster escucha solo en 127.0.0.1 porque el worker está en la misma máquina. Si más adelante añades workers en otras redes, cámbialo a una IP accesible por ellos y abre el puerto 9201 solo para sus direcciones.

Este archivo contiene la contraseña de la base de datos y las claves, así que restringe sus permisos:

sudo chown boundary:boundary /etc/boundary.d/controller.hcl
sudo chmod 0640 /etc/boundary.d/controller.hcl

Inicializa la base de datos. Este comando se ejecuta una sola vez, crea el esquema y genera un método de autenticación por contraseña con un usuario admin, una organización, un proyecto y un target de ejemplo:

sudo -u boundary boundary database init -config /etc/boundary.d/controller.hcl
Initial auth information:
  Auth Method ID:     ampw_1234567890
  Auth Method Name:   Generated global scope initial password auth method
  Login Name:         admin
  Password:           AbCdEf1234567890
  Scope ID:           global
  User ID:            u_1234567890
  User Name:          admin
...
Initial org scope information:
  Scope ID:           o_1234567890
...
Initial project scope information:
  Scope ID:           p_1234567890

Guarda en un gestor de contraseñas el Auth Method ID, la contraseña de admin y los identificadores de la organización y el proyecto: no se vuelven a mostrar.

Crea la unidad de systemd del controller:

sudo nano /etc/systemd/system/boundary-controller.service
[Unit]
Description=HashiCorp Boundary controller
Wants=network-online.target
After=network-online.target postgresql.service

[Service]
User=boundary
Group=boundary
ExecStart=/usr/bin/boundary server -config=/etc/boundary.d/controller.hcl
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Arranca el controller y comprueba el endpoint de salud:

sudo systemctl daemon-reload
sudo systemctl enable --now boundary-controller
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9203/health
200

Paso 5: Configurar el worker

El worker es el proceso por el que pasa el tráfico de las sesiones. Se autentica ante el controller con la misma clave worker-auth, y su public_addr es la dirección a la que se conectarán los clientes. Crea su configuración:

sudo nano /etc/boundary.d/worker.hcl
disable_mlock = true

listener "tcp" {
  address = "0.0.0.0:9202"
  purpose = "proxy"
}

worker {
  name              = "worker-1"
  description       = "Worker con acceso a la red privada"
  public_addr       = "boundary.your_domain"
  initial_upstreams = ["127.0.0.1:9201"]
}

kms "aead" {
  purpose   = "worker-auth"
  aead_type = "aes-gcm"
  key       = "your_worker_auth_key"
  key_id    = "global_worker-auth"
}

Restringe sus permisos y crea su unidad de systemd:

sudo chown boundary:boundary /etc/boundary.d/worker.hcl
sudo chmod 0640 /etc/boundary.d/worker.hcl
sudo nano /etc/systemd/system/boundary-worker.service
[Unit]
Description=HashiCorp Boundary worker
Wants=network-online.target
After=network-online.target boundary-controller.service

[Service]
User=boundary
Group=boundary
ExecStart=/usr/bin/boundary server -config=/etc/boundary.d/worker.hcl
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Arranca el worker y comprueba en el log que se ha conectado al controller:

sudo systemctl daemon-reload
sudo systemctl enable --now boundary-worker
sudo journalctl -u boundary-worker -n 30 --no-pager

Si ves errores de autenticación, la clave worker-auth no coincide exactamente con la del controller.

Paso 6: Instalar la CLI en tu equipo e iniciar sesión

La administración y las conexiones se hacen desde tu equipo. En Ubuntu o Debian, añade el mismo repositorio del paso 1 e instala boundary; en macOS usa brew install hashicorp/tap/boundary. Para que las sesiones SSH funcionen, el equipo también necesita el cliente ssh.

Apunta la CLI a tu controller e inicia sesión con el Auth Method ID del paso 4:

export BOUNDARY_ADDR=https://boundary.your_domain:9200
boundary authenticate password -auth-method-id=ampw_1234567890 -login-name=admin

La CLI pide la contraseña y guarda el token en el llavero del sistema:

Authentication information:
  Account ID:      acctpw_1234567890
  Auth Method ID:  ampw_1234567890
  User ID:         u_1234567890

The token was successfully stored in the chosen keyring and is not displayed here.

Comprueba que el worker está registrado:

boundary workers list

También puedes abrir https://boundary.your_domain:9200 en el navegador: el panel web muestra las mismas organizaciones, proyectos y sesiones.

Paso 7: Crear un target SSH

Boundary organiza los recursos en scopes: global, organizaciones y proyectos. Los hosts y targets viven en un proyecto; usa el que generó database init (p_1234567890 en el ejemplo).

Crea un catálogo de hosts estático, añade el servidor interno y agrúpalo en un host set:

boundary host-catalogs create static -scope-id=p_1234567890 -name="servidores-internos"
boundary hosts create static -host-catalog-id=hcst_1234567890 -name="web-01" -address="10.0.0.20"
boundary host-sets create static -host-catalog-id=hcst_1234567890 -name="web"
boundary host-sets add-hosts -id=hsst_1234567890 -host=hst_1234567890

Cada comando devuelve el identificador del recurso creado (hcst_..., hst_..., hsst_...), que usas en el siguiente. Crea ahora un target TCP en el puerto 22 y asígnale el host set:

boundary targets create tcp -scope-id=p_1234567890 -name="ssh-web-01" -default-port=22
boundary targets add-host-sources -id=ttcp_1234567890 -host-source=hsst_1234567890

Paso 8: Conectarse y gestionar sesiones

boundary connect ssh pide una sesión al controller, abre un túnel local a través del worker y lanza tu cliente ssh contra él:

boundary connect ssh -target-id=ttcp_1234567890 -username=your_user

La autenticación SSH en el servidor de destino sigue siendo la tuya (clave o contraseña); Boundary controla quién puede llegar hasta él y registra la sesión. Mientras estás conectado, desde otra terminal puedes ver la sesión activa:

boundary sessions list -scope-id=p_1234567890
Session information:
  ID:                    s_1234567890
    Status:              active
    Target ID:           ttcp_1234567890
    User ID:             u_1234567890

Un administrador puede cortar una sesión en cualquier momento:

boundary sessions cancel -id=s_1234567890

Una vez que el acceso por Boundary funciona, el servidor interno ya no necesita tener el puerto 22 abierto hacia Internet: basta con que sea accesible desde el worker por la red privada.

Solución de problemas

El controller no arranca. Revisa sudo journalctl -u boundary-controller -n 50. Los errores más comunes son una URL de base de datos incorrecta, permisos sobre los archivos de /etc/boundary.d/tls o una clave KMS que no son 32 bytes en base64.

boundary connect se queda esperando o falla con timeout. El cliente debe llegar a boundary.your_domain:9202. Comprueba el firewall con sudo ufw status y que el public_addr del worker resuelve a la IP pública.

La sesión se abre pero SSH no conecta con el host. El worker no alcanza el destino. Desde el servidor de Boundary, prueba nc -zv 10.0.0.20 22.

Conclusión

Has instalado un controller y un worker de Boundary con PostgreSQL y TLS, y has publicado un servidor interno como target SSH al que se accede con sesiones autenticadas, visibles y revocables. Como siguientes pasos puedes crear usuarios y roles limitados por proyecto en lugar de usar admin, conectar un proveedor OIDC para el inicio de sesión y desplegar workers adicionales dentro de otras redes privadas para alcanzar sus hosts sin exponerlos.