Learn to configure SSH port forwarding, remote tunnels, and SOCKS proxy for secure network connections. Set up local, remote, and dynamic port forwarding with automated monitoring and security hardening.
Prerequisites
- Root or sudo access
- SSH server installed
- Basic understanding of networking concepts
- SSH key pair configured
What this solves
SSH port forwarding creates secure tunnels through encrypted SSH connections, allowing you to access remote services, bypass firewalls, and route traffic securely through intermediate servers. This technique is essential for accessing services on private networks, securing unencrypted protocols, and creating secure pathways for database connections, web services, and administrative tasks.
SSH configuration and security hardening
Update system and install OpenSSH
Ensure you have the latest SSH server and client packages installed on your system.
sudo apt update && sudo apt upgrade -y
sudo apt install -y openssh-server openssh-client
Configure SSH server for port forwarding
Edit the SSH daemon configuration to enable port forwarding and configure security settings.
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
sudo nano /etc/ssh/sshd_config
# SSH Port Forwarding Configuration
Port 22
Protocol 2
Enable port forwarding
AllowTcpForwarding yes
GatewayPorts no
X11Forwarding no
Security hardening
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
Connection limits
MaxAuthTries 3
MaxSessions 10
ClientAliveInterval 300
ClientAliveCountMax 2
Restrict users (optional)
AllowUsers username1 username2
AllowGroups ssh-users
Enable and restart SSH service
Apply the configuration changes by restarting the SSH daemon and enabling it for automatic startup.
sudo systemctl enable ssh
sudo systemctl restart ssh
sudo systemctl status ssh
Configure SSH client settings
Create a client configuration file to store connection settings and tunnel definitions.
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/config
chmod 600 ~/.ssh/config
# SSH Client Configuration
Host jumpserver
HostName 203.0.113.10
User admin
Port 22
IdentityFile ~/.ssh/id_rsa
ServerAliveInterval 60
ServerAliveCountMax 3
Host database-tunnel
HostName 203.0.113.20
User dbuser
Port 22
IdentityFile ~/.ssh/id_rsa
LocalForward 5432 localhost:5432
ServerAliveInterval 60
Local port forwarding setup
Configure basic local port forwarding
Local port forwarding redirects traffic from a local port through the SSH tunnel to a destination server.
# Forward local port 8080 to remote web server port 80
ssh -L 8080:localhost:80 user@203.0.113.10
Forward local port 5432 to remote database server
ssh -L 5432:database.internal:5432 user@jumpserver.example.com
Forward local port 3306 to MySQL server through jump host
ssh -L 3306:mysql.internal:3306 -N -f user@jumpserver.example.com
Create persistent local tunnels
Set up background tunnels that automatically reconnect and persist across system restarts.
sudo nano /etc/systemd/system/ssh-tunnel-database.service
[Unit]
Description=SSH Tunnel to Database Server
After=network.target
[Service]
Type=simple
User=tunneluser
ExecStart=/usr/bin/ssh -N -L 5432:database.internal:5432 user@jumpserver.example.com
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable ssh-tunnel-database
sudo systemctl start ssh-tunnel-database
Configure multi-port forwarding
Forward multiple ports simultaneously through a single SSH connection for complex service access.
# Forward multiple services through one connection
ssh -L 8080:web.internal:80 \
-L 5432:db.internal:5432 \
-L 6379:redis.internal:6379 \
-L 9200:elasticsearch.internal:9200 \
-N -f user@jumpserver.example.com
Remote port forwarding configuration
Set up basic remote port forwarding
Remote port forwarding allows external connections to reach services on your local machine through the remote server.
# Allow remote server to forward connections to local service
ssh -R 8080:localhost:80 user@203.0.113.10
Forward remote port to local development server
ssh -R 3000:localhost:3000 -N -f user@development.example.com
Forward remote port to local database for remote access
ssh -R 5432:localhost:5432 user@remote.example.com
Configure gateway ports for external access
Enable external connections to reach forwarded ports by configuring gateway ports on the remote server.
sudo nano /etc/ssh/sshd_config
# Allow remote port forwarding from external IPs
GatewayPorts yes
Or restrict to specific interfaces
GatewayPorts clientspecified
sudo systemctl restart ssh
Now bind to all interfaces on remote server
ssh -R 0.0.0.0:8080:localhost:80 user@remote.example.com
Create reverse tunnel service
Set up a persistent reverse tunnel service for continuous remote access to local services.
sudo nano /etc/systemd/system/reverse-tunnel-web.service
[Unit]
Description=Reverse SSH Tunnel for Web Service
After=network.target
[Service]
Type=simple
User=tunneluser
ExecStart=/usr/bin/ssh -N -R 8080:localhost:80 user@public.example.com
Restart=always
RestartSec=15
StandardOutput=journal
StandardError=journal
Auto-restart on failure
StartLimitInterval=0
StartLimitBurst=0
[Install]
WantedBy=multi-user.target
sudo systemctl enable reverse-tunnel-web
sudo systemctl start reverse-tunnel-web
Dynamic port forwarding and SOCKS proxy
Create SOCKS proxy tunnel
Dynamic port forwarding creates a SOCKS proxy that routes traffic through the SSH connection for any destination.
# Create SOCKS5 proxy on local port 8080
ssh -D 8080 user@proxy.example.com
Background SOCKS proxy with auto-reconnect
ssh -D 8080 -N -f user@proxy.example.com
SOCKS proxy with specific bind address
ssh -D 127.0.0.1:8080 -N user@proxy.example.com
Configure applications to use SOCKS proxy
Configure web browsers and applications to route traffic through the SOCKS proxy tunnel.
# Test SOCKS proxy with curl
curl --socks5 127.0.0.1:8080 http://httpbin.org/ip
Configure git to use SOCKS proxy
git config --global http.proxy socks5://127.0.0.1:8080
git config --global https.proxy socks5://127.0.0.1:8080
Remove proxy configuration
git config --global --unset http.proxy
git config --global --unset https.proxy
Set up persistent SOCKS proxy service
Create a system service for continuous SOCKS proxy availability with automatic reconnection.
sudo nano /etc/systemd/system/socks-proxy.service
[Unit]
Description=SSH SOCKS Proxy
After=network.target
[Service]
Type=simple
User=proxyuser
ExecStart=/usr/bin/ssh -N -D 127.0.0.1:8080 user@proxy.example.com
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment variables
Environment=SSH_AUTH_SOCK=/run/user/1001/ssh-agent.socket
[Install]
WantedBy=multi-user.target
sudo systemctl enable socks-proxy
sudo systemctl start socks-proxy
SSH tunnel automation and monitoring
Create tunnel monitoring script
Develop a monitoring script to check tunnel health and automatically restart failed connections.
sudo nano /usr/local/bin/monitor-ssh-tunnels.sh
#!/bin/bash
SSH Tunnel Monitoring Script
LOG_FILE="/var/log/ssh-tunnel-monitor.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
Function to log messages
log_message() {
echo "[$DATE] $1" | sudo tee -a "$LOG_FILE"
}
Check if tunnel port is responsive
check_tunnel() {
local port=$1
local service_name=$2
if nc -z localhost "$port" 2>/dev/null; then
log_message "SUCCESS: $service_name tunnel on port $port is active"
return 0
else
log_message "ERROR: $service_name tunnel on port $port is down"
return 1
fi
}
Restart tunnel service
restart_service() {
local service_name=$1
log_message "Restarting service: $service_name"
sudo systemctl restart "$service_name"
sleep 5
}
Monitor database tunnel
if ! check_tunnel 5432 "Database"; then
restart_service "ssh-tunnel-database"
fi
Monitor SOCKS proxy
if ! check_tunnel 8080 "SOCKS Proxy"; then
restart_service "socks-proxy"
fi
Monitor web tunnel
if ! check_tunnel 8081 "Web Service"; then
restart_service "reverse-tunnel-web"
fi
sudo chmod +x /usr/local/bin/monitor-ssh-tunnels.sh
Schedule tunnel monitoring
Set up automated monitoring using systemd timers to regularly check tunnel health.
sudo nano /etc/systemd/system/ssh-tunnel-monitor.service
[Unit]
Description=SSH Tunnel Health Monitor
[Service]
Type=oneshot
ExecStart=/usr/local/bin/monitor-ssh-tunnels.sh
User=root
sudo nano /etc/systemd/system/ssh-tunnel-monitor.timer
[Unit]
Description=Run SSH Tunnel Monitor every 5 minutes
Requires=ssh-tunnel-monitor.service
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable ssh-tunnel-monitor.timer
sudo systemctl start ssh-tunnel-monitor.timer
Configure connection multiplexing
Enable SSH connection multiplexing to reuse connections and improve tunnel reliability.
nano ~/.ssh/config
# SSH Connection Multiplexing
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
ServerAliveInterval 60
ServerAliveCountMax 3
Create socket directory
Host *
ProxyCommand none
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets
Set up tunnel logging and alerting
Configure comprehensive logging and integrate with monitoring systems for tunnel visibility.
sudo nano /etc/rsyslog.d/50-ssh-tunnels.conf
# SSH Tunnel Logging Configuration
:programname, isequal, "ssh" /var/log/ssh-tunnels.log
:programname, isequal, "ssh" stop
sudo systemctl restart rsyslog
Create log rotation configuration
sudo nano /etc/logrotate.d/ssh-tunnels
/var/log/ssh-tunnels.log {
daily
missingok
rotate 30
compress
notifempty
create 644 root root
postrotate
systemctl reload rsyslog
endscript
}
Verify your setup
Test your SSH tunnels and monitoring configuration to ensure everything works correctly.
# Check SSH service status
sudo systemctl status ssh
List active SSH connections
ss -tuln | grep :22
Test local port forwarding
netstat -tlnp | grep :8080
curl -I http://localhost:8080
Verify SOCKS proxy
curl --socks5 127.0.0.1:8080 http://httpbin.org/ip
Check tunnel services
sudo systemctl status ssh-tunnel-database
sudo systemctl status socks-proxy
sudo systemctl status reverse-tunnel-web
Monitor tunnel health
sudo /usr/local/bin/monitor-ssh-tunnels.sh
tail -f /var/log/ssh-tunnel-monitor.log
Verify monitoring timer
sudo systemctl status ssh-tunnel-monitor.timer
sudo systemctl list-timers ssh-tunnel-monitor.timer
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Connection refused on forwarded port | Service not running on target server | Verify target service with systemctl status service-name |
| Tunnel drops frequently | Network instability or timeout | Add ServerAliveInterval 60 to SSH config |
| Cannot bind to port | Port already in use | Check with netstat -tlnp | grep port and change port |
| Remote forwarding fails | GatewayPorts disabled | Set GatewayPorts yes in /etc/ssh/sshd_config |
| SOCKS proxy not working | Application not configured | Configure application proxy settings to use 127.0.0.1:8080 |
| Permission denied for tunnel | SSH key not configured | Copy SSH key with ssh-copy-id user@server |
| Service won't start | User doesn't exist | Create tunnel user with sudo useradd -r -s /bin/false tunneluser |
Next steps
- Configure SSH key authentication and disable password login for secure server access
- Configure OpenVPN LDAP authentication for enterprise users with Active Directory integration
- Set up centralized logging with rsyslog and logrotate for security events
- Configure SSH jump hosts and bastion servers for secure network access
- Implement SSH certificate authority for scalable authentication
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
SSH_PORT=${1:-22}
SSH_USER=${2:-""}
# Usage message
usage() {
echo "Usage: $0 [SSH_PORT] [SSH_USER]"
echo " SSH_PORT: SSH port to configure (default: 22)"
echo " SSH_USER: User to allow SSH access (optional)"
exit 1
}
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Error handling
cleanup() {
if [ -f /etc/ssh/sshd_config.backup ]; then
log_warning "Restoring SSH configuration backup..."
cp /etc/ssh/sshd_config.backup /etc/ssh/sshd_config
fi
}
trap cleanup ERR
# Check if running as root or with sudo
check_privileges() {
if [ "$EUID" -ne 0 ]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
}
# Detect OS and package manager
detect_os() {
if [ ! -f /etc/os-release ]; then
log_error "/etc/os-release not found. Cannot detect OS."
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
SSH_SERVICE="ssh"
OPENSSH_SERVER="openssh-server"
OPENSSH_CLIENT="openssh-client"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
SSH_SERVICE="sshd"
OPENSSH_SERVER="openssh-server"
OPENSSH_CLIENT="openssh-clients"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
SSH_SERVICE="sshd"
OPENSSH_SERVER="openssh-server"
OPENSSH_CLIENT="openssh-clients"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
SSH_SERVICE="sshd"
OPENSSH_SERVER="openssh-server"
OPENSSH_CLIENT="openssh-clients"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_success "Detected OS: $PRETTY_NAME"
}
# Update system packages
update_system() {
log_info "[1/6] Updating system packages..."
$PKG_UPDATE
log_success "System packages updated"
}
# Install OpenSSH
install_openssh() {
log_info "[2/6] Installing OpenSSH server and client..."
$PKG_INSTALL $OPENSSH_SERVER $OPENSSH_CLIENT
log_success "OpenSSH installed"
}
# Configure SSH daemon
configure_sshd() {
log_info "[3/6] Configuring SSH daemon..."
# Backup original config
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
# Create new SSH configuration
cat > /etc/ssh/sshd_config << EOF
# SSH Daemon Configuration - Generated by SSH Tunnel Setup Script
Port $SSH_PORT
Protocol 2
# Port forwarding configuration
AllowTcpForwarding yes
GatewayPorts no
X11Forwarding no
# Security hardening
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
# Connection limits
MaxAuthTries 3
MaxSessions 10
ClientAliveInterval 300
ClientAliveCountMax 2
# Host keys
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
# Logging
SyslogFacility AUTH
LogLevel INFO
# Authentication
LoginGraceTime 60
StrictModes yes
MaxStartups 10:30:100
EOF
# Add user restriction if specified
if [ -n "$SSH_USER" ]; then
echo "AllowUsers $SSH_USER" >> /etc/ssh/sshd_config
log_info "Added user restriction for: $SSH_USER"
fi
# Set proper permissions
chmod 644 /etc/ssh/sshd_config
log_success "SSH daemon configured"
}
# Configure firewall
configure_firewall() {
log_info "[4/6] Configuring firewall..."
case "$ID" in
ubuntu|debian)
if command -v ufw >/dev/null 2>&1; then
ufw allow $SSH_PORT/tcp
log_info "UFW rule added for port $SSH_PORT"
fi
;;
almalinux|rocky|centos|rhel|ol|fedora)
if command -v firewall-cmd >/dev/null 2>&1; then
if [ "$SSH_PORT" != "22" ]; then
firewall-cmd --permanent --add-port=$SSH_PORT/tcp
firewall-cmd --reload
log_info "Firewalld rule added for port $SSH_PORT"
fi
fi
;;
esac
log_success "Firewall configured"
}
# Start SSH service
start_ssh_service() {
log_info "[5/6] Starting SSH service..."
# Test configuration first
/usr/sbin/sshd -t
# Enable and start service
systemctl enable $SSH_SERVICE
systemctl restart $SSH_SERVICE
log_success "SSH service started and enabled"
}
# Create SSH client configuration template
create_client_config() {
log_info "[6/6] Creating SSH client configuration template..."
# Create template in /etc/skel for new users
mkdir -p /etc/skel/.ssh
chmod 700 /etc/skel/.ssh
cat > /etc/skel/.ssh/config << 'EOF'
# SSH Client Configuration Template
# Copy this to ~/.ssh/config and customize for your needs
# Example jump server configuration
#Host jumpserver
# HostName your-jump-server.com
# User your-username
# Port 22
# IdentityFile ~/.ssh/id_rsa
# ServerAliveInterval 60
# ServerAliveCountMax 3
# Example database tunnel
#Host database-tunnel
# HostName your-db-server.com
# User your-username
# Port 22
# IdentityFile ~/.ssh/id_rsa
# LocalForward 5432 localhost:5432
# ServerAliveInterval 60
# Example web service tunnel
#Host web-tunnel
# HostName your-web-server.com
# User your-username
# Port 22
# IdentityFile ~/.ssh/id_rsa
# LocalForward 8080 localhost:80
# ServerAliveInterval 60
EOF
chmod 600 /etc/skel/.ssh/config
# Create systemd service template for persistent tunnels
cat > /etc/systemd/system/ssh-tunnel@.service << 'EOF'
[Unit]
Description=SSH Tunnel Service (%i)
After=network.target
[Service]
Type=simple
User=nobody
ExecStart=/usr/bin/ssh -N -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes %i
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
chmod 644 /etc/systemd/system/ssh-tunnel@.service
systemctl daemon-reload
log_success "SSH client configuration template created"
}
# Verify installation
verify_installation() {
log_info "Verifying SSH tunnel configuration..."
# Check SSH service status
if systemctl is-active --quiet $SSH_SERVICE; then
log_success "SSH service is running"
else
log_error "SSH service is not running"
return 1
fi
# Check SSH configuration
if /usr/sbin/sshd -t; then
log_success "SSH configuration is valid"
else
log_error "SSH configuration has errors"
return 1
fi
# Check listening port
if ss -tlnp | grep -q ":$SSH_PORT "; then
log_success "SSH is listening on port $SSH_PORT"
else
log_error "SSH is not listening on port $SSH_PORT"
return 1
fi
log_success "SSH tunnel setup completed successfully!"
echo
echo "Next steps:"
echo "1. Generate SSH keys: ssh-keygen -t ed25519 -C 'your_email@example.com'"
echo "2. Copy public key to remote servers: ssh-copy-id user@remote-server"
echo "3. Configure client tunnels in ~/.ssh/config"
echo "4. Test local port forwarding: ssh -L 8080:localhost:80 user@remote-server"
echo "5. For persistent tunnels, customize /etc/systemd/system/ssh-tunnel@.service"
}
# Main execution
main() {
log_info "Starting SSH tunnel configuration..."
check_privileges
detect_os
update_system
install_openssh
configure_sshd
configure_firewall
start_ssh_service
create_client_config
verify_installation
}
# Run main function
main "$@"
Review the script before running. Execute with: bash install.sh