Secure MySQL Access with SSH Tunnels: Complete Guide for Mac and Windows
Why SSH Tunnels Matter for Database Security
Exposing your MySQL database directly to the internet is a significant security risk. Even with strong passwords, open database ports attract automated attacks and brute-force attempts. Database servers on port 3306 are constantly scanned by malicious actors looking for vulnerabilities.
SSH tunneling (also called SSH port forwarding) provides a secure solution. Instead of opening your database port to the internet, you create an encrypted tunnel through SSH. Your database client connects to localhost, and SSH forwards that connection securely to your remote server. To attackers, your database port appears closed—because it is.
This guide shows you exactly how to set up SSH tunnels on both Mac and Windows, configure SSH keys for passwordless authentication, and keep your tunnels running reliably for ongoing development work.
Understanding SSH Tunnels and Port Forwarding
An SSH tunnel works by creating a secure, encrypted connection between your local machine and a remote server. Port forwarding maps a port on your local machine to a port on the remote server (or another server the remote host can access).
For MySQL access, the typical setup looks like this:
- Local port: 3307 (or any available port on your machine)
- Remote MySQL port: 3306 (standard MySQL port)
- SSH server: Your web server with SSH access
- MySQL server: Usually localhost on the remote server, but could be a separate database server
When you connect your MySQL client to localhost:3307, SSH automatically forwards that connection through the encrypted tunnel to remote-server:3306. Your database credentials never travel over an unencrypted connection.
Prerequisites
Before starting, ensure you have:
- SSH access to your web server (username, password, and server address)
- MySQL database credentials (username, password, database name)
- A MySQL client installed (MySQL Workbench, DBeaver, TablePlus, or command-line client)
- Administrative access on your local machine to generate SSH keys
For this guide, we'll use these example values (replace with your actual details):
- SSH server: web.example.com
- SSH username: username
- SSH port: 22 (default)
- Local port: 3307
- Remote MySQL port: 3306
Setting Up SSH Keys (Recommended)
SSH keys provide more secure authentication than passwords and enable passwordless connections. You'll generate a key pair: a private key that stays on your computer and a public key that you copy to the server.
Generating SSH Keys on Mac
Open Terminal and run:
ssh-keygen -t ed25519 -C "your_email@example.com"
When prompted for a file location, press Enter to accept the default (~/.ssh/id_ed25519).
For the passphrase, you have two options:
- With passphrase: More secure, but you'll need to enter it when using the key (can be stored in macOS Keychain)
- Without passphrase: Convenient for automated scripts, but less secure if your computer is compromised
For most development work, using a passphrase with Keychain storage offers the best balance.
Generating SSH Keys on Windows
Windows 10 and 11 include OpenSSH. Open PowerShell or Command Prompt and run:
ssh-keygen -t ed25519 -C "your_email@example.com"
Accept the default location (C:\Users\YourUsername\.ssh\id_ed25519) and choose whether to set a passphrase.
If you don't have OpenSSH, you can use PuTTYgen (comes with PuTTY):
- Download and install PuTTY from putty.org
- Open PuTTYgen
- Select "EdDSA" and curve "Ed25519"
- Click "Generate" and move your mouse to create randomness
- Add a comment (like your email) and optional passphrase
- Click "Save private key" to save as
id_ed25519.ppk - Copy the public key from the text box (you'll need this next)
Copying Your Public Key to the Server
On Mac:
ssh-copy-id username@web.example.com
Enter your password when prompted. The command automatically adds your public key to ~/.ssh/authorized_keys on the server.
On Windows with OpenSSH:
type %USERPROFILE%\.ssh\id_ed25519.pub | ssh username@web.example.com "cat >> .ssh/authorized_keys"
Manual method (works for all systems):
- Display your public key:
- Mac/Linux:
cat ~/.ssh/id_ed25519.pub - Windows:
type %USERPROFILE%\.ssh\id_ed25519.pub
- Mac/Linux:
- Copy the entire output (starts with
ssh-ed25519) - SSH into your server:
ssh username@web.example.com - Edit the authorized keys file:
nano ~/.ssh/authorized_keys - Paste your public key on a new line
- Save and exit (Ctrl+X, Y, Enter in nano)
- Set proper permissions:
chmod 600 ~/.ssh/authorized_keys
Testing SSH Key Authentication
Try connecting without specifying a password:
ssh username@web.example.com
If your SSH key is set up correctly, you'll connect without entering your password (or just your passphrase if you set one).
Creating SSH Tunnels on Mac
One-Time Tunnel via Terminal
Open Terminal and create a tunnel with this command:
ssh -L 3307:localhost:3306 username@web.example.com -N
Breaking down the command:
-L 3307:localhost:3306- Forward local port 3307 to remote localhost:3306username@web.example.com- SSH connection details-N- Don't execute remote commands (just forward ports)
The tunnel runs in the foreground. Keep this Terminal window open while using the database connection.
Background Tunnel
To run the tunnel in the background, add the -f flag:
ssh -f -L 3307:localhost:3306 username@web.example.com -N
The tunnel runs in the background. To stop it, find the process and kill it:
# Find the SSH tunnel process
ps aux | grep "ssh -f -L 3307"
# Kill it using the process ID
kill [PID]
Persistent Tunnel with Auto-Reconnect
For development work, you want the tunnel to reconnect automatically if it drops. Create a simple script:
#!/bin/bash
# Save as ~/bin/mysql-tunnel.sh
while true; do
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 3307:localhost:3306 username@web.example.com -N
echo "SSH tunnel disconnected. Reconnecting in 5 seconds..."
sleep 5
done
Make it executable and run it:
chmod +x ~/bin/mysql-tunnel.sh
~/bin/mysql-tunnel.sh
The ServerAliveInterval options keep the connection alive and detect when it fails.
Launch Agent for Always-On Tunnels (Advanced)
To start the tunnel automatically when you log in, create a Launch Agent:
nano ~/Library/LaunchAgents/com.user.mysqltunnel.plist
Add this content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.mysqltunnel</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/ssh</string>
<string>-N</string>
<string>-L</string>
<string>3307:localhost:3306</string>
<string>username@web.example.com</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/mysqltunnel.log</string>
<key>StandardErrorPath</key>
<string>/tmp/mysqltunnel.err</string>
</dict>
</plist>
Load the agent:
launchctl load ~/Library/LaunchAgents/com.user.mysqltunnel.plist
The tunnel now starts automatically at login and restarts if it fails.
Creating SSH Tunnels on Windows
Using Built-in OpenSSH (Windows 10/11)
Open PowerShell or Command Prompt and run:
ssh -L 3307:localhost:3306 username@web.example.com -N
This works identically to Mac. Keep the window open while using the tunnel.
For a background tunnel:
start /B ssh -f -L 3307:localhost:3306 username@web.example.com -N
Using PuTTY for SSH Tunnels
PuTTY is a popular SSH client for Windows with a graphical interface:
- Open PuTTY
- In "Session", enter your hostname:
web.example.com - In "Connection > Data", enter your username
- In "Connection > SSH > Auth", browse and select your private key (.ppk file)
- In "Connection > SSH > Tunnels":
- Source port:
3307 - Destination:
localhost:3306 - Click "Add"
- Source port:
- Return to "Session", enter a name under "Saved Sessions", and click "Save"
- Click "Open" to connect
The tunnel remains active while PuTTY is open. Minimize it to keep it running in the background.
Auto-Reconnecting Tunnel with PowerShell
Create a PowerShell script for persistent tunnels:
# Save as mysql-tunnel.ps1
while ($true) {
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 3307:localhost:3306 username@web.example.com -N
Write-Host "SSH tunnel disconnected. Reconnecting in 5 seconds..."
Start-Sleep -Seconds 5
}
Run it with:
powershell -ExecutionPolicy Bypass -File mysql-tunnel.ps1
Windows Task Scheduler for Always-On Tunnels
To start the tunnel automatically at login:
- Open Task Scheduler (search in Start menu)
- Click "Create Task" (not "Create Basic Task")
- General tab:
- Name: "MySQL SSH Tunnel"
- Select "Run whether user is logged on or not"
- Triggers tab:
- New trigger
- Begin: "At log on"
- Specific user: (your username)
- Actions tab:
- New action
- Program:
C:\Windows\System32\OpenSSH\ssh.exe - Arguments:
-N -L 3307:localhost:3306 username@web.example.com
- Conditions tab:
- Uncheck "Start only if on AC power"
- Settings tab:
- Check "If task fails, restart every: 1 minute"
Connecting to MySQL Through the Tunnel
Once your tunnel is running, configure your MySQL client to connect through it.
MySQL Workbench
- Open MySQL Workbench
- Click the "+" icon next to "MySQL Connections"
- Configure the connection:
- Connection Name: "Production DB via Tunnel"
- Hostname:
127.0.0.1 - Port:
3307(your local tunnel port) - Username: (your MySQL username)
- Password: Click "Store in Keychain" and enter your MySQL password
- Click "Test Connection"
- Click "OK" to save
Command-Line MySQL Client
mysql -h 127.0.0.1 -P 3307 -u your_mysql_username -p
Note the capital -P for port specification.
DBeaver
- Create a new MySQL connection
- Server Host:
localhost - Port:
3307 - Database: (your database name)
- Username and password: (your MySQL credentials)
- Test connection and save
Programming Language Examples
PHP:
<?php
$mysqli = new mysqli("127.0.0.1", "username", "password", "database", 3307);
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
?>
Python:
import pymysql
connection = pymysql.connect(
host='127.0.0.1',
port=3307,
user='username',
password='password',
database='database'
)
Node.js:
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: '127.0.0.1',
port: 3307,
user: 'username',
password: 'password',
database: 'database'
});
Advanced Tunnel Scenarios
Accessing Multiple Databases
You can create multiple tunnels using different local ports:
# Production database
ssh -f -L 3307:localhost:3306 username@prod.example.com -N
# Staging database
ssh -f -L 3308:localhost:3306 username@staging.example.com -N
Connect to production on localhost:3307 and staging on localhost:3308.
Database on Separate Server
If your MySQL server is on a different machine than your SSH server:
ssh -L 3307:db-server.internal:3306 username@web.example.com -N
This tunnels through web.example.com to reach db-server.internal:3306. The database server must be accessible from the SSH server.
Reverse Tunnels
In some scenarios, you might need a reverse tunnel (exposing your local database to the remote server):
ssh -R 3307:localhost:3306 username@web.example.com
The remote server can now connect to your local MySQL on localhost:3307.
Keeping Tunnels Running Smoothly
Connection Keep-Alive Settings
Add these settings to your SSH config file (~/.ssh/config on Mac/Linux, %USERPROFILE%\.ssh\config on Windows):
Host web.example.com
HostName web.example.com
User username
Port 22
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
LocalForward 3307 localhost:3306
With this configuration, you can simply run:
ssh -N web.example.com
The tunnel settings are automatically applied.
Server-Side Keep-Alive
On your server, edit /etc/ssh/sshd_config (requires root access):
ClientAliveInterval 60
ClientAliveCountMax 3
TCPKeepAlive yes
Restart SSH:
sudo systemctl restart sshd
This prevents the server from timing out idle connections.
Monitoring Tunnel Status
Check if your tunnel is active:
Mac/Linux:
# Check if port 3307 is listening
lsof -i :3307
# Check SSH processes with port forwarding
ps aux | grep "ssh.*3307"
Windows:
# Check listening ports
netstat -an | findstr :3307
# Check SSH processes
tasklist | findstr ssh.exe
Automated Health Checks
Create a script to verify the tunnel and restart if needed:
#!/bin/bash
# Save as check-tunnel.sh
# Test if local port is listening
if ! nc -z localhost 3307; then
echo "Tunnel is down. Restarting..."
pkill -f "ssh.*3307"
sleep 2
ssh -f -o ServerAliveInterval=60 -L 3307:localhost:3306 username@web.example.com -N
echo "Tunnel restarted"
else
echo "Tunnel is up"
fi
Add to crontab to run every 5 minutes:
*/5 * * * * /path/to/check-tunnel.sh
Troubleshooting Common Issues
Port Already in Use
Error: "bind: Address already in use"
Another process is using port 3307. Find and kill it:
# Mac/Linux
lsof -ti:3307 | xargs kill -9
# Windows
netstat -ano | findstr :3307
taskkill /PID [PID] /F
Connection Refused
Error: "Connection refused" when connecting to MySQL
Possible causes:
- Tunnel isn't running - verify with
lsof -i :3307ornetstat - MySQL isn't running on the remote server - SSH in and check:
sudo systemctl status mysql - MySQL is bound to 127.0.0.1 only (correct behavior for security)
Permission Denied (publickey)
Your SSH key isn't recognized. Verify:
- Public key is in
~/.ssh/authorized_keyson the server - File permissions are correct:
chmod 600 ~/.ssh/authorized_keys - You're using the correct private key:
ssh -i ~/.ssh/id_ed25519 username@web.example.com
Tunnel Drops Frequently
Add keep-alive settings (shown above) to both client and server. Also check:
- Network stability - unstable Wi-Fi can break SSH connections
- Firewall rules blocking idle connections
- NAT router timeout settings
Security Best Practices
Restrict SSH Key Usage
On the server, limit what the SSH key can do by adding restrictions to ~/.ssh/authorized_keys:
command="echo 'Only port forwarding allowed'",no-pty,no-X11-forwarding ssh-ed25519 AAAA...
This prevents the key from being used for shell access - only tunneling.
Use Non-Standard SSH Ports
If your server uses a non-standard SSH port (e.g., 2222):
ssh -p 2222 -L 3307:localhost:3306 username@web.example.com -N
Limit MySQL User Permissions
Create a MySQL user specifically for remote access with minimal permissions:
CREATE USER 'remote_dev'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON your_database.* TO 'remote_dev'@'localhost';
FLUSH PRIVILEGES;
Never use root credentials for tunneled connections.
Enable Two-Factor Authentication
For production servers, enable 2FA on SSH using Google Authenticator or similar tools. This adds an extra layer of security beyond SSH keys.
Alternative Tools and Solutions
Autossh for Reliable Tunnels
autossh monitors SSH connections and automatically restarts them if they fail:
# Mac
brew install autossh
# Linux
sudo apt-get install autossh
# Use autossh instead of ssh
autossh -M 0 -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" -L 3307:localhost:3306 username@web.example.com -N
Visual SSH Clients
Several GUI applications simplify SSH tunnel management:
- TablePlus: Database client with built-in SSH tunnel support (Mac/Windows)
- Termius: SSH client with saved tunnels and sync across devices
- SSH Tunnel Manager: Mac app for managing multiple tunnels
VPN as Alternative
For teams needing persistent access to multiple resources, a Virtual Private Cloud with VPN access might be more appropriate than individual SSH tunnels.
Conclusion
SSH tunnels provide robust, secure access to remote MySQL databases without exposing them to the internet. By following this guide, you've learned how to:
- Generate and configure SSH keys for passwordless authentication
- Create SSH tunnels on both Mac and Windows
- Configure persistent tunnels that auto-reconnect
- Connect various MySQL clients through tunnels
- Troubleshoot common issues
- Implement security best practices
SSH tunneling is an essential skill for developers and system administrators working with remote databases. It combines the security of SSH encryption with the convenience of local database access.
For businesses that need comprehensive remote access solutions, managed database hosting, or secure infrastructure, our managed hosting services include secure SSH access, database management, and expert support. Contact us to discuss how we can help secure your database infrastructure.
Whether you're a developer accessing staging databases, a data analyst pulling reports, or a system administrator managing production systems, SSH tunnels give you secure, reliable database access from anywhere.