Secure MySQL Access with SSH Tunnels: Complete Guide for Mac and Windows

November 4, 2025 Falcon Internet Team 17 views
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):

  1. Download and install PuTTY from putty.org
  2. Open PuTTYgen
  3. Select "EdDSA" and curve "Ed25519"
  4. Click "Generate" and move your mouse to create randomness
  5. Add a comment (like your email) and optional passphrase
  6. Click "Save private key" to save as id_ed25519.ppk
  7. 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):

  1. Display your public key:
    • Mac/Linux: cat ~/.ssh/id_ed25519.pub
    • Windows: type %USERPROFILE%\.ssh\id_ed25519.pub
  2. Copy the entire output (starts with ssh-ed25519)
  3. SSH into your server: ssh username@web.example.com
  4. Edit the authorized keys file: nano ~/.ssh/authorized_keys
  5. Paste your public key on a new line
  6. Save and exit (Ctrl+X, Y, Enter in nano)
  7. 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:3306
  • username@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:

  1. Open PuTTY
  2. In "Session", enter your hostname: web.example.com
  3. In "Connection > Data", enter your username
  4. In "Connection > SSH > Auth", browse and select your private key (.ppk file)
  5. In "Connection > SSH > Tunnels":
    • Source port: 3307
    • Destination: localhost:3306
    • Click "Add"
  6. Return to "Session", enter a name under "Saved Sessions", and click "Save"
  7. 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:

  1. Open Task Scheduler (search in Start menu)
  2. Click "Create Task" (not "Create Basic Task")
  3. General tab:
    • Name: "MySQL SSH Tunnel"
    • Select "Run whether user is logged on or not"
  4. Triggers tab:
    • New trigger
    • Begin: "At log on"
    • Specific user: (your username)
  5. Actions tab:
    • New action
    • Program: C:\Windows\System32\OpenSSH\ssh.exe
    • Arguments: -N -L 3307:localhost:3306 username@web.example.com
  6. Conditions tab:
    • Uncheck "Start only if on AC power"
  7. 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

  1. Open MySQL Workbench
  2. Click the "+" icon next to "MySQL Connections"
  3. 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
  4. Click "Test Connection"
  5. 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

  1. Create a new MySQL connection
  2. Server Host: localhost
  3. Port: 3307
  4. Database: (your database name)
  5. Username and password: (your MySQL credentials)
  6. 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 :3307 or netstat
  • 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_keys on 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.