Database Proxy & SSH Tunneling: Your Gateway to Secure Database Access πŸ”

June 20, 2025 β€’ Securing database connections like a pro

🎯 The Challenge: Accessing Private Databases Securely

Ever needed to connect to a database that's locked away in a private network? Whether it's an AWS RDS instance in a private subnet or a database behind a corporate firewall, direct access isn't always possibleβ€”and that's by design! Let's explore professional methods to establish secure database connections without compromising security.

πŸ›  Prerequisites

  • SSH client (OpenSSH, PuTTY, or similar) πŸ”‘
  • Database client (psql, DBeaver, pgAdmin, etc.) πŸ—„οΈ
  • Access to a bastion host or EC2 instance πŸ–₯️
  • Proper SSH keys and permissions πŸ“‹
  • Basic understanding of networking concepts 🌐

πŸš€ Method 1: SSH Tunnel Through Bastion Host

The Classic SSH Port Forwarding Approach

This is the most common and reliable method for accessing private databases. Here's how to set up a secure tunnel:

ssh -i /path/to/your-key.pem -L 5433:private-db.internal.domain:5432 ubuntu@bastion-host-ip -N

Real-World Example

Let's break down a practical scenario with actual parameters:

# Connect to staging database through bastion host
ssh -i infra-staging-bastion-key.pem \
    -L 5433:shared-db.databases.internal.company.stg:5432 \
    [email protected] \
    -N

Command Breakdown

Parameter Purpose Example Value
-i SSH private key file infra-staging-bastion-key.pem
-L Local port forwarding 5433:target-host:5432
-N No remote command execution Just tunnel, no shell
ubuntu@host SSH user and bastion host [email protected]

Enhanced SSH Tunnel with Additional Options

# More robust tunnel with connection options
ssh -i infra-staging-bastion-key.pem \
    -L 5433:shared-db.databases.internal.company.stg:5432 \
    -o ServerAliveInterval=60 \
    -o ServerAliveCountMax=3 \
    -o ExitOnForwardFailure=yes \
    [email protected] \
    -N -f

Advanced Options Explained

  • -f: Run in background after authentication
  • ServerAliveInterval=60: Send keepalive every 60 seconds
  • ServerAliveCountMax=3: Max failed keepalives before disconnect
  • ExitOnForwardFailure=yes: Exit if port forwarding fails

πŸ”§ Method 2: AWS RDS Proxy (Cloud-Native Solution)

Setting Up RDS Proxy

AWS RDS Proxy provides connection pooling and enhanced security for RDS databases:

# Create RDS Proxy using AWS CLI
aws rds create-db-proxy \
    --db-proxy-name production-db-proxy \
    --engine-family postgresql \
    --target-group-name default \
    --vpc-subnet-ids subnet-12345678 subnet-87654321 \
    --vpc-security-group-ids sg-abcdef123456 \
    --auth AuthScheme=SECRETS,SecretArn=arn:aws:secretsmanager:region:account:secret:db-credentials

Application Configuration

# Environment variables for your application
export DB_HOST=production-db-proxy.proxy-xyz.us-east-1.rds.amazonaws.com
export DB_PORT=5432
export DB_NAME=production_db
export DB_USER=app_user

πŸ–₯️ Method 3: AWS Systems Manager Session Manager

SSM Port Forwarding (No Direct SSH Required)

# Create tunnel through SSM
aws ssm start-session \
    --target i-1234567890abcdef0 \
    --document-name AWS-StartPortForwardingSessionToRemoteHost \
    --parameters host="shared-db.databases.internal.company.stg",portNumber="5432",localPortNumber="5433"

πŸ’» Database Client Configuration

Connecting Through the Tunnel

Once your tunnel is established, configure your database client to connect to the local forwarded port:

# Using psql command line
psql -h localhost -p 5433 -U your_username -d your_database

# Connection string format
postgresql://username:password@localhost:5433/database_name

DBeaver Configuration

Setting Value Notes
Host localhost Connect to local tunnel endpoint
Port 5433 Local forwarded port
Database your_database Target database name
Username db_user Database credentials

πŸ”’ Security Best Practices

SSH Key Management

# Set proper permissions on SSH keys
chmod 600 infra-staging-bastion-key.pem

# Store keys securely
mkdir -p ~/.ssh/keys
mv *.pem ~/.ssh/keys/
chmod 700 ~/.ssh/keys

Connection Security Checklist

  • βœ… Use strong SSH key pairs (RSA 2048+ or Ed25519)
  • βœ… Implement proper firewall rules on bastion hosts
  • βœ… Use non-standard local ports to avoid conflicts
  • βœ… Enable SSH key forwarding only when necessary
  • βœ… Monitor and log tunnel connections
  • βœ… Rotate SSH keys regularly

πŸ› οΈ Automation Scripts

Tunnel Management Script

#!/bin/bash
# db-tunnel.sh - Database tunnel management script

BASTION_HOST="203.0.113.45"
BASTION_USER="ubuntu"
BASTION_KEY="~/.ssh/keys/infra-staging-bastion-key.pem"
DB_HOST="shared-db.databases.internal.company.stg"
DB_PORT="5432"
LOCAL_PORT="5433"

start_tunnel() {
    echo "πŸš€ Starting database tunnel..."
    ssh -i "$BASTION_KEY" \
        -L "$LOCAL_PORT:$DB_HOST:$DB_PORT" \
        -o ServerAliveInterval=60 \
        -o ServerAliveCountMax=3 \
        -o ExitOnForwardFailure=yes \
        "$BASTION_USER@$BASTION_HOST" \
        -N -f
    
    if [ $? -eq 0 ]; then
        echo "βœ… Tunnel established on localhost:$LOCAL_PORT"
    else
        echo "❌ Failed to establish tunnel"
        exit 1
    fi
}

stop_tunnel() {
    echo "πŸ›‘ Stopping database tunnel..."
    pkill -f "ssh.*$LOCAL_PORT:$DB_HOST:$DB_PORT"
    echo "βœ… Tunnel stopped"
}

status_tunnel() {
    if pgrep -f "ssh.*$LOCAL_PORT:$DB_HOST:$DB_PORT" > /dev/null; then
        echo "βœ… Tunnel is active on localhost:$LOCAL_PORT"
    else
        echo "❌ Tunnel is not running"
    fi
}

case "$1" in
    start)
        start_tunnel
        ;;
    stop)
        stop_tunnel
        ;;
    status)
        status_tunnel
        ;;
    restart)
        stop_tunnel
        sleep 2
        start_tunnel
        ;;
    *)
        echo "Usage: $0 {start|stop|status|restart}"
        exit 1
        ;;
esac

Using the Script

# Make script executable
chmod +x db-tunnel.sh

# Start tunnel
./db-tunnel.sh start

# Check status
./db-tunnel.sh status

# Stop tunnel
./db-tunnel.sh stop

πŸ” Troubleshooting Common Issues

Connection Refused Errors

# Check if tunnel is running
ps aux | grep ssh | grep 5433

# Test local port
telnet localhost 5433

# Verify bastion host connectivity
ssh -i your-key.pem ubuntu@bastion-host echo "Connection OK"

Permission Denied Issues

# Fix SSH key permissions
chmod 600 your-key.pem

# Check SSH agent
ssh-add -l

# Add key to agent if needed
ssh-add your-key.pem

πŸ“Š Monitoring and Logging

Connection Monitoring

# Monitor active SSH connections
netstat -an | grep :5433

# Check SSH tunnel logs
ssh -v -i your-key.pem -L 5433:db-host:5432 user@bastion -N

# Monitor database connections
psql -h localhost -p 5433 -c "SELECT * FROM pg_stat_activity;"

🎯 Production Considerations

High Availability Setup

  • πŸ”„ Use multiple bastion hosts for redundancy
  • βš–οΈ Implement load balancing for database connections
  • πŸ“Š Set up monitoring and alerting for tunnel health
  • πŸ” Use AWS Secrets Manager for credential management
  • πŸ“ Implement proper logging and audit trails

Alternative Solutions for Production

Solution Use Case Pros Cons
VPC Peering Cross-VPC access Native AWS, low latency Complex routing
AWS PrivateLink Service-to-service Highly secure, scalable Additional cost
VPN Gateway Site-to-site connectivity Persistent connection Higher complexity
Direct Connect Dedicated bandwidth Consistent performance Expensive, long setup

🐳 Docker-Based Tunnel Solution

Containerized SSH Tunnel

For consistent environments and easy deployment, you can containerize your SSH tunnel:

# Dockerfile for SSH tunnel container
FROM alpine:latest

RUN apk add --no-cache openssh-client

COPY ssh-keys/ /root/.ssh/
RUN chmod 700 /root/.ssh && chmod 600 /root/.ssh/*

EXPOSE 5433

CMD ["sh", "-c", "ssh -i /root/.ssh/bastion-key.pem -L 0.0.0.0:5433:${DB_HOST}:${DB_PORT} -o StrictHostKeyChecking=no ${BASTION_USER}@${BASTION_HOST} -N"]

Docker Compose Configuration

# docker-compose.yml
version: '3.8'

services:
  db-tunnel:
    build: .
    ports:
      - "5433:5433"
    environment:
      - DB_HOST=shared-db.databases.internal.company.stg
      - DB_PORT=5432
      - BASTION_HOST=203.0.113.45
      - BASTION_USER=ubuntu
    volumes:
      - ./ssh-keys:/root/.ssh:ro
    restart: unless-stopped

  app:
    image: your-app:latest
    depends_on:
      - db-tunnel
    environment:
      - DATABASE_URL=postgresql://user:pass@db-tunnel:5433/dbname

πŸ”§ Advanced Tunneling Techniques

Dynamic Port Forwarding (SOCKS Proxy)

# Create SOCKS proxy for multiple services
ssh -i bastion-key.pem -D 1080 [email protected] -N

# Configure application to use SOCKS proxy
export HTTP_PROXY=socks5://localhost:1080
export HTTPS_PROXY=socks5://localhost:1080

Reverse SSH Tunnel

# Allow remote access to local service
ssh -i bastion-key.pem -R 8080:localhost:3000 [email protected]

# Useful for exposing local development server to remote team

Multi-Hop SSH Tunneling

# Connect through multiple jump hosts
ssh -i key1.pem -J [email protected],[email protected] \
    -L 5433:final-db.internal:5432 ubuntu@final-host -N

# Or using ProxyCommand
ssh -i final-key.pem \
    -o ProxyCommand="ssh -i jump-key.pem ubuntu@jump-host nc %h %p" \
    -L 5433:db.internal:5432 ubuntu@final-host -N

πŸ“± GUI Tools and Alternatives

SSH Tunnel Manager Applications

Tool Platform Features Best For
SSH Tunnel Manager macOS GUI, profiles, auto-reconnect Mac users, visual management
PuTTY Windows SSH client, tunnel config Windows environments
MobaXterm Windows All-in-one SSH toolkit Power users, multiple protocols
Termius Cross-platform Cloud sync, mobile support Team collaboration

Database-Specific Tools

# pgAdmin with SSH tunnel
# Configure in pgAdmin:
# - SSH Tunnel tab: Enable
# - Tunnel host: bastion-host-ip
# - Tunnel port: 22
# - Username: ubuntu
# - Authentication: Identity file

# DBeaver SSH configuration
# Connection -> SSH:
# βœ… Use SSH Tunnel
# Host/IP: 203.0.113.45
# Port: 22
# User Name: ubuntu
# Authentication Method: Public Key
# Private Key: /path/to/bastion-key.pem

🚨 Security Hardening

Bastion Host Security

# Secure bastion host configuration
# /etc/ssh/sshd_config
Port 2222                          # Non-standard port
PermitRootLogin no                 # Disable root login
PasswordAuthentication no          # Key-only authentication
PubkeyAuthentication yes           # Enable public key auth
MaxAuthTries 3                     # Limit auth attempts
ClientAliveInterval 300            # Session timeout
ClientAliveCountMax 2              # Max missed heartbeats
AllowUsers ubuntu admin            # Whitelist users
DenyUsers root                     # Blacklist users

# Restart SSH service
sudo systemctl restart sshd

Network Security Groups

# AWS Security Group rules for bastion host
# Inbound rules:
# SSH (22) from your IP only: 203.0.113.100/32
# Custom TCP (2222) from your IP: 203.0.113.100/32

# Outbound rules:
# PostgreSQL (5432) to database security group
# HTTPS (443) for package updates
# HTTP (80) for package updates

πŸ“Š Performance Optimization

SSH Configuration Tuning

# ~/.ssh/config optimization
Host bastion-staging
    HostName 203.0.113.45
    User ubuntu
    IdentityFile ~/.ssh/keys/bastion-key.pem
    Port 22
    Compression yes
    ServerAliveInterval 60
    ServerAliveCountMax 3
    TCPKeepAlive yes
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

# Create socket directory
mkdir -p ~/.ssh/sockets

Connection Multiplexing

# Reuse existing SSH connection
ssh bastion-staging -L 5433:db.internal:5432 -N -f

# Additional tunnels use same connection
ssh bastion-staging -L 6379:redis.internal:6379 -N -f
ssh bastion-staging -L 9200:elasticsearch.internal:9200 -N -f

πŸ”„ Automation and CI/CD Integration

GitHub Actions Example

# .github/workflows/database-migration.yml
name: Database Migration

on:
  push:
    branches: [main]

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup SSH tunnel
        run: |
          echo "${{ secrets.BASTION_PRIVATE_KEY }}" > bastion_key.pem
          chmod 600 bastion_key.pem
          ssh -i bastion_key.pem -L 5433:${{ secrets.DB_HOST }}:5432 \
              -o StrictHostKeyChecking=no \
              ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }} \
              -N -f
      
      - name: Run migrations
        run: |
          export DATABASE_URL="postgresql://${{ secrets.DB_USER }}:${{ secrets.DB_PASS }}@localhost:5433/${{ secrets.DB_NAME }}"
          npm run migrate
      
      - name: Cleanup
        if: always()
        run: |
          pkill -f "ssh.*5433"
          rm -f bastion_key.pem

πŸ“ˆ Monitoring and Alerting

Health Check Script

#!/bin/bash
# tunnel-health-check.sh

TUNNEL_PORT=5433
DB_HOST="localhost"
DB_PORT=$TUNNEL_PORT
DB_NAME="production_db"
DB_USER="monitor_user"

check_tunnel() {
    if ! netstat -an | grep -q ":$TUNNEL_PORT.*LISTEN"; then
        echo "❌ SSH tunnel not listening on port $TUNNEL_PORT"
        return 1
    fi
    echo "βœ… SSH tunnel is active"
    return 0
}

check_database() {
    if timeout 10 pg_isready -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME; then
        echo "βœ… Database is reachable"
        return 0
    else
        echo "❌ Database connection failed"
        return 1
    fi
}

send_alert() {
    local message="$1"
    # Send to Slack, email, or monitoring system
    curl -X POST -H 'Content-type: application/json' \
        --data "{\"text\":\"🚨 Database Tunnel Alert: $message\"}" \
        "$SLACK_WEBHOOK_URL"
}

main() {
    if ! check_tunnel; then
        send_alert "SSH tunnel is down"
        exit 1
    fi
    
    if ! check_database; then
        send_alert "Database unreachable through tunnel"
        exit 1
    fi
    
    echo "πŸŽ‰ All checks passed"
}

main "$@"

πŸŽ“ Best Practices Summary

βœ… Do's

  • Use strong SSH keys (RSA 4096 or Ed25519)
  • Implement proper firewall rules
  • Monitor tunnel health continuously
  • Use connection multiplexing for efficiency
  • Automate tunnel management with scripts
  • Keep SSH clients and servers updated
  • Use non-standard ports when possible
  • Implement proper logging and auditing

❌ Don'ts

  • Don't use password authentication
  • Don't expose SSH keys in version control
  • Don't use default ports in production
  • Don't skip connection timeout settings
  • Don't ignore failed connection alerts
  • Don't use weak encryption algorithms
  • Don't leave tunnels running indefinitely

πŸ”— Additional Resources

🎯 Conclusion

Secure database access through SSH tunneling and proxy solutions is essential for modern infrastructure security. Whether you're using traditional SSH port forwarding, AWS RDS Proxy, or Systems Manager Session Manager, the key is to implement proper security practices while maintaining operational efficiency.

Remember that security is not a one-time setupβ€”it requires ongoing monitoring, regular updates, and continuous improvement of your access patterns. Choose the method that best fits your infrastructure, security requirements, and operational complexity.

Stay secure, stay connected, and may your tunnels always be stable! πŸš‡

#Database #SSH #Tunneling #Proxy #AWS #RDS #PostgreSQL #Security #DevOps #Infrastructure