51

I'm using MySqldb with Python 2.7 to allow Python to make connections to another MySQL server

import MySQLdb
db = MySQLdb.connect(host="sql.domain.com",
     user="dev", 
      passwd="*******", 
      db="appdb")

Instead of connecting normally like this, how can the connection be made through a SSH tunnel using SSH key pairs?

The SSH tunnel should ideally be opened by Python. The SSH tunnel host and the MySQL server are the same machine.

4
  • 1
    Did you google? Opening an ssh tunnel with python: stackoverflow.com/questions/4364355/… , connecting to MySql over said tunnel: stackoverflow.com/questions/3577555/… Commented Feb 20, 2014 at 9:32
  • You probably have good reason to use SSH, but if this is a direct connection to a MySQL server, start using SSL instead. Less things that can go wrong. Commented Feb 20, 2014 at 9:42
  • @geertjanvdk Thats interesting, why would SSL be the better choice? I'm looking to make secure connections between client and server and SSH was the first to come to mind Commented Feb 20, 2014 at 9:44
  • 1
    You don't use SSH to connect to a secure web site, do you? It would just complicate things. SSL is the way to go if your MySQL server is directly accessible. Also, SSL works from any connector or operating system like Windows. When using an SSH tunnel, you would need to keep it up, monitor it, etc.. Commented Feb 20, 2014 at 9:49

11 Answers 11

53

Only this worked for me

import pymysql
import paramiko
import pandas as pd
from paramiko import SSHClient
from sshtunnel import SSHTunnelForwarder
from os.path import expanduser

home = expanduser('~')
mypkey = paramiko.RSAKey.from_private_key_file(home + pkeyfilepath)
# if you want to use ssh password use - ssh_password='your ssh password', bellow

sql_hostname = 'sql_hostname'
sql_username = 'sql_username'
sql_password = 'sql_password'
sql_main_database = 'db_name'
sql_port = 3306
ssh_host = 'ssh_hostname'
ssh_user = 'ssh_username'
ssh_port = 22
sql_ip = '1.1.1.1.1'

with SSHTunnelForwarder(
        (ssh_host, ssh_port),
        ssh_username=ssh_user,
        ssh_pkey=mypkey,
        remote_bind_address=(sql_hostname, sql_port)) as tunnel:
    conn = pymysql.connect(host='127.0.0.1', user=sql_username,
            passwd=sql_password, db=sql_main_database,
            port=tunnel.local_bind_port)
    query = '''SELECT VERSION();'''
    data = pd.read_sql_query(query, conn)
    conn.close()
Sign up to request clarification or add additional context in comments.

7 Comments

What is the sql_ip used for?
@kathanshah how to use this if there'a jump host in between? & does it allow default .ssh/config file settings in place?
@OneAdamTwelve, sql_ip appears to be unnecessary and unused. But this example works! (I used MySQLdb)
Just a tiny add-on comment for anyone who experiences "xxx.com Host is not allowed to connect to this MySQL server" with this solution. You then need to add local_bind_address = ('127.0.0.1', sql_port) in the first part of the SSHTunnelForwarder(), e.g.: with SSHTunnelForwarder( (ssh_host, ssh_port), ssh_username=ssh_user, ssh_pkey=mypkey, remote_bind_address=('127.0.0.1', sql_port), local_bind_address = ('127.0.0.1', sql_port) ) as tunnel: Why? The default local address resolves to the external hostname that mysql can't parse.
As mentioned by @DimitriBolt If you use mysql.connector from Oracle you must use a construction cnx = mysql.connector.MySQLConnection(... Important: a construction cnx = mysql.connector.connect(... does not work via an SSh! It is a bug
|
31

I'm guessing you'll need port forwarding. I recommend sshtunnel.SSHTunnelForwarder

import mysql.connector
import sshtunnel

with sshtunnel.SSHTunnelForwarder(
        (_host, _ssh_port),
        ssh_username=_username,
        ssh_password=_password,
        remote_bind_address=(_remote_bind_address, _remote_mysql_port),
        local_bind_address=(_local_bind_address, _local_mysql_port)
) as tunnel:
    connection = mysql.connector.connect(
        user=_db_user,
        password=_db_password,
        host=_local_bind_address,
        database=_db_name,
        port=_local_mysql_port)
    ...

5 Comments

What am I suppose to put in remote_bind_address? Where can I find this information?
If you're in your local you can try something like this: _remote_bind_address = '127.0.0.1' _local_bind_address = '0.0.0.0'
What type of forwarding is this? Remote forwarding? Does need any server setup?
It doesn't need any server setup. Indeed it is a setup for port forwarding that enables applications on the server side of a Secure Shell (SSH) connection to be accessed through a SSH's tunnel.
@CarlosD. Any tips on how to package sshtunnel. I installed via pip, zipped, and imported to AWS lambda. I receive error when I import sshtunnel. No module named '_cffi_backend'. Having a hard time resolving this.
13
from sshtunnel import SSHTunnelForwarder
import pymysql
import pandas as pd

tunnel = SSHTunnelForwarder(('SSH_HOST', 22), ssh_password=SSH_PASS, ssh_username=SSH_UNAME,
     remote_bind_address=(DB_HOST, 3306)) 
tunnel.start()
conn = pymysql.connect(host='127.0.0.1', user=DB_UNAME, passwd=DB_PASS, port=tunnel.local_bind_port)
data = pd.read_sql_query("SHOW DATABASES;", conn)

credits to https://www.reddit.com/r/learnpython/comments/53wph1/connecting_to_a_mysql_database_in_a_python_script/

3 Comments

Approach with starting tunnel worked way better for me than these using 'with' statement. It somehow solved my problem: stackoverflow.com/questions/63774543/…
I don't know why, but here only worked using this way too. Using with statement seems not to work for me.
Using {with} initiates tunnel.close(), which for some reason hangs. Using tunnel.stop() and then tunnel.close() seems to circumvent the issue.
8

If your private key file is encrypted, this is what worked for me:

    mypkey = paramiko.RSAKey.from_private_key_file(<<file location>>, password='password')
    sql_hostname = 'sql_hostname'
    sql_username = 'sql_username'
    sql_password = 'sql_password'
    sql_main_database = 'sql_main_database'
    sql_port = 3306
    ssh_host = 'ssh_host'
    ssh_user = 'ssh_user'
    ssh_port = 22


    with SSHTunnelForwarder(
            (ssh_host, ssh_port),
            ssh_username=ssh_user,
            ssh_pkey=mypkey,
            ssh_password='ssh_password',
            remote_bind_address=(sql_hostname, sql_port)) as tunnel:
        conn = pymysql.connect(host='localhost', user=sql_username,
                               passwd=sql_password, db=sql_main_database,
                               port=tunnel.local_bind_port)
        query = '''SELECT VERSION();'''
        data = pd.read_sql_query(query, conn)
        print(data)
        conn.close()

Comments

6

You may only write the path to the private key file: ssh_pkey='/home/userName/.ssh/id_ed25519' (documentation is here: https://sshtunnel.readthedocs.io/en/latest/).

If you use mysql.connector from Oracle you must use a construction cnx = mysql.connector.MySQLConnection(... Important: a construction cnx = mysql.connector.connect(... does not work via an SSh! It is a bug. (The documentation is here: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html).

Also, your SQL statement must be ideal. In case of an error on SQL server side, you do not receive an error message from SQL-server.

import sshtunnel
import numpy as np

with sshtunnel.SSHTunnelForwarder(ssh_address_or_host='ssh_host',
                                  ssh_username="ssh_username",
                                  ssh_pkey='/home/userName/.ssh/id_ed25519',
                                  remote_bind_address=('localhost', 3306),
                                  ) as tunnel:
    cnx = mysql.connector.MySQLConnection(user='sql_username',
                                          password='sql_password',
                                          host='127.0.0.1',
                                          database='db_name',
                                          port=tunnel.local_bind_port)
    cursor = cnx.cursor()
    cursor.execute('SELECT * FROM db_name.tableName;')
    arr = np.array(cursor.fetchall())
    cursor.close()
    cnx.close()

1 Comment

Could you provide supporting reference on the bug? I had .connect() work in an earlier version but no longer, so this is a helpful clue.
2

Someone said this in another comment. If you use the python mysql.connector from Oracle then you must use a construction cnx = mysql.connector.MySQLConnection(....

Important: a construction cnx = mysql.connector.connect(... does not work via an SSH! This bug cost me a whole day trying to understand why connections were being dropped by the remote server:

with sshtunnel.SSHTunnelForwarder(
        (ssh_host,ssh_port),
        ssh_username=ssh_username,
        ssh_pkey=ssh_pkey,
        remote_bind_address=(sql_host, sql_port)) as tunnel:
    connection = mysql.connector.MySQLConnection(
        host='127.0.0.1',
        port=tunnel.local_bind_port,
        user=sql_username,
        password=sql_password)
    query = 'select version();'
    data = pd.read_sql_query(query, connection)
    print(data)
    connection.close()

2 Comments

This should be marked as the correct answer. Thanks Martin for explaining that mysql.connector doesn't work.
Just saved my day
2

If you are using python, and all the username, password, host and port are correct then there is just one thing left, that is using the argument (use_pure=True). This argument uses python to parse the details and password. You can see the doc of mysql.connector.connect() arguments.

with sshtunnel.SSHTunnelForwarder(
    (ssh_host,ssh_port),
    ssh_username=ssh_username,
    ssh_pkey=ssh_pkey,
    remote_bind_address=(sql_host, sql_port)) as tunnel:
connection = mysql.connector.MySQLConnection(
    host='127.0.0.1',
    port=tunnel.local_bind_port,
    user=sql_username,
    password=sql_password,
    use_pure='True')
query = 'select version();'
data = pd.read_sql_query(query, connection)
print(data)
connection. Close()

Comments

1

This works for me:

import mysql.connector
import sshtunnel
with sshtunnel.SSHTunnelForwarder(
    ('ip-of-ssh-server', 'port-in-number-format'),
    ssh_username = 'ssh-username',
    ssh_password = 'ssh-password',
    remote_bind_address = ('127.0.0.1', 3306)
) as tunnel:
    connection = mysql.connector.connect(
        user = 'database-username',
        password = 'database-password',
        host = '127.0.0.1',
        port = tunnel.local_bind_port,
        database = 'databasename',
    )
    mycursor = connection.cursor()
    query = "SELECT * FROM datos"
    mycursor.execute(query)

Comments

0

For everyone having issues with using SSHTunnelForwarder as a context manager, it's very likely you're letting that context end before using the database connection. This is how you can encapsulate both SSHTunnelForwarder and pymysql.connect in a new context manager:

(likewise for MySQLdb.connect, mysql.connector.connect or any other package following the Python Database API Specification)

from contextlib import contextmanager

import pymysql
from sshtunnel import SSHTunnelForwarder

@contextmanager
def connect():
    tunnel = SSHTunnelForwarder(
        ("ssh_host", 9922),
        ssh_password="ssh_password",
        ssh_username="ssh_username",
        remote_bind_address=("127.0.0.1", 3306),
    )
    tunnel.start()
    try:
        conn = pymysql.connect(
            host="127.0.0.1",
            user="user",
            password="password",
            database="database",
            port=tunnel.local_bind_port,
        )
        try:
            yield conn
        finally:
            conn.close()
    finally:
        tunnel.stop()

# ... then elsewhere you can do ...
with connect() as conn, conn.cursor() as cur:
    cur.execute("SELECT VERSION()")
    print(cur.fetchone())

Comments

-1

Paramiko is the best python module to do ssh tunneling. Check out the code here: https://github.com/paramiko/paramiko/blob/master/demos/forward.py

As said in comments this one works perfect. SSH Tunnel for Python MySQLdb connection

Comments

-1

Best practice is to parameterize the connection variables. Here is how I have implemented. Works like charm!

import mysql.connector
import sshtunnel
import pandas as pd
import configparser

config = configparser.ConfigParser()
config.read('c:/work/tmf/data_model/tools/config.ini')

ssh_host = config['db_qa01']['SSH_HOST']
ssh_port = int(config['db_qa01']['SSH_PORT'])
ssh_username = config['db_qa01']['SSH_USER']
ssh_pkey = config['db_qa01']['SSH_PKEY']
sql_host = config['db_qa01']['HOST']
sql_port = int(config['db_qa01']['PORT'])
sql_username = config['db_qa01']['USER']
sql_password = config['db_qa01']['PASSWORD']

with sshtunnel.SSHTunnelForwarder(
        (ssh_host,ssh_port),
        ssh_username=ssh_username,
        ssh_pkey=ssh_pkey,
        remote_bind_address=(sql_host, sql_port)) as tunnel:
    connection = mysql.connector.connect(
        host='127.0.0.1',
        port=tunnel.local_bind_port,
        user=sql_username,
        password=sql_password)
    query = 'select version();'
    data = pd.read_sql_query(query, connection)
    print(data)
    connection.close()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.