Local Port Forwarding (-L)
Local port forwarding (SSH -L flag) creates a secure tunnel that lets you access remote services from your local machine as if they were running locally.
What is Local Port Forwarding?
Simple Explanation: You want to access a service (like MySQL, Redis, web app) running on a remote server, but it's not exposed to the internet. Local port forwarding creates a tunnel that makes the remote service appear local.
Flow:
Your App → localhost:LOCAL_PORT → SSH Tunnel → Remote Server:REMOTE_PORT
Example:
MySQL Client → localhost:3306 → SSH → Production Server:3306 (MySQL)
Now you can connect your local MySQL client to localhost:3306 and it accesses the remote MySQL!
Why Use Local Port Forwarding?
Security Benefits
✅ Encrypted Connection - All traffic goes through SSH
✅ Firewall Bypass - Service doesn't need public access
✅ No VPN Needed - SSH provides secure tunnel
✅ Temporary Access - Close tunnel when done
Common Use Cases
-
Database Access
- Connect to production MySQL
- Query PostgreSQL databases
- Access MongoDB remotely
- Redis cache management
-
Web Development
- Access internal web apps
- Test APIs not publicly exposed
- Preview staging environments
- Access admin panels
-
Remote Services
- Access internal APIs
- Connect to message queues
- Reach application servers
- Access monitoring tools
Creating Local Port Forwarding
From Port Forwarding Panel
- Open Port Forwarding in sidebar
- Click "New Tunnel"
- Fill in configuration:
┌──────────────────────────────────────────────┐
│ New Port Forwarding Tunnel │
├──────────────────────────────────────────────┤
│ Name: * │
│ [MySQL Database Access ] │
│ │
│ Connection: * │
│ [Production Server ▼] │
│ │
│ Type: * │
│ [●] Local [ ] Remote [ ] Dynamic │
│ │
│ ─── Local Configuration ─── │
│ Local Port: * │
│ [3306 ] │
│ │
│ Bind Address: │
│ [127.0.0.1 ] ← Only localhost │
│ │
│ ─── Remote Configuration ─── │
│ Remote Host: * │
│ [localhost ] ← On remote server │
│ │
│ Remote Port: * │
│ [3306 ] │
│ │
│ ─── Options ─── │
│ [✓] Auto-start with connection │
│ [ ] Start immediately │
│ │
│ Description: │
│ [Access MySQL on production server ] │
│ │
│ [Cancel] [Create Tunnel] │
└──────────────────────────────────────────────┘
- Click "Create Tunnel"
- Click "Start" to activate
Understanding Configuration
Name
Descriptive name for the tunnel:
✓ "MySQL Production Database"
✓ "Redis Cache Access"
✓ "Internal API Server"
✗ "Tunnel 1"
✗ "Forward"
Local Port
Port on your computer to listen on:
Examples:
3306- MySQL default5432- PostgreSQL default6379- Redis default8080- Common web port
Important:
- Must be available (not in use)
- Ports < 1024 require admin/root
- Use same as remote for convenience
Check if port is free:
# macOS/Linux
lsof -i :3306
# Windows
netstat -ano | findstr :3306
Bind Address
Which network interface to bind to:
Options:
127.0.0.1 (localhost) - Recommended
✓ Only accessible from your computer
✓ Secure - no network exposure
✓ Best for most use cases
0.0.0.0 (all interfaces) - Risky
⚠ Accessible from your local network
⚠ Others can connect through you
⚠ Only use if you know what you're doing
Specific IP
Bind to one network interface
Example: 192.168.1.100
Remote Host
Target server from SSH server's perspective:
Options:
localhost (most common)
Service runs on same server as SSH
Example: MySQL on same server as SSH
Internal IP
192.168.1.50
Service on different server in network
SSH server can reach it
Internal hostname
database.internal
db-primary.local
Service on internal network
Important: This is from the remote server's viewpoint!
Remote Port
Port of the service on remote host:
Common Ports:
- 3306 - MySQL/MariaDB
- 5432 - PostgreSQL
- 6379 - Redis
- 27017 - MongoDB
- 5672 - RabbitMQ
- 9200 - Elasticsearch
- 8080 - Common web apps
Real-World Examples
Example 1: MySQL Database Access
Scenario: Production MySQL not publicly accessible
Setup:
Name: Production MySQL
Type: Local
Local Port: 3306
Bind Address: 127.0.0.1
Remote Host: localhost
Remote Port: 3306
Usage:
# Start tunnel in Xermius
# Connect with MySQL client
mysql -h 127.0.0.1 -P 3306 -u dbuser -p
# Or with GUI tool (MySQL Workbench, DBeaver)
Host: 127.0.0.1
Port: 3306
Why it works:
- Your MySQL client connects to
127.0.0.1:3306 - Xermius forwards to SSH server
- SSH server connects to its own MySQL at port 3306
- Data flows through encrypted tunnel
Example 2: PostgreSQL on Internal Server
Scenario: PostgreSQL runs on separate database server
Network:
Your Computer
↓ SSH
SSH Server (192.168.1.10)
↓ Internal Network
Database Server (192.168.1.50:5432)
Setup:
Name: PostgreSQL Database
Type: Local
Local Port: 5432
Bind Address: 127.0.0.1
Remote Host: 192.168.1.50 ← Internal database server
Remote Port: 5432
Usage:
# Connect with psql
psql -h 127.0.0.1 -p 5432 -U dbuser database_name
# Or with GUI tool (pgAdmin, DataGrip)
Host: localhost
Port: 5432
Example 3: Redis Cache Access
Scenario: Access Redis for debugging
Setup:
Name: Redis Cache
Type: Local
Local Port: 6379
Bind Address: 127.0.0.1
Remote Host: localhost
Remote Port: 6379
Usage:
# Connect with redis-cli
redis-cli -h 127.0.0.1 -p 6379
# Test connection
127.0.0.1:6379> PING
PONG
# View keys
127.0.0.1:6379> KEYS *
Example 4: Web Application Preview
Scenario: Preview internal web app
Setup:
Name: Internal Admin Panel
Type: Local
Local Port: 8080
Bind Address: 127.0.0.1
Remote Host: localhost
Remote Port: 80
Usage:
Open browser: http://localhost:8080
You see the remote web app as if it's local!
Example 5: Multiple Ports to Same Server
Scenario: Access multiple services on one server
Setup:
Tunnel 1:
Name: MySQL
Local: 3306 → Remote: localhost:3306
Tunnel 2:
Name: Redis
Local: 6379 → Remote: localhost:6379
Tunnel 3:
Name: Web App
Local: 8080 → Remote: localhost:80
All run simultaneously!
Example 6: Non-Standard Ports
Scenario: MySQL running on custom port
Setup:
Name: MySQL Custom Port
Type: Local
Local Port: 3307 ← Different local port
Bind Address: 127.0.0.1
Remote Host: localhost
Remote Port: 3307 ← Custom MySQL port
Why different local port?
- Maybe you have local MySQL on 3306
- Avoid port conflict
- Can access both local and remote
Example 7: Internal API Server
Scenario: Test internal REST API
Setup:
Name: Internal API
Type: Local
Local Port: 8000
Bind Address: 127.0.0.1
Remote Host: api.internal
Remote Port: 8000
Usage:
# Test API endpoints
curl http://localhost:8000/api/users
# Or use Postman/Insomnia
Base URL: http://localhost:8000
Example 8: MongoDB Database
Scenario: Connect to production MongoDB
Setup:
Name: Production MongoDB
Type: Local
Local Port: 27017
Bind Address: 127.0.0.1
Remote Host: localhost
Remote Port: 27017
Usage:
# Connect with mongo shell
mongo --host 127.0.0.1 --port 27017
# Or with MongoDB Compass
mongodb://127.0.0.1:27017
Advanced Scenarios
Chaining Through Jump Hosts
Scenario: Database is only accessible from app server
Network:
Your Computer
↓ SSH
Bastion/Jump Host
↓ SSH
App Server
↓ Internal
Database Server (192.168.10.50:3306)
Setup in Xermius:
- First tunnel (to app server):
Host: Bastion
Jump Host: (none)
- Second tunnel (to database):
Host: App Server
Jump Host: Bastion ← Use bastion as jump
Type: Local
Local Port: 3306
Remote Host: 192.168.10.50
Remote Port: 3306
Multiple Databases Different Ports
Scenario: Access 3 different databases
Setup:
MySQL:
Local: 3306 → Remote: localhost:3306
PostgreSQL:
Local: 5432 → Remote: db-postgres.internal:5432
MongoDB:
Local: 27017 → Remote: db-mongo.internal:27017
Usage:
# All at once!
mysql -h 127.0.0.1 -P 3306 -u user -p
psql -h 127.0.0.1 -p 5432 -U user db
mongo --host 127.0.0.1 --port 27017
Development Environment Access
Scenario: Team shares dev environment
Setup:
Dev DB:
Local: 3306 → Remote: dev-db.internal:3306
Dev Redis:
Local: 6379 → Remote: dev-cache.internal:6379
Dev API:
Local: 8080 → Remote: dev-api.internal:8080
Team Benefits:
- Everyone uses same dev environment
- No local setup needed
- Consistent data across team
- Easy to scale
Troubleshooting
Port Already in Use
Error: "Cannot bind to port 3306: Address already in use"
Causes:
- Local MySQL running
- Another tunnel using that port
- Other application using port
Solutions:
Option 1: Stop local service
# macOS
brew services stop mysql
# Linux
sudo systemctl stop mysql
# Windows
net stop MySQL80
Option 2: Use different local port
Local Port: 3307 instead of 3306
Connect to: localhost:3307
Option 3: Find and kill process
# macOS/Linux
lsof -ti:3306 | xargs kill -9
# Windows
netstat -ano | findstr :3306
taskkill /PID <pid> /F
Connection Refused
Error: "Connection refused by remote server"
Causes:
- Service not running on remote
- Remote host/port incorrect
- Firewall blocking on remote
- Service not listening on correct interface
Debug Steps:
1. SSH to server and test locally:
ssh user@server
# Test if service is running
telnet localhost 3306
# or
nc -zv localhost 3306
# Check if service is listening
sudo netstat -tlnp | grep 3306
# or
sudo lsof -i :3306
2. Check service status:
# MySQL
sudo systemctl status mysql
# PostgreSQL
sudo systemctl status postgresql
# Redis
sudo systemctl status redis
3. Check firewall:
# Ubuntu
sudo ufw status
# CentOS
sudo firewall-cmd --list-all
Slow Performance
Issue: Tunnel is very slow
Causes:
- High network latency
- Large data transfer
- CPU overhead from encryption
Solutions:
1. Enable compression:
In Xermius settings:
Connection → Enable compression
2. Check latency:
ping your-server-ip
3. Use faster cipher:
# In SSH config (~/.ssh/config)
Host yourserver
Ciphers aes128-gcm@openssh.com
4. Reduce data transfer:
- Query less data
- Use WHERE clauses
- Limit result sets
Tunnel Disconnects
Issue: Tunnel keeps dropping
Causes:
- Unstable network
- Server reboots
- Idle timeout
- Firewall rules
Solutions:
1. Enable keep-alive:
Xermius Settings:
Connection → Keep-alive interval: 30 seconds
2. Auto-reconnect:
Tunnel Settings:
[✓] Auto-reconnect on disconnect
3. Increase timeout:
# In SSH config
Host yourserver
ServerAliveInterval 30
ServerAliveCountMax 3
Permission Denied
Issue: Cannot start tunnel
Causes:
- Port < 1024 requires privileges
- Firewall blocking
- SELinux/AppArmor restrictions
Solutions:
1. Use port > 1024:
Change: Local Port: 8080 (not 80)
2. Run with elevated privileges:
# macOS/Linux (not recommended)
sudo xermius
# Better: Use higher port
3. Check firewall:
# macOS
System Preferences → Security → Firewall
# Windows
Control Panel → Windows Firewall
# Linux
sudo ufw allow 3306
Best Practices
1. Use Descriptive Names
✓ "Production MySQL - Read Only"
✓ "Staging PostgreSQL - Dev Team"
✓ "Redis Cache - Analytics"
✗ "Tunnel 1"
✗ "DB"
✗ "Forward"
2. Bind to Localhost
Always use 127.0.0.1 unless you specifically need network access:
✓ Bind: 127.0.0.1 ← Secure
✗ Bind: 0.0.0.0 ← Risky
3. Document Tunnels
Add descriptions:
Description:
Production MySQL (read-only user)
Used for reports and analytics
Contact: dba@example.com
Port: 3306 (default)
4. Use Standard Ports
When possible, match local and remote ports:
✓ Local 3306 → Remote 3306 (MySQL)
✓ Local 5432 → Remote 5432 (PostgreSQL)
Makes it easier to remember!
5. Auto-Start Important Tunnels
[✓] Auto-start with connection
Tunnel starts automatically when connecting to host
6. Monitor Usage
Check tunnel statistics:
- Connections count
- Data transferred
- Errors logged
- Uptime
7. Close When Not Needed
Don't leave tunnels running 24/7:
- Security risk
- Resource usage
- May mask issues
- Start when needed
8. Test Before Production Use
Always test tunnels:
- Create tunnel
- Start it
- Test connection
- Verify data access
- Check performance
Security Considerations
1. Least Privilege Access
Use read-only accounts when possible:
-- MySQL: Create read-only user
CREATE USER 'readonly'@'localhost'
IDENTIFIED BY 'password';
GRANT SELECT ON database.*
TO 'readonly'@'localhost';
2. Limit Network Access
Use 127.0.0.1, not 0.0.0.0:
✓ Only you can access
✗ Anyone on network can access
3. Use Strong SSH Keys
Protect tunnel authentication:
- 2048+ bit RSA or Ed25519
- Password-protected keys
- Key rotation policy
4. Audit Trail
Log tunnel usage:
- Who created tunnel
- When started/stopped
- Data accessed
- Errors encountered
5. Firewall Rules
On remote server:
# Only allow SSH, not direct DB access
sudo ufw deny 3306
sudo ufw allow 22
6. Temporary Access
Create tunnels for specific tasks:
- Start tunnel
- Complete work
- Stop tunnel
- Delete if one-time use
Performance Tips
1. Use Compression
For slow connections:
Xermius Settings → Connection
[✓] Enable compression
2. Optimize Queries
Reduce data transferred:
-- Bad: SELECT * FROM huge_table;
-- Good: SELECT id, name FROM huge_table LIMIT 100;
3. Local Caching
Cache frequently accessed data locally:
- Use local Redis for caching
- Replicate subset of data
- Reduce tunnel usage
4. Batch Operations
Group operations:
# Bad: Many small queries through tunnel
for id in ids:
query(f"SELECT * FROM table WHERE id={id}")
# Good: One query
query(f"SELECT * FROM table WHERE id IN ({','.join(ids)})")