diff --git a/docs/en_US/images/server_advanced.png b/docs/en_US/images/server_advanced.png
old mode 100644
new mode 100755
index 10f1809a364579a39b2fe1911391568260b379b8..bb35d7b220ec6c647135d4cffdfc53235dabc859
GIT binary patch
diff --git a/docs/en_US/images/server_connection.png b/docs/en_US/images/server_connection.png
old mode 100644
new mode 100755
index dd18376e5884effefabf45fc1bf6332f5522a73e..813bbbfb511277cc10813939a5019f3d1fe9ab6a
GIT binary patch
diff --git a/docs/en_US/images/server_general.png b/docs/en_US/images/server_general.png
old mode 100644
new mode 100755
index 6e26bb31fa9b94bad27f83d4dd8318621308eaa7..f4847c550244e1b3fb0d2cbbca72b9470ba69443
GIT binary patch
diff --git a/docs/en_US/images/server_ssh_tunnel.png b/docs/en_US/images/server_ssh_tunnel.png
new file mode 100755
index 0000000000000000000000000000000000000000..f71069b30014095152718fc1bc71941ea787c358
GIT binary patch
diff --git a/docs/en_US/images/server_ssl.png b/docs/en_US/images/server_ssl.png
old mode 100644
new mode 100755
index 0b9cbcb6b9ac6d7751371fadcfeca91cd25b1eb6..c0bcb95c5cd12e919ce926c34b5d7bde736832d5
GIT binary patch
diff --git a/docs/en_US/server_dialog.rst b/docs/en_US/server_dialog.rst
index b1159c5..448afb7 100644
--- a/docs/en_US/server_dialog.rst
+++ b/docs/en_US/server_dialog.rst
@@ -53,6 +53,28 @@ If pgAdmin is installed in Server mode (the default mode), you can use the platf
 
 *WARNING:* In Server mode, certificates, private keys, and the revocation list are stored in the per-user file storage area on the server, which is owned by the user account under which the pgAdmin server process is run. This means that administrators of the server may be able to access those files; appropriate caution should be taken before choosing to use this feature.
 
+Click the *SSH Tunnel* tab to continue.
+
+.. image:: images/server_ssh_tunnel.png
+    :alt: Server dialog ssh tunnel tab
+
+Use the fields in the *SSH Tunnel* tab to configure SSH Tunnel:
+
+You can use the "SSH Tunnel" tab to connect pgAdmin (through an intermediary
+proxy host) to a server that resides on a network to which the client may
+not be able to connect directly.
+
+*  When "Use SSH tunneling" is set to *Yes* specify that pgAdmin should use an SSH tunnel when connecting to the specified server.
+*  Specify the name or IP address of the SSH host (through which client connections will be forwarded) in the *Tunnel host* field.
+*  Specify the port of the SSH host (through which client connections will be forwarded) in the *Tunnel port* field.
+*  Specify the name of a user with connection privileges for the SSH host in the *Username* field.
+*  Specify the type of authentication that will be used when connecting to the SSH host in the *Authentication* field.
+
+   *  Select the *Password* to specify that pgAdmin will use a password for authentication by the SSH host. This is the default.
+   *  Select the *Identity file* to specify that pgAdmin will use a private key file when connecting.
+* If the SSH host is expecting a private key file for authentication, use the *Identity file* field to specify the location of the key file.
+* If the SSH host is expecting a password, use the *Password/Passphrase* field to specify a the password, or if an identity file is being used, specify the passphrase.
+
 Click the *Advanced* tab to continue.
 
 .. image:: images/server_advanced.png
diff --git a/requirements.txt b/requirements.txt
index 9b6b228..38646fb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -38,6 +38,7 @@ htmlmin==0.1.12;                python_version >= '2.7'
 Flask-HTMLmin==1.3.2;           python_version >= '2.7'
 SQLAlchemy>=1.2.5;              python_version >= '2.7'
 Flask-Security>=3.0.0;          python_version >= '2.7'
+sshtunnel>=0.1.3;               python_version >= '2.7'
 
 ###############################################################
 # Modules specifically required for Python2.7 or lesser version
diff --git a/web/config.py b/web/config.py
index 3d8f6f7..6143146 100644
--- a/web/config.py
+++ b/web/config.py
@@ -378,3 +378,13 @@ try:
     from config_local import *
 except ImportError:
     pass
+
+##########################################################################
+# SSH Tunneling supports only for Python 2.7 and 3.4+
+##########################################################################
+SUPPORT_SSH_TUNNEL = False
+if (
+    (sys.version_info[0] == 2 and sys.version_info[1] >= 7) or
+    (sys.version_info[0] == 3 and sys.version_info[1] >= 4)
+):
+    SUPPORT_SSH_TUNNEL = True
diff --git a/web/migrations/versions/a68b374fe373_.py b/web/migrations/versions/a68b374fe373_.py
new file mode 100644
index 0000000..5e94bb1
--- /dev/null
+++ b/web/migrations/versions/a68b374fe373_.py
@@ -0,0 +1,41 @@
+
+"""Added columns for SSH tunneling
+
+Revision ID: a68b374fe373
+Revises: 50aad68f99c2
+Create Date: 2018-04-05 13:59:57.588355
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from pgadmin.model import db
+
+# revision identifiers, used by Alembic.
+revision = 'a68b374fe373'
+down_revision = '50aad68f99c2'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    db.engine.execute(
+        'ALTER TABLE server ADD COLUMN use_ssh_tunnel INTEGER DEFAULT 0'
+    )
+    db.engine.execute(
+        'ALTER TABLE server ADD COLUMN tunnel_host TEXT'
+    )
+    db.engine.execute(
+        'ALTER TABLE server ADD COLUMN tunnel_port TEXT'
+    )
+    db.engine.execute(
+        'ALTER TABLE server ADD COLUMN tunnel_username TEXT'
+    )
+    db.engine.execute(
+        'ALTER TABLE server ADD COLUMN tunnel_authentication INTEGER DEFAULT 0'
+    )
+    db.engine.execute(
+        'ALTER TABLE server ADD COLUMN tunnel_identity_file TEXT'
+    )
+
+def downgrade():
+    pass
diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py
index 9df9dc9..3741d9d 100644
--- a/web/pgadmin/browser/__init__.py
+++ b/web/pgadmin/browser/__init__.py
@@ -617,7 +617,8 @@ def utils():
             editor_insert_pair_brackets=insert_pair_brackets,
             editor_indent_with_tabs=editor_indent_with_tabs,
             app_name=config.APP_NAME,
-            pg_libpq_version=pg_libpq_version
+            pg_libpq_version=pg_libpq_version,
+            support_ssh_tunnel=config.SUPPORT_SSH_TUNNEL
         ),
         200, {'Content-Type': 'application/x-javascript'})
 
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py
index 01c72e5..625d8da 100644
--- a/web/pgadmin/browser/server_groups/servers/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/__init__.py
@@ -479,7 +479,13 @@ class ServerNode(PGChildNodeView):
             'sslcompression': 'sslcompression',
             'bgcolor': 'bgcolor',
             'fgcolor': 'fgcolor',
-            'service': 'service'
+            'service': 'service',
+            'use_ssh_tunnel': 'use_ssh_tunnel',
+            'tunnel_host': 'tunnel_host',
+            'tunnel_port': 'tunnel_port',
+            'tunnel_username': 'tunnel_username',
+            'tunnel_authentication': 'tunnel_authentication',
+            'tunnel_identity_file': 'tunnel_identity_file',
         }
 
         disp_lbl = {
@@ -665,7 +671,19 @@ class ServerNode(PGChildNodeView):
                 'sslcrl': server.sslcrl if is_ssl else None,
                 'sslcompression': True if is_ssl and server.sslcompression
                 else False,
-                'service': server.service if server.service else None
+                'service': server.service if server.service else None,
+                'use_ssh_tunnel': server.use_ssh_tunnel
+                if server.use_ssh_tunnel else 0,
+                'tunnel_host': server.tunnel_host if server.tunnel_host
+                else None,
+                'tunnel_port': server.tunnel_port if server.tunnel_port
+                else 22,
+                'tunnel_username': server.tunnel_username
+                if server.tunnel_username else None,
+                'tunnel_identity_file': server.tunnel_identity_file
+                if server.tunnel_identity_file else None,
+                'tunnel_authentication': server.tunnel_authentication
+                if server.tunnel_authentication else 0
             }
         )
 
@@ -736,7 +754,13 @@ class ServerNode(PGChildNodeView):
                 sslcompression=1 if is_ssl and data['sslcompression'] else 0,
                 bgcolor=data.get('bgcolor', None),
                 fgcolor=data.get('fgcolor', None),
-                service=data.get('service', None)
+                service=data.get('service', None),
+                use_ssh_tunnel=data.get('use_ssh_tunnel', 0),
+                tunnel_host=data.get('tunnel_host', None),
+                tunnel_port=data.get('tunnel_port', 22),
+                tunnel_username=data.get('tunnel_username', None),
+                tunnel_authentication=data.get('tunnel_authentication', 0),
+                tunnel_identity_file=data.get('tunnel_identity_file', None)
             )
             db.session.add(server)
             db.session.commit()
@@ -754,6 +778,7 @@ class ServerNode(PGChildNodeView):
                 have_password = False
                 password = None
                 passfile = None
+                tunnel_password = None
                 if 'password' in data and data["password"] != '':
                     # login with password
                     have_password = True
@@ -764,9 +789,15 @@ class ServerNode(PGChildNodeView):
                     setattr(server, 'passfile', passfile)
                     db.session.commit()
 
+                if 'tunnel_password' in data and data["tunnel_password"] != '':
+                    tunnel_password = data['tunnel_password']
+                    tunnel_password = \
+                        encrypt(tunnel_password, current_user.password)
+
                 status, errmsg = conn.connect(
                     password=password,
                     passfile=passfile,
+                    tunnel_password=tunnel_password,
                     server_types=ServerType.types()
                 )
                 if hasattr(str, 'decode') and errmsg is not None:
@@ -877,10 +908,11 @@ class ServerNode(PGChildNodeView):
         res = conn.connected()
 
         if res:
-            from pgadmin.utils.exception import ConnectionLost
+            from pgadmin.utils.exception import ConnectionLost, \
+                SSHTunnelConnectionLost
             try:
                 conn.execute_scalar('SELECT 1')
-            except ConnectionLost:
+            except (ConnectionLost, SSHTunnelConnectionLost):
                 res = False
 
         return make_json_response(data={'connected': res})
@@ -924,28 +956,37 @@ class ServerNode(PGChildNodeView):
 
         password = None
         passfile = None
+        tunnel_password = None
         save_password = False
 
         # Connect the Server
         manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
         conn = manager.connection()
 
+        # If server using SSH Tunnel
+        if server.use_ssh_tunnel:
+            if 'tunnel_password' not in data:
+                return self.get_response_for_password(server, 428)
+            else:
+                tunnel_password = data['tunnel_password'] if 'tunnel_password'\
+                                                             in data else None
+                # Encrypt the password before saving with user's login
+                # password key.
+                try:
+                    tunnel_password = encrypt(tunnel_password, user.password) \
+                        if tunnel_password is not None else \
+                        server.tunnel_password
+                except Exception as e:
+                    current_app.logger.exception(e)
+                    return internal_server_error(errormsg=e.message)
+
         if 'password' not in data:
             conn_passwd = getattr(conn, 'password', None)
             if conn_passwd is None and server.password is None and \
                     server.passfile is None and server.service is None:
                 # Return the password template in case password is not
                 # provided, or password has not been saved earlier.
-                return make_json_response(
-                    success=0,
-                    status=428,
-                    result=render_template(
-                        'servers/password.html',
-                        server_label=server.name,
-                        username=server.username,
-                        _=gettext
-                    )
-                )
+                return self.get_response_for_password(server, 428)
             elif server.passfile and server.passfile != '':
                 passfile = server.passfile
             else:
@@ -969,22 +1010,13 @@ class ServerNode(PGChildNodeView):
             status, errmsg = conn.connect(
                 password=password,
                 passfile=passfile,
+                tunnel_password=tunnel_password,
                 server_types=ServerType.types()
             )
         except Exception as e:
             current_app.logger.exception(e)
-
-            return make_json_response(
-                success=0,
-                status=401,
-                result=render_template(
-                    'servers/password.html',
-                    server_label=server.name,
-                    username=server.username,
-                    errmsg=getattr(e, 'message', str(e)),
-                    _=gettext
-                )
-            )
+            return self.get_response_for_password(
+                server, 401, getattr(e, 'message', str(e)))
 
         if not status:
             if hasattr(str, 'decode'):
@@ -995,17 +1027,7 @@ class ServerNode(PGChildNodeView):
                 .format(server.id, server.name, errmsg)
             )
 
-            return make_json_response(
-                success=0,
-                status=401,
-                result=render_template(
-                    'servers/password.html',
-                    server_label=server.name,
-                    username=server.username,
-                    errmsg=errmsg,
-                    _=gettext
-                )
-            )
+            return self.get_response_for_password(server, 401, errmsg)
         else:
             if save_password and config.ALLOW_SAVE_PASSWORD:
                 try:
@@ -1376,5 +1398,34 @@ class ServerNode(PGChildNodeView):
             )
             return internal_server_error(errormsg=str(e))
 
+    def get_response_for_password(self, server, status, errmsg=None):
+        if server.use_ssh_tunnel:
+            return make_json_response(
+                success=0,
+                status=status,
+                result=render_template(
+                    'servers/tunnel_password.html',
+                    server_label=server.name,
+                    username=server.username,
+                    tunnel_username=server.tunnel_username,
+                    tunnel_host=server.tunnel_host,
+                    tunnel_identity_file=server.tunnel_identity_file,
+                    errmsg=errmsg,
+                    _=gettext
+                )
+            )
+        else:
+            return make_json_response(
+                success=0,
+                status=status,
+                result=render_template(
+                    'servers/password.html',
+                    server_label=server.name,
+                    username=server.username,
+                    errmsg=errmsg,
+                    _=gettext
+                )
+            )
+
 
 ServerNode.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js
index 375b0b6..9bee2e6 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/server.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js
@@ -669,6 +669,13 @@ define('pgadmin.node.server', [
           sslrootcert: undefined,
           sslcrl: undefined,
           service: undefined,
+          use_ssh_tunnel: 0,
+          tunnel_host: undefined,
+          tunnel_port: 22,
+          tunnel_username: undefined,
+          tunnel_identity_file: undefined,
+          tunnel_password: undefined,
+          tunnel_authentication: 0,
         },
         // Default values!
         initialize: function(attrs, args) {
@@ -695,8 +702,7 @@ define('pgadmin.node.server', [
         },{
           id: 'connected', label: gettext('Connected?'), type: 'switch',
           mode: ['properties'], group: gettext('Connection'), 'options': {
-            'onText':  gettext('True'), 'offText':  gettext('False'), 'onColor':  'success',
-            'offColor': 'danger', 'size': 'small',
+            'onText':  gettext('True'), 'offText':  gettext('False'), 'size': 'small',
           },
         },{
           id: 'version', label: gettext('Version'), type: 'text', group: null,
@@ -735,11 +741,29 @@ define('pgadmin.node.server', [
         },{
           id: 'save_password', controlLabel: gettext('Save password?'),
           type: 'checkbox', group: gettext('Connection'), mode: ['create'],
-          deps: ['connect_now'], visible: function(m) {
+          deps: ['connect_now', 'use_ssh_tunnel'], visible: function(m) {
             return m.get('connect_now') && m.isNew();
           },
-          disabled: function() {
-            return !current_user.allow_save_password;
+          disabled: function(m) {
+            if (!current_user.allow_save_password)
+              return true;
+
+            if (m.get('use_ssh_tunnel')) {
+              if (m.get('save_password')) {
+                Alertify.alert(
+                  gettext('Stored Password'),
+                  gettext('Database passwords cannot be stored when using SSH tunnelling. The \'Save password\' option has been turned off.')
+                );
+              }
+
+              setTimeout(function() {
+                m.set('save_password', false);
+              }, 10);
+
+              return true;
+            }
+
+            return false;
           },
         },{
           id: 'role', label: gettext('Role'), type: 'text', group: gettext('Connection'),
@@ -782,8 +806,7 @@ define('pgadmin.node.server', [
         },{
           id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
           mode: ['edit', 'create'], group: gettext('SSL'),
-          'options': { 'onText':   gettext('True'), 'offText':  gettext('False'),
-            'onColor':  'success', 'offColor': 'danger', 'size': 'small'},
+          'options': {'size': 'small'},
           deps: ['sslmode'], disabled: 'isSSL',
         },{
           id: 'sslcert', label: gettext('Client certificate'), type: 'text',
@@ -820,13 +843,77 @@ define('pgadmin.node.server', [
         },{
           id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
           mode: ['properties'], group: gettext('SSL'),
-          'options': { 'onText':  gettext('True'), 'offText':  gettext('False'),
-            'onColor':  'success', 'offColor': 'danger', 'size': 'small'},
+          'options': {'size': 'small'},
           deps: ['sslmode'], visible: function(m) {
             var sslmode = m.get('sslmode');
             return _.indexOf(SSL_MODES, sslmode) != -1;
           },
         },{
+          id: 'use_ssh_tunnel', label: gettext('Use SSH tunneling'), type: 'switch',
+          mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
+          'options': {'size': 'small'},
+          disabled: function(m) {
+            if (!pgAdmin.Browser.utils.support_ssh_tunnel) {
+              setTimeout(function() {
+                m.set('use_ssh_tunnel', 0);
+              }, 10);
+
+              return true;
+            }
+
+            return m.get('connected');
+          },
+        },{
+          id: 'tunnel_host', label: gettext('Tunnel host'), type: 'text', group: gettext('SSH Tunnel'),
+          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
+          disabled: function(m) {
+            return !m.get('use_ssh_tunnel');
+          },
+        },{
+          id: 'tunnel_port', label: gettext('Tunnel port'), type: 'int', group: gettext('SSH Tunnel'),
+          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'], max: 65535,
+          disabled: function(m) {
+            return !m.get('use_ssh_tunnel');
+          },
+        },{
+          id: 'tunnel_username', label: gettext('Username'), type: 'text', group: gettext('SSH Tunnel'),
+          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
+          disabled: function(m) {
+            return !m.get('use_ssh_tunnel');
+          },
+        },{
+          id: 'tunnel_authentication', label: gettext('Authentication'), type: 'switch',
+          mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
+          'options': {'onText':  gettext('Identity file'),
+            'offText':  gettext('Password'), 'size': 'small'},
+          deps: ['use_ssh_tunnel'],
+          disabled: function(m) {
+            return !m.get('use_ssh_tunnel');
+          },
+        }, {
+          id: 'tunnel_identity_file', label: gettext('Identity file'), type: 'text',
+          group: gettext('SSH Tunnel'), mode: ['edit', 'create'],
+          control: Backform.FileControl, dialog_type: 'select_file', supp_types: ['*'],
+          deps: ['tunnel_authentication', 'use_ssh_tunnel'],
+          disabled: function(m) {
+            if (!m.get('tunnel_authentication') || !m.get('use_ssh_tunnel')) {
+              setTimeout(function() {
+                m.set('tunnel_identity_file', '');
+              }, 10);
+            }
+            return !m.get('tunnel_authentication');
+          },
+        },{
+          id: 'tunnel_identity_file', label: gettext('Identity file'), type: 'text',
+          group: gettext('SSH Tunnel'), mode: ['properties'],
+        },{
+          id: 'tunnel_password', label: gettext('Password/Passphrase'), type: 'password',
+          group: gettext('SSH Tunnel'), control: 'input', mode: ['create'],
+          deps: ['use_ssh_tunnel'],
+          disabled: function(m) {
+            return !m.get('use_ssh_tunnel');
+          },
+        }, {
           id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
           mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
         },{
diff --git a/web/pgadmin/browser/server_groups/servers/templates/servers/tunnel_password.html b/web/pgadmin/browser/server_groups/servers/templates/servers/tunnel_password.html
new file mode 100644
index 0000000..ed0b68b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/templates/servers/tunnel_password.html
@@ -0,0 +1,28 @@
+<form name="frmPassword" id="frmPassword" style="height: 100%; width: 100%" onsubmit="return false;">
+    <div>{% if errmsg %}
+        <div class="highlight has-error">
+            <div class='control-label'>{{ errmsg }}</div>
+        </div>
+        {% endif %}
+        {% if tunnel_identity_file %}
+        <div><b>{{ _('SSH Tunnel password for the identity file \'{0}\' to connect the server "{1}"').format(tunnel_identity_file, tunnel_host) }}</b></div>
+        {% else %}
+        <div><b>{{ _('SSH Tunnel password for the user \'{0}\' to connect the server "{1}"').format(tunnel_username, tunnel_host) }}</b></div>
+        {% endif %}
+        <div style="padding: 5px; height: 1px;"></div>
+        <div style="width: 100%">
+            <span style="width: 97%;display: inline-block;">
+                <input style="width:100%" id="tunnel_password" class="form-control" name="tunnel_password" type="password">
+            </span>
+        </div>
+        <div style="padding: 5px; height: 1px;"></div>
+        <div><b>{{ _('Database server password for the user \'{0}\' to connect the server "{1}"').format(username, server_label) }}</b></div>
+        <div style="padding: 5px; height: 1px;"></div>
+        <div style="width: 100%">
+            <span style="width: 97%;display: inline-block;">
+                <input style="width:100%" id="password" class="form-control" name="password" type="password">
+            </span>
+        </div>
+        <div style="padding: 5px; height: 1px;"></div>
+    </div>
+</form>
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py
new file mode 100644
index 0000000..cca9322
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py
@@ -0,0 +1,62 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+
+
+class ServersWithSSHTunnelAddTestCase(BaseTestGenerator):
+    """ This class will add the servers under default server group. """
+
+    scenarios = [
+        (
+            'Add server using SSH tunnel with password', dict(
+                url='/browser/server/obj/',
+                with_password=True
+            )
+        ),
+        (
+            'Add server using SSH tunnel with identity file', dict(
+                url='/browser/server/obj/',
+                with_password=False
+            )
+        ),
+    ]
+
+    def setUp(self):
+        pass
+
+    def runTest(self):
+        """ This function will add the server under default server group."""
+        url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
+        # Add service name in the config
+        self.server['use_ssh_tunnel'] = 1
+        self.server['tunnel_host'] = '127.0.0.1'
+        self.server['tunnel_port'] = 22
+        self.server['tunnel_username'] = 'user'
+        if self.with_password:
+            self.server['tunnel_authentication'] = 0
+        else:
+            self.server['tunnel_authentication'] = 1
+            self.server['tunnel_identity_file'] = 'pkey_rsa'
+
+        response = self.tester.post(
+            url,
+            data=json.dumps(self.server),
+            content_type='html/json'
+        )
+        self.assertEquals(response.status_code, 200)
+        response_data = json.loads(response.data.decode('utf-8'))
+        self.server_id = response_data['node']['_id']
+
+    def tearDown(self):
+        """This function delete the server from SQLite """
+        utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/templates/browser/js/utils.js b/web/pgadmin/browser/templates/browser/js/utils.js
index be3b712..79cf69a 100644
--- a/web/pgadmin/browser/templates/browser/js/utils.js
+++ b/web/pgadmin/browser/templates/browser/js/utils.js
@@ -26,6 +26,7 @@ define('pgadmin.browser.utils',
     is_indent_with_tabs: '{{ editor_indent_with_tabs }}' == 'True',
     app_name: '{{ app_name }}',
     pg_libpq_version: {{pg_libpq_version|e}},
+    support_ssh_tunnel: '{{ support_ssh_tunnel }}' == 'True',
 
     counter: {total: 0, loaded: 0},
     registerScripts: function (ctx) {
diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py
index 11bc9f0..c3171ce 100644
--- a/web/pgadmin/model/__init__.py
+++ b/web/pgadmin/model/__init__.py
@@ -145,6 +145,24 @@ class Server(db.Model):
     bgcolor = db.Column(db.Text(10), nullable=True)
     fgcolor = db.Column(db.Text(10), nullable=True)
     service = db.Column(db.Text(), nullable=True)
+    use_ssh_tunnel = db.Column(
+        db.Integer(),
+        db.CheckConstraint('use_ssh_tunnel >= 0 AND use_ssh_tunnel <= 1'),
+        nullable=False
+    )
+    tunnel_host = db.Column(db.String(128), nullable=True)
+    tunnel_port = db.Column(
+        db.Integer(),
+        db.CheckConstraint('port <= 65534'),
+        nullable=True)
+    tunnel_username = db.Column(db.String(64), nullable=True)
+    tunnel_authentication = db.Column(
+        db.Integer(),
+        db.CheckConstraint('tunnel_authentication >= 0 AND '
+                           'tunnel_authentication <= 1'),
+        nullable=False
+    )
+    tunnel_identity_file = db.Column(db.String(64), nullable=True)
 
 
 class ModulePreference(db.Model):
diff --git a/web/pgadmin/static/js/browser/server_groups/servers/model_validation.js b/web/pgadmin/static/js/browser/server_groups/servers/model_validation.js
index f1627aa..c790eeb 100644
--- a/web/pgadmin/static/js/browser/server_groups/servers/model_validation.js
+++ b/web/pgadmin/static/js/browser/server_groups/servers/model_validation.js
@@ -43,6 +43,15 @@ export class ModelValidation {
     this.checkForEmpty('username', gettext('Username must be specified.'));
     this.checkForEmpty('port', gettext('Port must be specified.'));
 
+    if (this.model.get('use_ssh_tunnel')) {
+      this.checkForEmpty('tunnel_host', gettext('SSH Tunnel host must be specified.'));
+      this.checkForEmpty('tunnel_port', gettext('SSH Tunnel port must be specified.'));
+      this.checkForEmpty('tunnel_username', gettext('SSH Tunnel username must be specified.'));
+      if (this.model.get('tunnel_authentication')) {
+        this.checkForEmpty('tunnel_identity_file', gettext('SSH Tunnel identity file must be specified.'));
+      }
+    }
+
     this.model.errorModel.set(this.err);
 
     if (_.size(this.err)) {
diff --git a/web/pgadmin/tools/datagrid/__init__.py b/web/pgadmin/tools/datagrid/__init__.py
index 9b18804..d3c3bf9 100644
--- a/web/pgadmin/tools/datagrid/__init__.py
+++ b/web/pgadmin/tools/datagrid/__init__.py
@@ -27,7 +27,7 @@ from config import PG_DEFAULT_DRIVER
 from pgadmin.utils.preferences import Preferences
 from pgadmin.model import Server
 from pgadmin.utils.driver import get_driver
-from pgadmin.utils.exception import ConnectionLost
+from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
 from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
     get_query_tool_keyboard_shortcuts, get_text_representation_of_shortcut
 
@@ -135,7 +135,7 @@ def initialize_datagrid(cmd_type, obj_type, sgid, sid, did, obj_id):
                                   auto_reconnect=False,
                                   use_binary_placeholder=True,
                                   array_to_string=True)
-    except ConnectionLost as e:
+    except (ConnectionLost, SSHTunnelConnectionLost) as e:
         raise
     except Exception as e:
         app.logger.error(e)
@@ -363,7 +363,7 @@ def initialize_query_tool(sgid, sid, did=None):
                                   array_to_string=True)
         if connect:
             conn.connect()
-    except ConnectionLost as e:
+    except (ConnectionLost, SSHTunnelConnectionLost) as e:
         raise
     except Exception as e:
         app.logger.error(e)
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index fae527c..c72505a 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -34,7 +34,7 @@ from pgadmin.utils.ajax import make_json_response, bad_request, \
     success_return, internal_server_error, unauthorized
 from pgadmin.utils.driver import get_driver
 from pgadmin.utils.menu import MenuItem
-from pgadmin.utils.exception import ConnectionLost
+from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
 from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete
 from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
     RegisterQueryToolPreferences
@@ -166,7 +166,7 @@ def check_transaction_status(trans_id):
             use_binary_placeholder=True,
             array_to_string=True
         )
-    except ConnectionLost as e:
+    except (ConnectionLost, SSHTunnelConnectionLost) as e:
         raise
     except Exception as e:
         current_app.logger.error(e)
@@ -212,7 +212,7 @@ def start_view_data(trans_id):
         manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
             trans_obj.sid)
         default_conn = manager.connection(did=trans_obj.did)
-    except ConnectionLost as e:
+    except (ConnectionLost, SSHTunnelConnectionLost) as e:
         raise
     except Exception as e:
         current_app.logger.error(e)
@@ -261,7 +261,7 @@ def start_view_data(trans_id):
         # Execute sql asynchronously
         try:
             status, result = conn.execute_async(sql)
-        except ConnectionLost as e:
+        except (ConnectionLost, SSHTunnelConnectionLost) as e:
             raise
     else:
         status = False
diff --git a/web/pgadmin/tools/sqleditor/utils/start_running_query.py b/web/pgadmin/tools/sqleditor/utils/start_running_query.py
index 6c09067..8c598ff 100644
--- a/web/pgadmin/tools/sqleditor/utils/start_running_query.py
+++ b/web/pgadmin/tools/sqleditor/utils/start_running_query.py
@@ -25,7 +25,7 @@ from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \
     update_session_grid_transaction
 from pgadmin.utils.ajax import make_json_response, internal_server_error
 from pgadmin.utils.driver import get_driver
-from pgadmin.utils.exception import ConnectionLost
+from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
 
 
 class StartRunningQuery:
@@ -61,7 +61,7 @@ class StartRunningQuery:
                                           auto_reconnect=False,
                                           use_binary_placeholder=True,
                                           array_to_string=True)
-            except ConnectionLost:
+            except (ConnectionLost, SSHTunnelConnectionLost):
                 raise
             except Exception as e:
                 self.logger.error(e)
@@ -127,7 +127,7 @@ class StartRunningQuery:
         # and formatted_error is True.
         try:
             status, result = conn.execute_async(sql)
-        except ConnectionLost:
+        except (ConnectionLost, SSHTunnelConnectionLost):
             raise
 
         # If the transaction aborted for some reason and
diff --git a/web/pgadmin/utils/driver/psycopg2/connection.py b/web/pgadmin/utils/driver/psycopg2/connection.py
index 8a05ba8..798b528 100644
--- a/web/pgadmin/utils/driver/psycopg2/connection.py
+++ b/web/pgadmin/utils/driver/psycopg2/connection.py
@@ -215,6 +215,18 @@ class Connection(BaseConnection):
 
         encpass = kwargs['password'] if 'password' in kwargs else None
         passfile = kwargs['passfile'] if 'passfile' in kwargs else None
+        tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \
+                                                       kwargs else None
+
+        # Check SSH Tunnel needs to be created
+        if mgr.use_ssh_tunnel == 1 and tunnel_password is not None:
+            status, error = mgr.create_ssh_tunnel(tunnel_password)
+            if not status:
+                return False, error
+
+        # Check SSH Tunnel is alive or not.
+        if mgr.use_ssh_tunnel == 1:
+            mgr.check_ssh_tunnel_alive()
 
         if encpass is None:
             encpass = self.password or getattr(mgr, 'password', None)
@@ -240,6 +252,7 @@ class Connection(BaseConnection):
                     password = password.decode()
 
             except Exception as e:
+                mgr.stop_ssh_tunnel()
                 current_app.logger.exception(e)
                 return False, \
                     _(
@@ -268,9 +281,10 @@ class Connection(BaseConnection):
                 config.APP_NAME, conn_id)
 
             pg_conn = psycopg2.connect(
-                host=mgr.host,
-                hostaddr=mgr.hostaddr,
-                port=mgr.port,
+                host=mgr.local_bind_host if mgr.use_ssh_tunnel else mgr.host,
+                hostaddr=mgr.local_bind_host if mgr.use_ssh_tunnel
+                else mgr.hostaddr,
+                port=mgr.local_bind_port if mgr.use_ssh_tunnel else mgr.port,
                 database=database,
                 user=user,
                 password=password,
@@ -291,6 +305,7 @@ class Connection(BaseConnection):
                 self._wait(pg_conn)
 
         except psycopg2.Error as e:
+            mgr.stop_ssh_tunnel()
             if e.pgerror:
                 msg = e.pgerror
             elif e.diag.message_detail:
@@ -317,6 +332,7 @@ class Connection(BaseConnection):
         try:
             status, msg = self._initialize(conn_id, **kwargs)
         except Exception as e:
+            mgr.stop_ssh_tunnel()
             current_app.logger.exception(e)
             self.conn = None
             if not self.reconnecting:
@@ -480,6 +496,12 @@ WHERE
         return True, None
 
     def __cursor(self, server_cursor=False):
+
+        # Check SSH Tunnel is alive or not. If used by the database
+        # server for the connection.
+        if self.manager.use_ssh_tunnel == 1:
+            self.manager.check_ssh_tunnel_alive()
+
         if self.wasConnected is False:
             raise ConnectionLost(
                 self.manager.sid,
@@ -1181,9 +1203,10 @@ WHERE
 
         try:
             pg_conn = psycopg2.connect(
-                host=mgr.host,
-                hostaddr=mgr.hostaddr,
-                port=mgr.port,
+                host=mgr.local_bind_host if mgr.use_ssh_tunnel else mgr.host,
+                hostaddr=mgr.local_bind_host if mgr.use_ssh_tunnel
+                else mgr.hostaddr,
+                port=mgr.local_bind_port if mgr.use_ssh_tunnel else mgr.port,
                 database=self.db,
                 user=mgr.user,
                 password=password,
@@ -1456,9 +1479,13 @@ Failed to reset the connection to the server due to following error:
 
             try:
                 pg_conn = psycopg2.connect(
-                    host=self.manager.host,
-                    hostaddr=self.manager.hostaddr,
-                    port=self.manager.port,
+                    host=self.manager.local_bind_host if
+                    self.manager.use_ssh_tunnel else self.manager.host,
+                    hostaddr=self.manager.local_bind_host if
+                    self.manager.use_ssh_tunnel else
+                    self.manager.hostaddr,
+                    port=self.manager.local_bind_port if
+                    self.manager.use_ssh_tunnel else self.manager.port,
                     database=self.db,
                     user=self.manager.user,
                     password=password,
diff --git a/web/pgadmin/utils/driver/psycopg2/server_manager.py b/web/pgadmin/utils/driver/psycopg2/server_manager.py
index 3066b1a..30a7a75 100644
--- a/web/pgadmin/utils/driver/psycopg2/server_manager.py
+++ b/web/pgadmin/utils/driver/psycopg2/server_manager.py
@@ -12,14 +12,19 @@ Implementation of ServerManager
 """
 import os
 import datetime
+import config
 from flask import current_app, session
 from flask_security import current_user
 from flask_babelex import gettext
 
+from pgadmin.utils import get_complete_file_path
 from pgadmin.utils.crypto import decrypt
 from .connection import Connection
-from pgadmin.model import Server
-from pgadmin.utils.exception import ConnectionLost
+from pgadmin.model import Server, User
+from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
+
+if config.SUPPORT_SSH_TUNNEL:
+    from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
 
 
 class ServerManager(object):
@@ -32,6 +37,9 @@ class ServerManager(object):
 
     def __init__(self, server):
         self.connections = dict()
+        self.local_bind_host = '127.0.0.1'
+        self.local_bind_port = None
+        self.tunnel_object = None
 
         self.update(server)
 
@@ -66,6 +74,20 @@ class ServerManager(object):
         self.sslcrl = server.sslcrl
         self.sslcompression = True if server.sslcompression else False
         self.service = server.service
+        if config.SUPPORT_SSH_TUNNEL:
+            self.use_ssh_tunnel = server.use_ssh_tunnel
+            self.tunnel_host = server.tunnel_host
+            self.tunnel_port = server.tunnel_port
+            self.tunnel_username = server.tunnel_username
+            self.tunnel_authentication = server.tunnel_authentication
+            self.tunnel_identity_file = server.tunnel_identity_file
+        else:
+            self.use_ssh_tunnel = 0
+            self.tunnel_host = None
+            self.tunnel_port = 22
+            self.tunnel_username = None
+            self.tunnel_authentication = None
+            self.tunnel_identity_file = None
 
         for con in self.connections:
             self.connections[con]._release()
@@ -167,7 +189,11 @@ WHERE db.oid = {0}""".format(did))
                             ))
 
         if database is None:
-            raise ConnectionLost(self.sid, None, None)
+            # Check SSH Tunnel is alive or not.
+            if self.use_ssh_tunnel == 1:
+                self.check_ssh_tunnel_alive()
+            else:
+                raise ConnectionLost(self.sid, None, None)
 
         my_id = (u'CONN:{0}'.format(conn_id)) if conn_id is not None else \
             (u'DB:{0}'.format(database))
@@ -247,6 +273,9 @@ WHERE db.oid = {0}""".format(did))
                     self.connections.pop(conn_info['conn_id'])
 
     def release(self, database=None, conn_id=None, did=None):
+        # Stop the SSH tunnel if created.
+        self.stop_ssh_tunnel()
+
         if did is not None:
             if did in self.db_info and 'datname' in self.db_info[did]:
                 database = self.db_info[did]['datname']
@@ -332,3 +361,73 @@ WHERE db.oid = {0}""".format(did))
                 self.password, current_user.password
             ).decode()
             os.environ[str(env)] = password
+
+    def create_ssh_tunnel(self, tunnel_password):
+        """
+        This method is used to create ssh tunnel and update the IP Address and
+        IP Address and port to localhost and the local bind port return by the
+        SSHTunnelForwarder class.
+        :return: True if tunnel is successfully created else error message.
+        """
+        # Fetch Logged in User Details.
+        user = User.query.filter_by(id=current_user.id).first()
+        if user is None:
+            return False, gettext("Unauthorized request.")
+
+        try:
+            tunnel_password = decrypt(tunnel_password, user.password)
+            # Handling of non ascii password (Python2)
+            if hasattr(str, 'decode'):
+                tunnel_password = \
+                    tunnel_password.decode('utf-8').encode('utf-8')
+            # password is in bytes, for python3 we need it in string
+            elif isinstance(tunnel_password, bytes):
+                tunnel_password = tunnel_password.decode()
+
+        except Exception as e:
+            current_app.logger.exception(e)
+            return False, "Failed to decrypt the SSH tunnel " \
+                          "password.\nError: {0}".format(str(e))
+
+        try:
+            # If authentication method is 1 then it uses identity file
+            # and password
+            if self.tunnel_authentication == 1:
+                self.tunnel_object = SSHTunnelForwarder(
+                    self.tunnel_host,
+                    ssh_username=self.tunnel_username,
+                    ssh_pkey=get_complete_file_path(self.tunnel_identity_file),
+                    ssh_private_key_password=tunnel_password,
+                    remote_bind_address=(self.host, self.port)
+                )
+            else:
+                self.tunnel_object = SSHTunnelForwarder(
+                    self.tunnel_host,
+                    ssh_username=self.tunnel_username,
+                    ssh_password=tunnel_password,
+                    remote_bind_address=(self.host, self.port)
+                )
+
+            self.tunnel_object.start()
+        except BaseSSHTunnelForwarderError as e:
+            current_app.logger.exception(e)
+            return False, "Failed to create the SSH tunnel." \
+                          "\nError: {0}".format(str(e))
+
+        # Update the port to communicate locally
+        self.local_bind_port = self.tunnel_object.local_bind_port
+
+        return True, None
+
+    def check_ssh_tunnel_alive(self):
+        # Check SSH Tunnel is alive or not. if it is not then
+        # raise the ConnectionLost exception.
+        if self.tunnel_object is None or not self.tunnel_object.is_active:
+            raise SSHTunnelConnectionLost(self.tunnel_host)
+
+    def stop_ssh_tunnel(self):
+        # Stop the SSH tunnel if created.
+        if self.tunnel_object and self.tunnel_object.is_active:
+            self.tunnel_object.stop()
+            self.local_bind_port = None
+            self.tunnel_object = None
diff --git a/web/pgadmin/utils/exception.py b/web/pgadmin/utils/exception.py
index 5fe2e92..62b7068 100644
--- a/web/pgadmin/utils/exception.py
+++ b/web/pgadmin/utils/exception.py
@@ -48,3 +48,35 @@ class ConnectionLost(HTTPException):
     def __repr__(self):
         return "Connection (id #{2}) lost for the server (#{0}) on " \
                "database ({1})".format(self.sid, self.db, self.conn_id)
+
+
+class SSHTunnelConnectionLost(HTTPException):
+    """
+    Exception when connection to SSH tunnel is lost
+    """
+
+    def __init__(self, _tunnel_host):
+        self.tunnel_host = _tunnel_host
+        HTTPException.__init__(self)
+
+    @property
+    def name(self):
+        return HTTP_STATUS_CODES.get(503, 'Service Unavailable')
+
+    def get_response(self, environ=None):
+        return service_unavailable(
+            _("Connection to the SSH Tunnel for host '{0}' has been lost. "
+              "Reconnect to the database server.").format(self.tunnel_host),
+            info="SSH_TUNNEL_CONNECTION_LOST",
+            data={
+                'tunnel_host': self.tunnel_host
+            }
+        )
+
+    def __str__(self):
+        return "Connection to the SSH Tunnel for host '{0}' has been lost. " \
+               "Reconnect to the database server".format(self.tunnel_host)
+
+    def __repr__(self):
+        return "Connection to the SSH Tunnel for host '{0}' has been lost. " \
+               "Reconnect to the database server".format(self.tunnel_host)
diff --git a/web/regression/javascript/browser/server_groups/servers/model_validation_spec.js b/web/regression/javascript/browser/server_groups/servers/model_validation_spec.js
index 85e51eb..57b8cf1 100644
--- a/web/regression/javascript/browser/server_groups/servers/model_validation_spec.js
+++ b/web/regression/javascript/browser/server_groups/servers/model_validation_spec.js
@@ -67,7 +67,52 @@ describe('Server#ModelValidation', () => {
         });
       });
 
-
+      describe('SSH Tunnel parameters', () => {
+        beforeEach(() => {
+          model.isNew.and.returnValue(true);
+          model.allValues['name'] = 'some name';
+          model.allValues['username'] = 'some username';
+          model.allValues['port'] = 12345;
+          model.allValues['host'] = 'some host';
+          model.allValues['db'] = 'some db';
+          model.allValues['hostaddr'] = '1.1.1.1';
+          model.allValues['use_ssh_tunnel'] = 1;
+        });
+        it('sets the "SSH Tunnel host must be specified." error', () => {
+          model.allValues['tunnel_port'] = 22;
+          model.allValues['tunnel_username'] = 'user1';
+          expect(modelValidation.validate()).toBe('SSH Tunnel host must be specified.');
+          expect(model.errorModel.set).toHaveBeenCalledWith({
+            tunnel_host:'SSH Tunnel host must be specified.'
+          });
+        });
+        it('sets the "SSH Tunnel port must be specified." error', () => {
+          model.allValues['tunnel_host'] = 'host';
+          model.allValues['tunnel_username'] = 'user1';
+          expect(modelValidation.validate()).toBe('SSH Tunnel port must be specified.');
+          expect(model.errorModel.set).toHaveBeenCalledWith({
+            tunnel_port:'SSH Tunnel port must be specified.'
+          });
+        });
+        it('sets the "SSH Tunnel username be specified." error', () => {
+          model.allValues['tunnel_host'] = 'host';
+          model.allValues['tunnel_port'] = 22;
+          expect(modelValidation.validate()).toBe('SSH Tunnel username must be specified.');
+          expect(model.errorModel.set).toHaveBeenCalledWith({
+            tunnel_username:'SSH Tunnel username must be specified.'
+          });
+        });
+        it('sets the "SSH Tunnel identity file be specified." error', () => {
+          model.allValues['tunnel_host'] = 'host';
+          model.allValues['tunnel_port'] = 22;
+          model.allValues['tunnel_username'] = 'user1';
+          model.allValues['tunnel_authentication'] = 1;
+          expect(modelValidation.validate()).toBe('SSH Tunnel identity file must be specified.');
+          expect(model.errorModel.set).toHaveBeenCalledWith({
+            tunnel_identity_file:'SSH Tunnel identity file must be specified.'
+          });
+        });
+      });
     });
 
     describe('When no parameters are valid', () => {
