Skip to main content

SSH Tunnels and Proxies

Comprehensive guide to SSH tunneling, port forwarding, and SOCKS proxy configuration for secure network access.

Port Forwarding​

Local Port Forwarding​

Forward local ports to remote destinations:

import { createSSHTunnel } from '@xec-sh/core';

// Basic local port forwarding
const tunnel = await createSSHTunnel({
ssh: {
host: 'jump.server.com',
username: 'user',
privateKey: '~/.ssh/id_rsa'
},
localPort: 3306,
remoteHost: 'database.internal',
remotePort: 3306
});

// Use the tunnel
const mysql = new MySQLClient({
host: 'localhost',
port: 3306
});

// Clean up when done
await tunnel.close();

Dynamic Port Forwarding (SOCKS Proxy)​

Create a SOCKS proxy for dynamic forwarding:

// Create SOCKS proxy
const socksProxy = await createSSHTunnel({
ssh: {
host: 'proxy.server.com',
username: 'user',
privateKey: '~/.ssh/id_rsa'
},
dynamic: true,
localPort: 1080
});

// Configure applications to use SOCKS proxy at localhost:1080
process.env.https_proxy = 'socks5://localhost:1080';
process.env.http_proxy = 'socks5://localhost:1080';

// Make requests through the proxy
const response = await fetch('https://internal.service.com/api');

await socksProxy.close();

Remote Port Forwarding​

Forward remote ports to local services:

// Make local service accessible from remote server
const remoteTunnel = await createSSHTunnel({
ssh: {
host: 'public.server.com',
username: 'user',
privateKey: '~/.ssh/id_rsa'
},
reverse: true,
remotePort: 8080,
localHost: 'localhost',
localPort: 3000
});

// Now public.server.com:8080 forwards to localhost:3000
console.log('Service accessible at public.server.com:8080');

await remoteTunnel.close();

Multiple Tunnels​

Tunnel Manager​

Manage multiple SSH tunnels:

class SSHTunnelManager {
constructor() {
this.tunnels = new Map();
}

async createTunnel(name, config) {
if (this.tunnels.has(name)) {
throw new Error(`Tunnel ${name} already exists`);
}

const tunnel = await createSSHTunnel(config);
this.tunnels.set(name, tunnel);

console.log(`Tunnel ${name} created:`, {
local: `localhost:${config.localPort}`,
remote: `${config.remoteHost}:${config.remotePort}`
});

return tunnel;
}

async closeTunnel(name) {
const tunnel = this.tunnels.get(name);
if (tunnel) {
await tunnel.close();
this.tunnels.delete(name);
console.log(`Tunnel ${name} closed`);
}
}

async closeAll() {
for (const [name, tunnel] of this.tunnels) {
await tunnel.close();
console.log(`Closed tunnel: ${name}`);
}
this.tunnels.clear();
}
}

// Usage
const manager = new SSHTunnelManager();

// Create multiple tunnels
await manager.createTunnel('database', {
ssh: sshConfig,
localPort: 5432,
remoteHost: 'db.internal',
remotePort: 5432
});

await manager.createTunnel('redis', {
ssh: sshConfig,
localPort: 6379,
remoteHost: 'redis.internal',
remotePort: 6379
});

// Clean up
await manager.closeAll();

Tunnel Through Commands​

Execute Commands with Tunneling​

Run commands that use tunneled connections:

// Create tunnel and execute command
async function withTunnel(tunnelConfig, callback) {
const tunnel = await createSSHTunnel(tunnelConfig);

try {
return await callback(tunnel);
} finally {
await tunnel.close();
}
}

// Use tunnel for database backup
await withTunnel({
ssh: sshConfig,
localPort: 5432,
remoteHost: 'database.internal',
remotePort: 5432
}, async (tunnel) => {
// Backup database through tunnel
await $`pg_dump -h localhost -p 5432 -U user dbname > backup.sql`;
});

Advanced Tunneling​

Multi-Hop Tunnels​

Create tunnels through multiple jump hosts:

// Tunnel through multiple servers
const multiHopTunnel = await createSSHTunnel({
ssh: {
host: 'bastion1.example.com',
username: 'user',
privateKey: '~/.ssh/id_rsa',
jumpHost: {
host: 'bastion2.example.com',
username: 'jumpuser',
privateKey: '~/.ssh/jump_key'
}
},
localPort: 3306,
remoteHost: 'database.private',
remotePort: 3306
});

Auto-Reconnecting Tunnels​

Create self-healing tunnels:

class ResilientTunnel {
constructor(config) {
this.config = config;
this.tunnel = null;
this.reconnectAttempts = 0;
this.maxReconnects = 5;
}

async connect() {
try {
this.tunnel = await createSSHTunnel(this.config);
this.reconnectAttempts = 0;

// Monitor tunnel health
this.startHealthCheck();

return this.tunnel;
} catch (error) {
if (this.reconnectAttempts < this.maxReconnects) {
this.reconnectAttempts++;
console.log(`Reconnect attempt ${this.reconnectAttempts}`);
await new Promise(r => setTimeout(r, 2000 * this.reconnectAttempts));
return this.connect();
}
throw error;
}
}

startHealthCheck() {
this.healthInterval = setInterval(async () => {
try {
// Test tunnel connectivity
await $`nc -zv localhost ${this.config.localPort}`;
} catch (error) {
console.log('Tunnel health check failed, reconnecting...');
await this.reconnect();
}
}, 30000); // Check every 30 seconds
}

async reconnect() {
if (this.tunnel) {
await this.tunnel.close().catch(() => {});
}
await this.connect();
}

async close() {
clearInterval(this.healthInterval);
if (this.tunnel) {
await this.tunnel.close();
}
}
}

Integration with Applications​

Database Connections​

Use tunnels for database access:

// PostgreSQL through tunnel
const pgTunnel = await createSSHTunnel({
ssh: sshConfig,
localPort: 5432,
remoteHost: 'postgres.internal',
remotePort: 5432
});

const { Client } = require('pg');
const pgClient = new Client({
host: 'localhost',
port: 5432,
database: 'mydb',
user: 'dbuser',
password: 'dbpass'
});

await pgClient.connect();
const result = await pgClient.query('SELECT NOW()');
await pgClient.end();
await pgTunnel.close();

HTTP Services​

Access internal HTTP services:

// Access internal API through tunnel
const apiTunnel = await createSSHTunnel({
ssh: sshConfig,
localPort: 8080,
remoteHost: 'api.internal',
remotePort: 80
});

// Make API requests
const response = await fetch('http://localhost:8080/api/data');
const data = await response.json();

await apiTunnel.close();

Security Considerations​

Tunnel Authentication​

Secure tunnel creation:

// Validate tunnel endpoints
async function createSecureTunnel(config) {
// Validate remote host
const allowedHosts = ['database.internal', 'api.internal'];
if (!allowedHosts.includes(config.remoteHost)) {
throw new Error(`Unauthorized remote host: ${config.remoteHost}`);
}

// Validate ports
const allowedPorts = [3306, 5432, 6379, 80, 443];
if (!allowedPorts.includes(config.remotePort)) {
throw new Error(`Unauthorized port: ${config.remotePort}`);
}

// Create tunnel with logging
console.log(`Creating tunnel to ${config.remoteHost}:${config.remotePort}`);
return await createSSHTunnel(config);
}

Tunnel Monitoring​

Monitor tunnel usage:

class TunnelMonitor {
constructor(tunnel, name) {
this.tunnel = tunnel;
this.name = name;
this.startTime = Date.now();
this.bytesTransferred = 0;
}

async monitor() {
// Monitor network traffic through tunnel
const interval = setInterval(async () => {
const stats = await this.getTunnelStats();
console.log(`Tunnel ${this.name} stats:`, {
uptime: Date.now() - this.startTime,
bytesTransferred: stats.bytes,
connections: stats.connections
});
}, 60000); // Every minute

return () => clearInterval(interval);
}

async getTunnelStats() {
// Get tunnel statistics
const result = await $`netstat -an | grep ${this.tunnel.localPort}`;
const connections = result.stdout.split('\n').length - 1;

return {
bytes: this.bytesTransferred,
connections
};
}
}

Best Practices​

1. Always Clean Up Tunnels​

// Use try-finally to ensure cleanup
const tunnel = await createSSHTunnel(config);
try {
// Use tunnel
await doWork();
} finally {
await tunnel.close();
}

2. Use Connection Pooling​

// Reuse tunnels for multiple operations
const tunnelPool = new Map();

function getTunnel(key, config) {
if (!tunnelPool.has(key)) {
tunnelPool.set(key, createSSHTunnel(config));
}
return tunnelPool.get(key);
}

3. Handle Tunnel Failures​

// Graceful degradation
try {
const tunnel = await createSSHTunnel(config);
// Use tunnel
} catch (error) {
console.log('Tunnel failed, trying direct connection');
// Fallback logic
}

Next Steps​