diff --git a/poetry.lock b/poetry.lock
index bd1f119..68eefdb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -560,6 +560,14 @@ dev = ["Cython (>=0.29.20,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0
 docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"]
 test = ["aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
 
+[[package]]
+name = "websockets"
+version = "9.1"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+category = "main"
+optional = false
+python-versions = ">=3.6.1"
+
 [[package]]
 name = "zipp"
 version = "3.4.1"
@@ -588,7 +596,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7"
-content-hash = "8b1f2a003502e099b7a3995cfbbbc413401adf08c391d3b07e749e919916ab85"
+content-hash = "8e1e4f30b4d5f3cba3dc4b594a872f149b9aa3eeb6d3b5b721f5847c6aa2fd84"
 
 [metadata.files]
 asgiref = [
@@ -866,6 +874,41 @@ uvloop = [
     {file = "uvloop-0.15.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c"},
     {file = "uvloop-0.15.2.tar.gz", hash = "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01"},
 ]
+websockets = [
+    {file = "websockets-9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da"},
+    {file = "websockets-9.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b4ad84b156cf50529b8ac5cc1638c2cf8680490e3fccb6121316c8c02620a2e4"},
+    {file = "websockets-9.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2cf04601633a4ec176b9cc3d3e73789c037641001dbfaf7c411f89cd3e04fcaf"},
+    {file = "websockets-9.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5c8f0d82ea2468282e08b0cf5307f3ad022290ed50c45d5cb7767957ca782880"},
+    {file = "websockets-9.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:caa68c95bc1776d3521f81eeb4d5b9438be92514ec2a79fececda814099c8314"},
+    {file = "websockets-9.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d2c2d9b24d3c65b5a02cac12cbb4e4194e590314519ed49db2f67ef561c3cf58"},
+    {file = "websockets-9.1-cp36-cp36m-win32.whl", hash = "sha256:f31722f1c033c198aa4a39a01905951c00bd1c74f922e8afc1b1c62adbcdd56a"},
+    {file = "websockets-9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3ddff38894c7857c476feb3538dd847514379d6dc844961dc99f04b0384b1b1b"},
+    {file = "websockets-9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:51d04df04ed9d08077d10ccbe21e6805791b78eac49d16d30a1f1fe2e44ba0af"},
+    {file = "websockets-9.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f68c352a68e5fdf1e97288d5cec9296664c590c25932a8476224124aaf90dbcd"},
+    {file = "websockets-9.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b43b13e5622c5a53ab12f3272e6f42f1ce37cd5b6684b2676cb365403295cd40"},
+    {file = "websockets-9.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9147868bb0cc01e6846606cd65cbf9c58598f187b96d14dd1ca17338b08793bb"},
+    {file = "websockets-9.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:836d14eb53b500fd92bd5db2fc5894f7c72b634f9c2a28f546f75967503d8e25"},
+    {file = "websockets-9.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:48c222feb3ced18f3dc61168ca18952a22fb88e5eb8902d2bf1b50faefdc34a2"},
+    {file = "websockets-9.1-cp37-cp37m-win32.whl", hash = "sha256:900589e19200be76dd7cbaa95e9771605b5ce3f62512d039fb3bc5da9014912a"},
+    {file = "websockets-9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ab5ee15d3462198c794c49ccd31773d8a2b8c17d622aa184f669d2b98c2f0857"},
+    {file = "websockets-9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85e701a6c316b7067f1e8675c638036a796fe5116783a4c932e7eb8e305a3ffe"},
+    {file = "websockets-9.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b2e71c4670ebe1067fa8632f0d081e47254ee2d3d409de54168b43b0ba9147e0"},
+    {file = "websockets-9.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:230a3506df6b5f446fed2398e58dcaafdff12d67fe1397dff196411a9e820d02"},
+    {file = "websockets-9.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:7df3596838b2a0c07c6f6d67752c53859a54993d4f062689fdf547cb56d0f84f"},
+    {file = "websockets-9.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:826ccf85d4514609219725ba4a7abd569228c2c9f1968e8be05be366f68291ec"},
+    {file = "websockets-9.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0dd4eb8e0bbf365d6f652711ce21b8fd2b596f873d32aabb0fbb53ec604418cc"},
+    {file = "websockets-9.1-cp38-cp38-win32.whl", hash = "sha256:1d0971cc7251aeff955aa742ec541ee8aaea4bb2ebf0245748fbec62f744a37e"},
+    {file = "websockets-9.1-cp38-cp38-win_amd64.whl", hash = "sha256:7189e51955f9268b2bdd6cc537e0faa06f8fffda7fb386e5922c6391de51b077"},
+    {file = "websockets-9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9e5fd6dbdf95d99bc03732ded1fc8ef22ebbc05999ac7e0c7bf57fe6e4e5ae2"},
+    {file = "websockets-9.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9e7fdc775fe7403dbd8bc883ba59576a6232eac96dacb56512daacf7af5d618d"},
+    {file = "websockets-9.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:597c28f3aa7a09e8c070a86b03107094ee5cdafcc0d55f2f2eac92faac8dc67d"},
+    {file = "websockets-9.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:ad893d889bc700a5835e0a95a3e4f2c39e91577ab232a3dc03c262a0f8fc4b5c"},
+    {file = "websockets-9.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:1d6b4fddb12ab9adf87b843cd4316c4bd602db8d5efd2fb83147f0458fe85135"},
+    {file = "websockets-9.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:ebf459a1c069f9866d8569439c06193c586e72c9330db1390af7c6a0a32c4afd"},
+    {file = "websockets-9.1-cp39-cp39-win32.whl", hash = "sha256:be5fd35e99970518547edc906efab29afd392319f020c3c58b0e1a158e16ed20"},
+    {file = "websockets-9.1-cp39-cp39-win_amd64.whl", hash = "sha256:85db8090ba94e22d964498a47fdd933b8875a1add6ebc514c7ac8703eb97bbf0"},
+    {file = "websockets-9.1.tar.gz", hash = "sha256:276d2339ebf0df4f45df453923ebd2270b87900eda5dfd4a6b0cfa15f82111c3"},
+]
 zipp = [
     {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"},
     {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"},
diff --git a/pyproject.toml b/pyproject.toml
index 06c847e..c10021d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,6 +13,7 @@ uvloop = "^0.15.2"
 djangorestframework-dataclasses = "^0.9"
 social-auth-app-django = "^4.0.0"
 python-dotenv = "^0.17.1"
+websockets = "^9.1"
 
 [tool.poetry.dev-dependencies]
 flake8 = "^3.9.2"
diff --git a/src/services/appConnector.service.ts b/src/services/appConnector.service.ts
index f2d3627..a82d418 100644
--- a/src/services/appConnector.service.ts
+++ b/src/services/appConnector.service.ts
@@ -11,14 +11,13 @@ export class SocketProxy {
     webSocket: WebSocket
     initialBuffer: Buffer
 
-    constructor (...args) {
-        console.log('ws ctr', args)
+    constructor () {
         this.initialBuffer = Buffer.from('')
     }
 
-    connect (...args) {
-        console.log('ws connect', args)
-        this.webSocket = new WebSocket(`ws://${location.host}/api/1/gateway/tcp`)
+    connect (options) {
+        console.log('ws connect', options)
+        this.webSocket = new WebSocket(`ws://${location.host}/api/1/gateway/tcp/${options.host}:${options.port}`)
         this.webSocket.onopen = event => {
             this.connect$.next()
             this.connect$.complete()
diff --git a/terminus/app/consumers.py b/terminus/app/consumers.py
index 78ef87f..e9eee4b 100644
--- a/terminus/app/consumers.py
+++ b/terminus/app/consumers.py
@@ -1,31 +1,74 @@
 import asyncio
+import os
+import ssl
+import websockets
 from channels.generic.websocket import AsyncWebsocketConsumer
+from django.conf import settings
+from urllib.parse import quote
+
+
+class GatewayConnection:
+    _ssl_context: ssl.SSLContext = None
+
+    def __init__(self, host: str, port: int):
+        if settings.CONNECTION_GATEWAY_AUTH_KEY and not GatewayConnection._ssl_context:
+            ctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
+            ctx.load_cert_chain(
+                os.path.realpath(settings.CONNECTION_GATEWAY_AUTH_CERTIFICATE),
+                os.path.realpath(settings.CONNECTION_GATEWAY_AUTH_KEY),
+            )
+            if settings.CONNECTION_GATEWAY_AUTH_CA:
+                ctx.load_verify_locations(
+                    cafile=os.path.realpath(settings.CONNECTION_GATEWAY_AUTH_CA),
+                )
+                ctx.verify_mode = ssl.CERT_REQUIRED
+            GatewayConnection._ssl_context = ctx
+
+        proto = 'wss' if GatewayConnection._ssl_context else 'ws'
+        self.url = f'{proto}://localhost:9000/connect/{quote(host)}:{quote(str(port))}'
+
+    async def connect(self):
+        self.context = websockets.connect(self.url, ssl=GatewayConnection._ssl_context)
+        self.socket = await self.context.__aenter__()
+
+    async def send(self, data):
+        await self.socket.send(data)
+
+    def recv(self, timeout=None):
+        return asyncio.wait_for(self.socket.recv(), timeout)
+
+    async def close(self):
+        await self.socket.close()
+        await self.context.__aexit__(None, None, None)
 
 
 class TCPConsumer(AsyncWebsocketConsumer):
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.loop = asyncio.get_event_loop()
-
     async def connect(self):
-        self.reader, self.writer = await asyncio.open_connection('192.168.78.233', 22)
-        self._socket_reader = self.loop.create_task(self.socket_reader())
+        self.closed = False
+        self.conn = GatewayConnection(
+            self.scope['url_route']['kwargs']['host'],
+            int(self.scope['url_route']['kwargs']['port']),
+        )
+        await self.conn.connect()
         await self.accept()
+        self.reader = asyncio.get_event_loop().create_task(self.socket_reader())
 
     async def disconnect(self, close_code):
-        await self.writer.drain()
-        self.writer.close()
-        await self._socket_reader
+        self.closed = True
+        await self.conn.close()
 
     async def receive(self, bytes_data):
-        self.writer.write(bytes_data)
+        await self.conn.send(bytes_data)
 
     async def socket_reader(self):
         while True:
-            await self.reader._wait_for_data('read')
-            data = bytes(self.reader._buffer.copy())
-            if not data:
+            if self.closed:
+                return
+            try:
+                data = await self.conn.recv(timeout=10)
+            except asyncio.TimeoutError:
+                continue
+            except websockets.exceptions.ConnectionClosed:
                 await self.close()
                 return
-            del self.reader._buffer[:]
             await self.send(bytes_data=data)
diff --git a/terminus/app/urls.py b/terminus/app/urls.py
index 4b12470..40a6e3e 100644
--- a/terminus/app/urls.py
+++ b/terminus/app/urls.py
@@ -1,4 +1,4 @@
-from django.urls import path, include
+from django.urls import path, re_path, include
 from rest_framework import routers
 
 from . import api
@@ -22,5 +22,5 @@ urlpatterns = [
 ]
 
 websocket_urlpatterns = [
-    path('api/1/gateway/tcp', consumers.TCPConsumer.as_asgi()),
+    re_path(r'^api/1/gateway/tcp/(?P<host>[^/]+):(?P<port>\d+)$', consumers.TCPConsumer.as_asgi()),
 ]
diff --git a/terminus/settings.py b/terminus/settings.py
index e62e8ff..101a310 100644
--- a/terminus/settings.py
+++ b/terminus/settings.py
@@ -149,5 +149,17 @@ for key in [
     'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET',
     'SOCIAL_AUTH_MICROSOFT_GRAPH_KEY',
     'SOCIAL_AUTH_MICROSOFT_GRAPH_SECRET',
+    'CONNECTION_GATEWAY_AUTH_CA',
+    'CONNECTION_GATEWAY_AUTH_CERTIFICATE',
+    'CONNECTION_GATEWAY_AUTH_KEY',
 ]:
     globals()[key] = os.getenv(key)
+
+for key in [
+    'CONNECTION_GATEWAY_AUTH_CA',
+    'CONNECTION_GATEWAY_AUTH_CERTIFICATE',
+    'CONNECTION_GATEWAY_AUTH_KEY',
+]:
+    v = globals()[key]
+    if v and not os.path.exists(v):
+        raise ValueError(f'{v} does not exist')